# MSHTML implementation of pyjd runtime.  Uses ctypes and comtypes to
# create a Win32 GDI window, embed an IWebBrowser2 OCX and allow access
# to the DOM functions using Python-DCOM.
#
# Copyright (C) 2009, 2010 Luke Kenneth Casson Leighton <lkcl@lkcl.net>
#
# get_xml_doc added 2010-09-08,
# copyright (C) Phil Charlesworth 2010, see Issue 481
#

#import win32traceutil

import traceback
import sys
import os
import time
import new

# these three are required pre-imported, for pyjamas to work
# with the pyjd imputil etc.  awful, i know...
import threading
import encodings
import encodings.cp437


from windows import *
from ctypes import *
from ctypes.wintypes import *

import comtypes
from comtypes import IUnknown, GUID, COMMETHOD, CoCreateInstanceEx
from comtypes.automation import IDispatch, VARIANT
from comtypes.client import wrap, GetModule
from comtypes.client.dynamic import Dispatch

import comtypes.gen

if not hasattr(sys, 'frozen'):
    GetModule('atl.dll')
    GetModule('shdocvw.dll')
    try:
        GetModule('msxml2.dll')
    except:
        pass
    try:
        GetModule('msxml3.dll')
    except:
        pass
    try:
        GetModule('msxml6.dll')
    except:
        pass
    GetModule('mshtml.tlb') 
    #GetModule('progdlg.tlb') 

from comtypes.gen import SHDocVw
from comtypes.gen import MSHTML
try:
    from comtypes.gen import MSXML2
except:
    pass
try:
    from comtypes.gen import MSXML6
except:
    pass
try:
    from comtypes.gen import MSXML3
except:
    pass

atl = windll.atl                  # If this fails, you need atl.dll

# do this after gen stuff, above
import mshtmlevents 

SID_SShellBrowser = GUID("{000214E2-0000-0000-C000-000000000046}")

class IPlayer2(IUnknown):
    _case_insensitive_ = True
    _iid_ = GUID('{6BF52A52-394A-11d3-B153-00C04F79FAA6}')
    _idlflags_ = []

    _methods_ = [
        ]

class IOleWindow(IUnknown):
    _case_insensitive_ = True
    u'IOleWindow Interface'
    _iid_ = GUID('{00000114-0000-0000-C000-000000000046}')
    _idlflags_ = []

    _methods_ = [
        COMMETHOD([], HRESULT, 'GetWindow',
                  ( ['in'], POINTER(c_void_p), 'pHwnd' ))
        ]

class IOleInPlaceActiveObject(IOleWindow):
    _iid_ = GUID("{00000117-0000-0000-C000-000000000046}")
    _idlflags_ = []
    _methods_ = IOleWindow._methods_ + [
        COMMETHOD([], HRESULT, 'TranslateAccelerator',
                  ( ['in'], POINTER(MSG), 'pMsg' ))
        ]
    

# http://www.mail-archive.com/comtypes-users@lists.sourceforge.net/msg00439.html
class IServiceProvider(IUnknown):
    _iid_ = GUID('{6D5140C1-7436-11CE-8034-00AA006009FA}')

    # Overridden QueryService to make it nicer to use (passing it an
    # interface and it returns a pointer to that interface)
    def QueryService(self, serviceIID, interface):
        p = POINTER(interface)()
        self._QueryService(byref(serviceIID), byref(interface._iid_), byref(p))
        return p

    _methods_ = [
        COMMETHOD([], HRESULT, 'QueryService',
                  ( ['in'], POINTER(GUID), 'guidService' ),
                  ( ['in'], POINTER(GUID), 'riid' ),
                  ( ['in'], POINTER(c_void_p), 'ppvObject' ))
        ]

#class IInputObject(IUnknown):
#    _iid_= GUID("{68284FAA-6A48-11D0-8C78-00C04FD918B4}")
#
#    _methods_= IUnknown._methods_ + [
#        (STDMETHOD (HRESULT, "UIActivateIO", BOOL, POINTER(MSG))),
#        (STDMETHOD (HRESULT, "HasFocusIO")),
#        (STDMETHOD (HRESULT, "TranslateAcceleratorIO", POINTER(MSG)))]

class EventSink(object):
    # some DWebBrowserEvents
    def OnVisible(self, this, *args):
        print "OnVisible", args

    def BeforeNavigate(self, this, *args):
        print "BeforeNavigate", args

    def NavigateComplete(self, this, *args):
        print "NavigateComplete", this, args
        return

    # some DWebBrowserEvents2
    def BeforeNavigate2(self, this, *args):
        print "BeforeNavigate2", args

    def NavigateComplete2(self, this, *args):
        print "NavigateComplete2", args

    def DocumentComplete(self, this, *args):
        print "DocumentComplete", args
        if self.workaround_ignore_first_doc_complete == False:
            # ignore first about:blank.  *sigh*...
            # TODO: work out how to parse *args byref VARIANT
            # in order to get at the URI.
            self.workaround_ignore_first_doc_complete = True
            return
            
        self._loaded()

    def NewWindow2(self, this, *args):
        print "NewWindow2", args
        return
        v = cast(args[1]._.c_void_p, POINTER(VARIANT))[0]
        v.value = True

    def NewWindow3(self, this, *args):
        print "NewWindow3", args
        return
        v = cast(args[1]._.c_void_p, POINTER(VARIANT))[0]
        v.value = True

fn_txt = """\
def event_fn(self, *args):
    #print "event %s", self, args
    #print "event callbacks", self._listeners
    callbacks = self._listeners.get('%s', [])
    for fn in callbacks:
        try:
            fn(self._sender, Dispatch(args[0]), True)
        except:
            traceback.print_exc()
            sys.stderr.flush()
"""

class EventCaller:
    def __init__(self, handler, name):
        self.handler = handler
        self.name = name
    def __call__(self, *args):
        callbacks = self.handler._listeners.get(self.name, [])
        #print "event", self.name, callbacks
        for fn in callbacks:
            try:
                fn(self.handler._sender, Dispatch(args[0]), True)
            except:
                sys.stderr.write( traceback.print_exc() )
                sys.stderr.flush()

class EventHandler(object):
    def __init__(self, sender):
        self._sender = sender
        self._listeners = {}
    def __getattr__(self, name):
        if name.startswith('__') and name.endswith('__'):
            raise AttributeError(name)
        #print "EventHandler requested ", name
        if name.startswith('_') or name == 'addEventListener':
            return self.__dict__[name]
        idx = name.find('_on')
        if idx >= 0:
            if idx > 0:
                name = name[idx+1:]
            #return EventCaller(self, name)
            exec fn_txt % (name[2:], name[2:])
            #exec fn_txt % (name[2:])
            #print event_fn
            return new.instancemethod(event_fn, self)
        raise AttributeError(name)

    def addEventListener(self, name, fn):
        if not self._listeners.has_key(name):
            self._listeners[name] = []
        self._listeners[name].append(fn)

class Browser(EventSink):
    def __init__(self, application, appdir):
        EventSink.__init__(self)
        self.platform = 'mshtml'
        self.application = application
        self.appdir = appdir
        self.already_initialised = False
        self.workaround_ignore_first_doc_complete = False
        self.window_handler = None
        self.node_handlers = {}

        # Create an instance of IE via AtlAxWin.
        atl.AtlAxWinInit()
        hInstance = GetModuleHandle(None)

        self.hwnd = CreateWindowEx(0,
                              "AtlAxWin",
                              "about:blank",
                              WS_OVERLAPPEDWINDOW |
                              WS_VISIBLE | 
                              WS_HSCROLL | WS_VSCROLL,
                              CW_USEDEFAULT,
                              CW_USEDEFAULT,
                              CW_USEDEFAULT,
                              CW_USEDEFAULT,
                              NULL,
                              NULL,
                              hInstance,
                              NULL)

        # Get the IWebBrowser2 interface for the IE control.
        self.pBrowserUnk = POINTER(IUnknown)()
        atl.AtlAxGetControl(self.hwnd, byref(self.pBrowserUnk))
        # the wrap call querys for the default interface
        self.pBrowser = wrap(self.pBrowserUnk)
        self.pBrowser.RegisterAsBrowser = True
        self.pBrowser.AddRef()

        self.conn = mshtmlevents.GetEvents(self.pBrowser, sink=self,
                        interface=SHDocVw.DWebBrowserEvents2)

        #print "browser HWND", SetFocus(self.pBrowser.HWND)

    def _alert(self, txt):
        self.getDomWindow().alert(txt)

    def load_app(self):

        uri = self.application
        if uri.find(":") == -1:
            # assume file
            uri = 'file://'+os.path.abspath(uri)

        #print "load_app", uri

        self.application = uri
        v = byref(VARIANT())
        self.pBrowser.Navigate(uri, v, v, v, v)

        # Show Window
        cw = c_int(self.hwnd)
        ShowWindow(cw, c_int(SW_SHOW))
        UpdateWindow(cw)


        # http://msdn.microsoft.com/en-us/library/aa752126%28VS.85%29.aspx
        wba = self.pBrowser.QueryInterface(IServiceProvider)
        wn = wrap(wba.QueryService(SID_SShellBrowser, IOleWindow))

        hwnd = c_void_p(0)
        pHnd = byref(hwnd)
        wn.GetWindow(pHnd)

        #PostMessage(pHnd.value, WM_SETFOCUS,0,0)
        SetFocus(hwnd)
        #print self.hwnd, pHnd.value

    def getDomDocument(self):
        return Dispatch(self.pBrowser.Document)

    def getDomWindow(self):
        return self.getDomDocument().parentWindow

    def _addXMLHttpRequestEventListener(self, node, event_name, event_fn):
        
        #print "_addXMLHttpRequestEventListener", event_name

        rcvr = mshtmlevents._DispEventReceiver()
        rcvr.dispmap = {0: event_fn}

        #print rcvr
        rcvr.sender = node
        #print rcvr.sender
        ifc = rcvr.QueryInterface(IDispatch)
        #print ifc
        v = VARIANT(ifc)
        #print v
        setattr(node, event_name, v)
        return ifc

    def addEventListener(self, node, event_name, event_fn):
        
        rcvr = mshtmlevents._DispEventReceiver()
        rcvr.dispmap = {0: event_fn}

        rcvr.sender = node
        ifc = rcvr.QueryInterface(IDispatch)
        v = VARIANT(ifc)
        setattr(node, "on"+event_name, v)
        return ifc

        rcvr = mshtmlevents.GetDispEventReceiver(MSHTML.HTMLElementEvents2, event_fn, "on%s" % event_name)
        rcvr.sender = node
        ifc = rcvr.QueryInterface(IDispatch)
        node.attachEvent("on%s" % event_name, ifc)
        return ifc

    def mash_attrib(self, attrib_name):
        return attrib_name

    def _addWindowEventListener(self, event_name, event_fn):
        
        #print "_addWindowEventListener", event_name, event_fn
        #rcvr = mshtmlevents.GetDispEventReceiver(MSHTML.HTMLWindowEvents,
        #                   event_fn, "on%s" % event_name)
        #print rcvr
        #rcvr.sender = self.getDomWindow()
        #print rcvr.sender
        #ifc = rcvr.QueryInterface(IDispatch)
        #print ifc
        #v = VARIANT(ifc)
        #print v
        #setattr(self.getDomWindow(), "on%s" % event_name, v)
        #return ifc

        wnd = self.pBrowser.Document.parentWindow
        if self.window_handler is None:
            self.window_handler = EventHandler(self)
            self.window_conn = mshtmlevents.GetEvents(wnd,
                                        sink=self.window_handler,
                                    interface=MSHTML.HTMLWindowEvents2)
        self.window_handler.addEventListener(event_name, event_fn)
        return event_name # hmmm...

    def getXmlHttpRequest(self):
        #print "getXMLHttpRequest"
        o = comtypes.client.CreateObject('MSXML2.XMLHTTP.3.0')
        #print "getXMLHttpRequest", o
        return Dispatch(o)
        
    def getDOMParser(self):
        o = comtypes.client.CreateObject('MSXML.DOMDocument')
        return Dispatch(o)
    
    def getUri(self):
        return self.application

    def GetPlayer(self, obj):
        return obj.QueryInterface(IPlayer2)

    def CoCreateInstanceEx(self, clsid):
        return CoCreateInstanceEx(GUID(clsid))

    def _loaded(self):

        #print "loaded"

        if self.already_initialised:
            return
        self.already_initialised = True

        self._addWindowEventListener("unload", self.on_unload_callback)

        from __pyjamas__ import pygwt_processMetas, set_main_frame
        set_main_frame(self)

        (pth, app) = os.path.split(self.application)
        if self.appdir:
            pth = os.path.abspath(self.appdir)
        sys.path.append(pth)
        
    def on_unload_callback(self, *args):
        PostQuitMessage(0)

global timer_q
timer_q = []

WM_USER_TIMER = RegisterWindowMessage("Timer Notify")

global wv
wv = None

def MainWin(one_event):

    # Pump Messages
    msg = MSG()
    pMsg = pointer(msg)
    
    while 1:
        res = GetMessage( pMsg, NULL, 0, 0)
        if res == -1:
            return 0
        if res == 0:
            break 

        if timer_q:
            fn = timer_q.pop()
            fn()

        if msg.message == WM_USER_TIMER:
            continue

        app = wv.pBrowser.Application
        ao = app.QueryInterface(IOleInPlaceActiveObject)
        if ao.TranslateAccelerator(pMsg):
        #if not TranslateAccelerator( 
        #                wv.hwnd,  #handle to receiving window 
        #                NULL,    #handle to active accelerator table 
        #                pMsg):     #message data 
            TranslateMessage(pMsg)
            DispatchMessage(pMsg)

        if one_event:
            break

    return msg.wParam
    
def add_timer_queue(fn):
    timer_q.append(fn)
    PostMessage(c_int(wv.hwnd), UINT(WM_USER_TIMER), WPARAM(0), LPARAM(0xffff))

def is_loaded():
    return wv.already_initialised

def run(one_event=False, block=True):
    try:
        MainWin(one_event) # TODO: ignore block arg for now
    except:
        sys.stderr.write( traceback.print_exc() )
        sys.stderr.flush()

def setup(application, appdir=None, width=800, height=600):

    global wv
    wv = Browser(application, appdir)

    wv.load_app()

    while 1:
        if is_loaded():
            return
        run(one_event=True)

