#-*- encoding: utf-8 -*-

import os
import cv2
import sys
import time
import bisect
import tempfile
import threading
from math import ceil
from collections import namedtuple

from atx.device.windows import WindowsDevice
from atx.device.android import AndroidDevice
from atx.imutils import diff_rect

__CaptureRecord = namedtuple('__CaptureRecord', ('ctime', 'image'))
class CaptureRecord(__CaptureRecord):
    def __eq__(self, other):
        return self[0] == other[0]
    def __ne__(self, other):
        return not self == other
    def __gt__(self, other):
        return self[0] > other[0]
    def __ge__(self, other):
        return not self<other
    def __lt__(self, other):
        return self[0] < other[0]
    def __le__(self, other):
        return not other<self

__Step = namedtuple('Step', ('index', 'ctime', 'image', 'action', 'args'))
class Step(__Step):
    def to_script(self, timeout, indent=4):
        res = []
        # res.append('if d.exists("%s-target.png", %s):' % (self.index, ceil(timeout)))
        # res.append('%sw.on("%s-action.png", %s).click()' % (' '*indent, self.index, self.args))
        res.append('')

        return '\n'.join(res)

class BaseRecorder(object):

    default_radius = 180
    capture_interval = 0.1
    capture_maxnum = 30 # watch out your memory!
    look_ahead_num = 3 # diff with later screens to find target object 

    def __init__(self, device=None):
        self.steps = []
        self.device = None
        if device is not None:
            self.attach(device)

        self.running = False
        
        self.steps_lock = threading.Lock()
        self.step_index = 0
        self.last_step = None

        self.capture_lock = threading.Lock()
        self.capture_cache = []
        self.capture_tmpdir = os.path.join(os.getcwd(), 'screenshots', time.strftime("%Y%m%d"))
        if not os.path.exists(self.capture_tmpdir):
            os.makedirs(self.capture_tmpdir)

        t = threading.Thread(target=self.async_capture)
        t.setDaemon(True)
        t.start()

    def attach(self, device):
        '''Attach to device, if current device is not None, should
        detach from it first. '''
        raise NotImplementedError()

    def detach(self):
        '''Detach from current device.'''
        raise NotImplementedError()

    def run(self):
        '''Start watching inputs & device screen.'''
        self.hook()
        with self.capture_lock:
            self.running = True

    def hook(self):
        raise NotImplementedError()

    def stop(self):
        '''Stop record.'''
        with self.capture_lock:
            self.running = False
        self.unhook()
        # for test, dump steps when stop
        self.dump()

    def unhook(self):
        raise NotImplementedError()

    def next_index(self):
        with self.steps_lock:
            self.step_index += 1
            return self.step_index

    def on_click(self, position):
        '''Handle touch event.'''
        t = threading.Thread(target=self.__async_handle_touch, args=(position, ))
        t.setDaemon(True)
        t.start()

    on_touch = on_click

    def __async_handle_touch(self, position):
        t = time.time()
        # add a little delay, so we can check the screen after the touch
        time.sleep(self.capture_interval*(self.look_ahead_num+1))
        self.capture_lock.acquire()
        try:
            # trace back a few moments, find a untouched image
            # we're sure all  item[0] won't be same
            idx = bisect.bisect(self.capture_cache, (t, None))
            if idx == 0 or idx == len(self.capture_cache):
                print "no captured screens yet", idx
                return
            # just use two for now. 
            before, after = self.capture_cache[idx-1], self.capture_cache[idx:idx+self.look_ahead_num]
        finally:
            self.capture_lock.release()

        idx = self.next_index()
        t0, img0 = before
        for t1, img1 in after:
            rect = diff_rect(img0, img1, position)
            if rect is not None:
                print idx, "click at", position, 'found rect', rect
                break
        if rect is None:
            rect = self.__get_default_rect(img0.shape[:2], position)
            print idx, "click at", position, 'use default rect', rect

        x0, y0, x1, y1 = rect
        subimg = img0[y0:y1, x0:x1, :]
        filepath = os.path.join(self.capture_tmpdir, "%d-action.png" % idx)
        cv2.imwrite(filepath, subimg)
        # filepath = os.path.join(self.capture_tmpdir, "%d-2.png" % idx)
        # cv2.imwrite(filepath, img1)

        step = Step(idx, t, img0, 'touch', position)
        self.__pack_last_step(step)

    def __pack_last_step(self, step):
        # find target for last step and pack it.
        if not self.last_step:
            self.last_step = step
            return

        last_step = self.last_step
        rect = diff_rect(last_step.image, step.image)
        if rect is None:
            h, w = step.image.shape[:2]
            if step.action == 'touch':
                position = step.args
            else:
                position = w/2, h/2
            rect = self.__get_default_rect((h, w), position)

        x0, y0, x1, y1 = rect
        subimg = step.image[y0:y1, x0:x1, :]
        filepath = os.path.join(self.capture_tmpdir, "%d-target.png" % step.index)
        cv2.imwrite(filepath, subimg)

        timeout = step.ctime - last_step.ctime
        script = last_step.to_script(timeout)
        # save last step
        with self.steps_lock:
            self.steps.append(script)
        self.last_step = step

    def __get_default_rect(self, size, position):
        h, w = size
        x, y = position
        r = self.default_radius
        return (max(x-r,0), max(y-r,0), min(x+r,w), min(y+r,h))

    def on_double_click(self, position):
        '''Handle double click event'''
        print 'on_double_click'

    def on_drag(self, start, end, speed=1):
        '''Handle drag event.'''
        print 'on_drag'

    on_pan = on_drag

    def on_swipe(self, direction, percent=1, speed=1):
        '''Handle swipe event.
        Args:
            direction: up/down/left/right
            percent: swipe speed, 1 - 100
        '''
        print 'on_swipe', direction

    def on_pinch(self, in_or_out, percent=1, speed=1):
        '''Handle pinch event'''

    def on_key(self, key, flags=None):
        '''Handle key(down->up) event.'''
        print 'on_key', key

    def dump(self, filepath=None):
        '''Generate python scripts.'''
        filepath = os.path.join(self.capture_tmpdir, 'steps.py')
        with open(filepath, 'w') as f:
            with self.steps_lock:
                f.write('\n'.join(self.steps))

    def async_capture(self):
        '''Keep capturing device screen. Should run in background
        as a thread.'''
        while True:
            self.capture_lock.acquire()
            try:
                time.sleep(self.capture_interval)
                if not self.running or self.device is None:
                    continue
                img = self.device.screenshot_cv2()
                self.capture_cache.append(CaptureRecord(time.time(), img))

                # TODO: change capture_cache to a loop list
                while len(self.capture_cache) > self.capture_maxnum:
                    _, img = self.capture_cache.pop(0)

            finally:
                self.capture_lock.release()
