#!/usr/bin/env python
# -*- coding: utf-8 -*-
# FIXME: TypeError: 'EventBinding' object is not callable

from __future__ import print_function

import clr
import gc
import sys

import System

from .utils import (CallableHandler, ClassMethodHandler, GenericHandler,
                    HelloClass, StaticMethodHandler, VarCallableHandler,
                    VariableArgsHandler, hello_func)


class LeakTest(object):
    """A leak-check test for the objects implemented in the managed
       runtime. For each kind of object tested, memory should reach
       a particular level after warming up and stay essentially the
       same, net of minor fluctuation induced by GC."""

    def __init__(self):
        self.count = 50000
        self.quiet = 0
        self._ws = 0

    def notify(self, msg):
        if not self.quiet:
            print(msg)

    def start_test(self):
        System.GC.Collect(System.GC.MaxGeneration)
        gc.collect()
        self._ws = System.Environment.WorkingSet

    def end_test(self):
        start = self._ws
        System.GC.Collect(System.GC.MaxGeneration)
        gc.collect()
        end = System.Environment.WorkingSet
        diff = end - start
        if diff > 0:
            diff = '+{0}'.format(diff)
        else:
            diff = '{0}'.format(diff)
        print("  start: {0}  end: {1} diff: {2}".format(start, end, diff))
        print("")

    def run(self):
        self.testModules()
        self.testClasses()
        self.testEnumerations()
        self.testEvents()
        self.testDelegates()

    def report(self):
        gc.collect()
        dicttype = type({})
        for item in gc.get_objects():
            if type(item) != dicttype:
                print(item, sys.getrefcount(item))

    def test_modules(self):
        self.notify("Running module leak check...")

        for i in range(self.count):
            if i == 10:
                self.start_test()

            __import__('clr')
            __import__('System')
            __import__('System.IO')
            __import__('System.Net')
            __import__('System.Xml')

        self.end_test()

    def test_classes(self):
        from System.Collections import Hashtable
        from Python.Test import StringDelegate

        self.notify("Running class leak check...")

        for i in range(self.count):
            if i == 10:
                self.start_test()

            # Reference type
            x = Hashtable()
            del x

            # Value type
            x = System.Int32(99)
            del x

            # Delegate type
            x = StringDelegate(hello_func)
            del x

        self.end_test()

    def test_enumerations(self):
        import Python.Test as Test

        self.notify("Running enum leak check...")

        for i in range(self.count):
            if i == 10:
                self.start_test()

            x = Test.ByteEnum.Zero
            del x

            x = Test.SByteEnum.Zero
            del x

            x = Test.ShortEnum.Zero
            del x

            x = Test.UShortEnum.Zero
            del x

            x = Test.IntEnum.Zero
            del x

            x = Test.UIntEnum.Zero
            del x

            x = Test.LongEnum.Zero
            del x

            x = Test.ULongEnum.Zero
            del x

        self.end_test()

    def test_events(self):
        from Python.Test import EventTest, EventArgsTest

        self.notify("Running event leak check...")

        for i in range(self.count):
            if i == 10:
                self.start_test()

            testob = EventTest()

            # Instance method event handler
            handler = GenericHandler()
            testob.PublicEvent += handler.handler
            testob.PublicEvent(testob, EventArgsTest(10))
            testob.PublicEvent -= handler.handler
            del handler

            # Vararg method event handler
            handler = VariableArgsHandler()
            testob.PublicEvent += handler.handler
            testob.PublicEvent(testob, EventArgsTest(10))
            testob.PublicEvent -= handler.handler
            del handler

            # Callable object event handler
            handler = CallableHandler()
            testob.PublicEvent += handler
            testob.PublicEvent(testob, EventArgsTest(10))
            testob.PublicEvent -= handler
            del handler

            # Callable vararg event handler
            handler = VarCallableHandler()
            testob.PublicEvent += handler
            testob.PublicEvent(testob, EventArgsTest(10))
            testob.PublicEvent -= handler
            del handler

            # Static method event handler
            handler = StaticMethodHandler()
            StaticMethodHandler.value = None
            testob.PublicEvent += handler.handler
            testob.PublicEvent(testob, EventArgsTest(10))
            testob.PublicEvent -= handler.handler
            del handler

            # Class method event handler
            handler = ClassMethodHandler()
            ClassMethodHandler.value = None
            testob.PublicEvent += handler.handler
            testob.PublicEvent(testob, EventArgsTest(10))
            testob.PublicEvent -= handler.handler
            del handler

            # Managed instance event handler
            testob.PublicEvent += testob.GenericHandler
            testob.PublicEvent(testob, EventArgsTest(10))
            testob.PublicEvent -= testob.GenericHandler

            # Static managed event handler
            testob.PublicEvent += EventTest.StaticHandler
            testob.PublicEvent(testob, EventArgsTest(10))
            testob.PublicEvent -= EventTest.StaticHandler

            # Function event handler
            dict_ = {'value': None}

            def handler(sender, args, dict_=dict_):
                dict_['value'] = args.value

            testob.PublicEvent += handler
            testob.PublicEvent(testob, EventArgsTest(10))
            testob.PublicEvent -= handler
            del handler

        self.end_test()

    def test_delegates(self):
        from Python.Test import DelegateTest, StringDelegate

        self.notify("Running delegate leak check...")

        for i in range(self.count):
            if i == 10:
                self.start_test()

            # Delegate from function
            testob = DelegateTest()
            d = StringDelegate(hello_func)
            testob.CallStringDelegate(d)
            testob.stringDelegate = d
            testob.stringDelegate()
            testob.stringDelegate = None
            del testob
            del d

            # Delegate from instance method
            inst = HelloClass()
            testob = DelegateTest()
            d = StringDelegate(inst.hello)
            testob.CallStringDelegate(d)
            testob.stringDelegate = d
            testob.stringDelegate()
            testob.stringDelegate = None
            del testob
            del inst
            del d

            # Delegate from static method
            testob = DelegateTest()
            d = StringDelegate(HelloClass.s_hello)
            testob.CallStringDelegate(d)
            testob.stringDelegate = d
            testob.stringDelegate()
            testob.stringDelegate = None
            del testob
            del d

            # Delegate from class method
            testob = DelegateTest()
            d = StringDelegate(HelloClass.c_hello)
            testob.CallStringDelegate(d)
            testob.stringDelegate = d
            testob.stringDelegate()
            testob.stringDelegate = None
            del testob
            del d

            # Delegate from callable object
            inst = HelloClass()
            testob = DelegateTest()
            d = StringDelegate(inst)
            testob.CallStringDelegate(d)
            testob.stringDelegate = d
            testob.stringDelegate()
            testob.stringDelegate = None
            del testob
            del inst
            del d

            # Delegate from managed instance method
            testob = DelegateTest()
            d = StringDelegate(testob.SayHello)
            testob.CallStringDelegate(d)
            testob.stringDelegate = d
            testob.stringDelegate()
            testob.stringDelegate = None
            del testob
            del d

            # Delegate from managed static method
            testob = DelegateTest()
            d = StringDelegate(DelegateTest.StaticSayHello)
            testob.CallStringDelegate(d)
            testob.stringDelegate = d
            testob.stringDelegate()
            testob.stringDelegate = None
            del testob
            del d

            # Nested delegates
            testob = DelegateTest()
            d1 = StringDelegate(hello_func)
            d2 = StringDelegate(d1)
            testob.CallStringDelegate(d2)
            testob.stringDelegate = d2
            testob.stringDelegate()
            testob.stringDelegate = None
            del testob
            del d1
            del d2

            # Multicast delegates
            testob = DelegateTest()
            d1 = StringDelegate(hello_func)
            d2 = StringDelegate(hello_func)
            md = System.Delegate.Combine(d1, d2)
            testob.CallStringDelegate(md)
            testob.stringDelegate = md
            testob.stringDelegate()
            testob.stringDelegate = None
            del testob
            del d1
            del d2
            del md

        self.end_test()


if __name__ == '__main__':
    test = LeakTest()
    test.run()
    test.report()
