import sys
import wx
import threading
import Queue
import win32api, win32con, win32gui
from ctypes import *
import binascii

BYTE = c_byte
WORD = c_ushort
DWORD = c_ulong

_ole32 = oledll.ole32

_StringFromCLSID = _ole32.StringFromCLSID
_CoTaskMemFree = _ole32.CoTaskMemFree
_ProgIDFromCLSID = _ole32.ProgIDFromCLSID
_CLSIDFromString = _ole32.CLSIDFromString
_CLSIDFromProgID = _ole32.CLSIDFromProgID
_CoCreateGuid = _ole32.CoCreateGuid


class GUID(Structure):
    _fields_ = [("Data1", DWORD),
                ("Data2", WORD),
                ("Data3", WORD),
                ("Data4", BYTE * 8)]

    def __init__(self, name=None):
        if name is not None:
            _CLSIDFromString(unicode(name), byref(self))

    def __repr__(self):
        return u'GUID("%s")' % unicode(self)

    def __unicode__(self):
        p = c_wchar_p()
        _StringFromCLSID(byref(self), byref(p))
        result = p.value
        _CoTaskMemFree(p)
        return result
    __str__ = __unicode__

    def __cmp__(self, other):
        if isinstance(other, GUID):
            return cmp(buffer(self), buffer(other))
        return -1

    def __nonzero__(self):
        return str(buffer(self)) != "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"

    def __eq__(self, other):
        return isinstance(other, GUID) and \
               buffer(self) == buffer(other)

    def __hash__(self):
        # We make GUID instances hashable, although they are mutable.
        return hash(buffer(self))

    def copy(self):
        return GUID(unicode(self))

    def from_progid(cls, progid):
        """Get guid from progid, ...
        """
        if hasattr(progid, "_reg_clsid_"):
            progid = progid._reg_clsid_
        if isinstance(progid, cls):
            return progid
        elif isinstance(progid, basestring):
            if progid.startswith("{"):
                return cls(progid)
            inst = cls()
            _CLSIDFromProgID(unicode(progid), byref(inst))
            return inst
        else:
            raise TypeError("Cannot construct guid from %r" % progid)
    from_progid = classmethod(from_progid)

    def as_progid(self):
        "Convert a GUID into a progid"
        progid = c_wchar_p()
        _ProgIDFromCLSID(byref(self), byref(progid))
        result = progid.value
        _CoTaskMemFree(progid)
        return result

    def create_new(cls):
        "Create a brand new guid"
        guid = cls()
        _CoCreateGuid(byref(guid))
        return guid
    create_new = classmethod(create_new)


def wndproc(hWnd, msg, wParam, lParam):
    global INITIALIZING
    if msg == win32con.WM_POWERBROADCAST:
        if INITIALIZING is False:
            code = GetPowerMessage(lParam)
            if code == 0:
                strcode = 'OFF'
            else:
                strcode = 'ON'
            print("wndproc received WM_POWERBROADCAST: %s:%s" % (wParam, strcode))
        else:
            INITIALIZING = False
    return win32gui.CallWindowProc(oldWndProc, hWnd, msg, wParam, lParam)

def GetPowerMessage(lparam):
    try:
        buffer_size = 22 * sizeof(c_byte)
        msg_buffer = create_string_buffer(buffer_size)
        memmove(msg_buffer, lparam, buffer_size)
        ret = ord(msg_buffer[20])
        msg = ''
        for i in xrange(0, 21):
            msg = msg + str(binascii.hexlify(msg_buffer[i])) + "."
        return ret
    except Exception as e:
        pass
        return None

def RegisterPowerSettings(hwnd):
    win32api.SetLastError(0)
    if sys.getwindowsversion().major > 6:
        cguid = GUID('{6fe69556-704a-47a0-8f24-c28d936fda47}')  # Use for Windows 8 - GUID_CONSOLE_DISPLAY_STATE
    else:
        cguid = GUID('{02731015-4510-4526-99e6-e5a17ebd1aea}')  # GUID_MONITOR_POWER_ON
    unreg = windll.user32.RegisterPowerSettingNotification(hwnd, byref(cguid), 0)


def monitorpowermessages():
    global oldWndProc
    hwnd = win32gui.FindWindow(None, "MonitorSleepWindow")
    if hwnd is not None and hwnd != 0:
        win32gui.DestroyWindow(hwnd)
    win32api.SetLastError(0)
    hinst = win32api.GetModuleHandle(None)
    messageMap = {}
    wndclass = win32gui.WNDCLASS()
    wndclass.hInstance = hinst
    wndclass.lpszClassName = "MonitorSleepClass"
    wndclass.lpfnWndProc = messageMap
    myWindowClass = win32gui.RegisterClass(wndclass)
    hwnd = win32gui.CreateWindowEx(win32con.WS_EX_LEFT,
                                   myWindowClass,
                                   "MonitorSleepWindow",
                                   0,
                                   0,
                                   0,
                                   win32con.CW_USEDEFAULT,
                                   win32con.CW_USEDEFAULT,
                                   0,
                                   0,
                                   hinst,
                                   None)
    RegisterPowerSettings(hwnd)

    # Set WndProc
    win32api.SetLastError(0)
    WndProcType = WINFUNCTYPE(c_int, c_long, c_int, c_int, c_int)
    newWndProc = WndProcType(wndproc)
    oldWndProc = windll.user32.SetWindowLongW(hwnd, win32con.GWL_WNDPROC, newWndProc)
    while True:
        win32gui.PumpWaitingMessages()
        time.sleep(0.25)


class SplashMessage ( wx.Frame ):

    def __init__( self, parent, msg, showtime):
        wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = wx.EmptyString, pos = wx.DefaultPosition, size = wx.Size( 381,116 ), style = wx.STAY_ON_TOP|wx.DOUBLE_BORDER )
        self.timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.close, self.timer)
        self.SetSizeHintsSz( wx.DefaultSize, wx.DefaultSize )
        bSizer1 = wx.BoxSizer( wx.VERTICAL )
        bSizer1.SetMinSize( wx.Size( 1,3 ) )
        self.m_staticText1 = wx.StaticText( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 )
        self.m_staticText1.Wrap( -1 )
        bSizer1.Add( self.m_staticText1, 0, wx.ALL|wx.EXPAND, 5 )
        self.m_staticText4 = wx.StaticText( self, wx.ID_ANY, '  %s  ' % msg, wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_CENTRE )
        self.m_staticText4.Wrap( -1 )
        self.m_staticText4.SetFont( wx.Font( wx.NORMAL_FONT.GetPointSize(), 70, 90, 92, False, wx.EmptyString ) )
        bSizer1.Add( self.m_staticText4, 0, wx.ALL|wx.EXPAND, 5 )
        self.m_staticText3 = wx.StaticText( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 )
        self.m_staticText3.Wrap( -1 )
        bSizer1.Add( self.m_staticText3, 0, wx.ALL|wx.EXPAND, 5 )
        self.SetSizer( bSizer1 )
        bSizer1.Fit(bSizer1.GetContainingWindow())
        self.Layout()
        self.Centre( wx.BOTH )
        self.showtime = showtime
        self.timer.Start(showtime)

    def close(self, event):
        self.Close()
        self.Destroy()


class wxAppThread(threading.Thread):

    def __init__(self):
        super(wxAppThread, self).__init__()
        self.messageq = Queue.Queue()
        self.resultq = Queue.Queue()
        self.abortrequest = threading.Event()
        self.app = None
        self.lock = threading.Lock()
        self.lock.acquire()

    def run(self):
        self.app = wx.App(0, useBestVisual=True)
        self.lock.release()
        while not self.abortrequest.is_set():
            try:
                message = self.messageq.get(True, 0.05)
                if message[0] == 1:
                    pass
                elif message[0] == 2:
                    print 'Showing message: %s' % message[1]
                    self.lock.acquire()
                    splash = SplashMessage(None, message[1], message[2])
                    splash.Show()
                    self.lock.release()
                    self.app.MainLoop()
                elif message[0] == 3:
                    pass
                elif message[0] == 4:
                    self.lock.acquire()
                    print 'Sleeping Display'
                    x = win32api.PostMessage(win32con.HWND_BROADCAST, win32con.WM_SYSCOMMAND, win32con.SC_MONITORPOWER, 2)
                    self.lock.release()
            except Queue.Empty:
                continue
        wx.App_CleanUp()

    def abort(self):
        self.abortrequest.set()
        super(wxAppThread, self).join()

    def showSplashMessage(self, msg, showtime=1000):
        self.messageq.put([2, msg, showtime])

    def sleepDisplay(self):
        self.messageq.put([4,])

if __name__ == '__main__':
    import time
    INITIALIZING = True
    oldWndProc = None
    powermsg_thread = threading.Thread(target=monitorpowermessages)
    powermsg_thread.start()
    app = wxAppThread()
    app.start()
    # Test 1:
    print 'Test 1:'
    app.showSplashMessage('Test 1 Starting')
    time.sleep(2)
    app.sleepDisplay()
    time.sleep(10)
    app.showSplashMessage('Testing Resleep', 1000)
    time.sleep(10)

    # Test 2:
    print 'Test 2:'
    app.showSplashMessage('Test 2 Starting')
    time.sleep(2)
    app.sleepDisplay()
    app.showSplashMessage('Sleeping Display') # Having a frame open during the monitor power event prevents problem
    time.sleep(10)
    app.showSplashMessage('Testing Resleep', 1000)
    time.sleep(10)

    while True:
        time.sleep(.25)
    app.abort()
    pass