#!/usr/bin/env python
# -*- coding: utf-8 -*-
 
 
#from zope.interface import Interface
from PIL import ImageGrab
import win32api,win32con,win32gui,win32process
import autopy
import ctypes,ctypes.wintypes
import pygame
import os
import time


class RECT(ctypes.Structure):# classtype for window position
    _fields_ = [('left', ctypes.c_long),
                ('top', ctypes.c_long),
                ('right', ctypes.c_long),
                ('bottom', ctypes.c_long)]
    def __str__(self):
        return str((self.left, self.top, self.right, self.bottom))

# The following two dictionaries don't contain all the characters; please check before you use them
# A dictionary to hold the characters, which may be used in text, and their corresponding key values
OriginalCodes = {
           '\n':0x0D, #enter
           ' ':0x20, #spacebar
           '\t':0x09, #TAB
           '0':0x30,
           '1':0x31,
           '2':0x32,
           '3':0x33,
           '4':0x34,
           '5':0x35,
           '6':0x36,
           '7':0x37,
           '8':0x38,
           '9':0x39,
           'a':0x41,
           'b':0x42,
           'c':0x43,
           'd':0x44,
           'e':0x45,
           'f':0x46,
           'g':0x47,
           'h':0x48,
           'i':0x49,
           'j':0x4A,
           'k':0x4B,
           'l':0x4C,
           'm':0x4D,
           'n':0x4E,
           'o':0x4F,
           'p':0x50,
           'q':0x51,
           'r':0x52,
           's':0x53,
           't':0x54,
           'u':0x55,
           'v':0x56,
           'w':0x57,
           'x':0x58,
           'y':0x59,
           'z':0x5A,
           '+':0xBB,
           ',':0xBC,
           '-':0xBD,
           '.':0xBE,
           '/':0xBF,
           '`':0xC0,
           ';':0xBA,
           '[':0xDB,
           '\\':0xDC,
           ']':0xDD,
           "'":0xDE,
           '`':0xC0}
# A dictionary which contains the characters should be typed with shift
ShiftCodes = {  
           'A':0x41,
           'B':0x42,
           'C':0x43,
           'D':0x44,
           'E':0x45,
           'F':0x46,
           'G':0x47,
           'H':0x48,
           'I':0x49,
           'J':0x4A,
           'K':0x4B,
           'L':0x4C,
           'M':0x4D,
           'N':0x4E,
           'O':0x4F,
           'P':0x50,
           'Q':0x51,
           'R':0x52,
           'S':0x53,
           'T':0x54,
           'U':0x55,
           'V':0x56,
           'W':0x57,
           'X':0x58,
           'Y':0x59,
           'Z':0x5A,
           ')':0x30,
           '!':0x31,
           '@':0x32,
           '#':0x33,
           '$':0x34,
           '%':0x35,
           '^':0x36,
           '&':0x37,
           '*':0x38,
           '(':0x39,
           '?':0xBF,
           '~':0xC0,
           ':':0xBA,
           '{':0xDB,
           '|':0xDC,
           '}':0xDD,
           "\"":0xDE}


#class Device(Interface):
class Device():
    ''' Interface documentation '''
    def __init__(self,filename=None):
        if '.exe' != filename[-4:len(filename)]:#check the name has postfix ".exe" or not; if not, add ".exe" to the end
            self.filename = filename+".exe"
        else:
            self.filename = filename
        HWND=self._getHandleThroughFilename()
        self.HWND = self._chosegamehandle(HWND)
        # print 'HWND:', self.HWND
        if not self.HWND:
            raise Exception('Can not find target application process')
        
    def _getHandleThroughFilename(self):
        Psapi = ctypes.WinDLL('Psapi.dll')
        EnumProcesses = Psapi.EnumProcesses
        EnumProcesses.restype = ctypes.wintypes.BOOL
        GetProcessImageFileName = Psapi.GetProcessImageFileNameA
        GetProcessImageFileName.restype = ctypes.wintypes.DWORD

        Kernel32 = ctypes.WinDLL('kernel32.dll')
        OpenProcess = Kernel32.OpenProcess
        OpenProcess.restype = ctypes.wintypes.HANDLE
        TerminateProcess = Kernel32.TerminateProcess
        TerminateProcess.restype = ctypes.wintypes.BOOL
        CloseHandle = Kernel32.CloseHandle
        

        MAX_PATH = 260
        PROCESS_TERMINATE = 0x0001
        PROCESS_QUERY_INFORMATION = 0x0400

        count = 32
        while True:
            ProcessIds = (ctypes.wintypes.DWORD*count)()
            cb = ctypes.sizeof(ProcessIds)
            BytesReturned = ctypes.wintypes.DWORD()
            if EnumProcesses(ctypes.byref(ProcessIds), cb, ctypes.byref(BytesReturned)):
                if BytesReturned.value<cb:
                    break
                else:
                    count *= 2
            else:
                raise Exception('Call to EnumProcesses failed')

        for index in range(BytesReturned.value / ctypes.sizeof(ctypes.wintypes.DWORD)):
            ProcessId = ProcessIds[index]
            hProcess = OpenProcess(PROCESS_TERMINATE | PROCESS_QUERY_INFORMATION, False, ProcessId)
            if hProcess:
                ImageFileName = (ctypes.c_char*MAX_PATH)()
                if GetProcessImageFileName(hProcess, ImageFileName, MAX_PATH)>0:
                    filename = os.path.basename(ImageFileName.value)
                    if filename == self.filename:
                        break
                #TerminateProcess(hProcess, 1)
                CloseHandle(hProcess)
                
        def get_hwnds_for_pid(pid):
            def callback (hwnd, hwnds):
                if win32gui.IsWindowVisible (hwnd) and win32gui.IsWindowEnabled (hwnd):
                    _, found_pid = win32process.GetWindowThreadProcessId (hwnd)
                    if found_pid == pid:
                        hwnds.append (hwnd)
                    return True
            hwnds = []
            win32gui.EnumWindows(callback, hwnds)
            return hwnds
        return get_hwnds_for_pid(ProcessId)
    
    def _chosegamehandle(self,HWND):
            if not HWND : return HWND
            else:
                for handle in HWND:
                    windowtext = win32gui.GetWindowText(handle)
                    if ":" not in windowtext: 
                        return handle
                        
    
        
    def _range(self):
        ''' Get Windows rectangle position '''
        rect = RECT()
        ctypes.windll.user32.GetWindowRect(self.HWND,ctypes.byref(rect))
        range_ = (rect.left+2,rect.top+2,rect.right-2,rect.bottom-2)
        return range_

    def _resetpt(self, x, y):
        left, top, _, _ = self._range()
        x, y = left+x, top+y
        return x, y
    
    def snapshot(self, filename=None ):
        ''' Capture device screen '''
        range_ = self._range()
        win32gui.SetForegroundWindow(self.HWND)
        time.sleep(0.1)
        pic = ImageGrab.grab(range_)
        if filename !=None:
            pic.save(filename)
        return pic
        
    def touch(self, x, y, duration=0.1):
        ''' Simulate touch '''
        (ox, oy) = self.mouseposition() # remember mouse position
        x, y = self._resetpt(x, y)
        win32api.SetCursorPos((x,y))

        win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN,0,0)
        time.sleep(duration)
        win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP,0,0)
        win32api.SetCursorPos((ox,oy)) # move back mouse position

        
    def drag(self, (x1, y1), (x2, y2), duration=0.5):
        ''' Simulate drag '''
        (ox, oy) = self.mouseposition() # remember mouse position
        x1, y1 = self._resetpt(x1, y1)
        x2, y2 = self._resetpt(x2, y2)
        win32api.SetCursorPos((x1, y1))
        win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN,0,0)
        autopy.mouse.smooth_move(x2, y2)
        win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP,0,0)
        win32api.SetCursorPos((ox,oy)) # move back mouse position
        
    def type(self, text):
        ''' Type text into device '''
        
        for c in text:
            if c in ShiftCodes:  #judge the character is a capital letter or not; 16 is the value of shift
                win32api.keybd_event(16,win32api.MapVirtualKey(16,0),0,0)
                win32api.keybd_event(ShiftCodes[c],win32api.MapVirtualKey(ShiftCodes[c],0),0,0)
                win32api.keybd_event(16,win32api.MapVirtualKey(16,0),win32con.KEYEVENTF_KEYUP,0)
                win32api.keybd_event(ShiftCodes[c],win32api.MapVirtualKey(ShiftCodes[c],0),win32con.KEYEVENTF_KEYUP,0)
            elif c in OriginalCodes:    #judge the character is a capital letter or not
                win32api.keybd_event(OriginalCodes[c],win32api.MapVirtualKey(OriginalCodes[c],0),0,0)
                win32api.keybd_event(OriginalCodes[c],win32api.MapVirtualKey(OriginalCodes[c],0),win32con.KEYEVENTF_KEYUP,0)
    
    def shape(self):
        ''' Return (width, height) '''
        pic = self.snapshot(filename=None)
        (width, height) =  pic.size
        return (width, height)
    
    def cutimage(self, filename=None):
        '''Cut picture from target window'''
        pic = self.snapshot(filename=None)
        mode = pic.mode
        size = pic.size
        data = pic.tostring()
        surface = pygame.image.fromstring(data, size, mode)
        s=pygame.display.set_mode(surface.get_size())
        box=k=0
        c=1
        while c:
            for evt in pygame.event.get():
                evt_type=evt.type
                if evt_type==5: x1,y1=evt.pos;k=1        # mouse down
                if evt_type==4 and k: x2,y2=evt.pos; box=(x1,y1,x2-x1,y2-y1) #mouse up and calculate the box range
                if evt_type==6: c=0
                s.blit(surface,(0,0))
                if box and c:pygame.draw.rect(s,0,box,1) 
                pygame.display.flip()
        q=s.subsurface(box)
        if filename!=None:
            pygame.image.save(q,filename)
        pygame.quit()
        
    def mouseposition(self):
        '''Get the current position of mouse'''
        class POINT(ctypes.Structure):
            _fields_ = [
                        ("x", ctypes.c_ulong),
                        ("y", ctypes.c_ulong)
                        ]
        point = POINT()
        ctypes.windll.User32.GetCursorPos(ctypes.byref(point))
        return point.x, point.y 
    
    def windowposition(self):
        '''Get the position of target window while given its name'''
        rect = RECT()
        ctypes.windll.user32.GetWindowRect(self.HWND,ctypes.byref(rect))
        return rect.left,rect.top,rect.right,rect.bottom
    
    def start(self, appname, extra={}):
        '''Start an app, TODO(not good now)'''
        '''appname is not used in windows interferences'''
        Path = extra.get('path')
        os.system('cd /d '+Path+' && '+'start '+self.filename)
        HWND=self._getHandleThroughFilename()
        self.HWND = self._chosegamehandle(HWND)
        if self.HWND==0:
            raise Exception(u'Target application is not successfully started')
        
    def stop(self, appname, extra={}):
        '''appname is not used in windows interferences'''
        win32gui.SendMessage(self.HWND,win32con.WM_CLOSE,0,0)
        
    def getCpu(self, appname):
        ''' Return cpu: float (Cpu usage for app) '''
        return 0.0
        
    def getMem(self, appname):
        ''' Return mem: float (unit MB, memory usage for app) '''
        return {}