import sys

import pythoncom
import pywintypes
from win32com.axscript import axscript
from win32com.axscript.server import axsite
from win32com.server import connect, util


class MySite(axsite.AXSite):
    def OnScriptError(self, error):
        exc = error.GetExceptionInfo()
        context, line, char = error.GetSourcePosition()
        print(" >Exception:", exc[1])
        try:
            st = error.GetSourceLineText()
        except pythoncom.com_error:
            st = None
        if st is None:
            st = ""
        text = st + "\n" + (" " * (char - 1)) + "^" + "\n" + exc[2]
        for line in text.splitlines():
            print("  >" + line)


class MyCollection(util.Collection):
    def _NewEnum(self):
        print("Making new Enumerator")
        return util.Collection._NewEnum(self)


class Test:
    _public_methods_ = ["echo"]
    _public_attrs_ = ["collection", "verbose"]

    def __init__(self):
        self.verbose = 0
        self.collection = util.wrap(MyCollection([1, "Two", 3]))
        self.last = ""

    #    self._connect_server_ = TestConnectServer(self)

    def echo(self, *args):
        self.last = "".join(map(str, args))
        if self.verbose:
            for arg in args:
                print(arg, end=" ")
            print()


#    self._connect_server_.Broadcast(last)


#### Connections currently won't work, as there is no way for the engine to
#### know what events we support.  We need typeinfo support.

IID_ITestEvents = pywintypes.IID("{8EB72F90-0D44-11d1-9C4B-00AA00125A98}")


class TestConnectServer(connect.ConnectableServer):
    _connect_interfaces_ = [IID_ITestEvents]

    # The single public method that the client can call on us
    # (ie, as a normal COM server, this exposes just this single method.
    def __init__(self, object):
        self.object = object

    def Broadcast(self, arg):
        # Simply broadcast a notification.
        self._BroadcastNotify(self.NotifyDoneIt, (arg,))

    def NotifyDoneIt(self, interface, arg):
        interface.Invoke(1000, 0, pythoncom.DISPATCH_METHOD, 1, arg)


VBScript = """\
prop = "Property Value"

sub hello(arg1)
   test.echo arg1
end sub

sub testcollection
   test.verbose = 1
   for each item in test.collection
     test.echo "Collection item is", item
   next
end sub
"""

PyScript = """\
print("PyScript is being parsed...")\n

prop = "Property Value"
def hello(arg1):
   test.echo(arg1)
   pass

def testcollection():
   test.verbose = 1
#   test.collection[1] = "New one"
   for item in test.collection:
     test.echo("Collection item is", item)
   pass
"""

ErrScript = """\
bad code for everyone!
"""


def TestEngine(engineName, code, bShouldWork=1):
    echoer = Test()
    model = {
        "test": util.wrap(echoer),
    }

    site = MySite(model)
    engine = site._AddEngine(engineName)
    engine.AddCode(code, axscript.SCRIPTTEXT_ISPERSISTENT)
    try:
        engine.Start()
    finally:
        if not bShouldWork:
            engine.Close()
            return
    doTestEngine(engine, echoer)
    # re-transition the engine back to the UNINITIALIZED state, a-la ASP.
    engine.eScript.SetScriptState(axscript.SCRIPTSTATE_UNINITIALIZED)
    engine.eScript.SetScriptSite(util.wrap(site))
    print("restarting")
    engine.Start()
    # all done!
    engine.Close()


def doTestEngine(engine, echoer):
    # Now call into the scripts IDispatch
    from win32com.client.dynamic import Dispatch

    ob = Dispatch(engine.GetScriptDispatch())
    try:
        ob.hello("Goober")
    except pythoncom.com_error as exc:
        print("***** Calling 'hello' failed", exc)
        return
    if echoer.last != "Goober":
        print(f"***** Function call didnt set value correctly {echoer.last!r}")

    if str(ob.prop) != "Property Value":
        print(f"***** Property Value not correct - {ob.prop!r}")

    ob.testcollection()

    # Now make sure my engines can evaluate stuff.
    result = engine.eParse.ParseScriptText(
        "1+1", None, None, None, 0, 0, axscript.SCRIPTTEXT_ISEXPRESSION
    )
    if result != 2:
        print("Engine could not evaluate '1+1' - said the result was", result)


def dotestall():
    for i in range(10):
        TestEngine("Python", PyScript)
        print(sys.gettotalrefcount())


# print("Testing Exceptions")
# try:
#     TestEngine("Python", ErrScript, 0)
# except pythoncom.com_error:
#     pass


def testall():
    dotestall()
    pythoncom.CoUninitialize()
    print(
        "AXScript Host worked correctly - %d/%d COM objects left alive."
        % (pythoncom._GetInterfaceCount(), pythoncom._GetGatewayCount())
    )


if __name__ == "__main__":
    testall()
