# -*- coding: utf-8 -*-
#
# This file is part of EventGhost.
# Copyright © 2005-2016 EventGhost Project <http://www.eventghost.net/>
#
# EventGhost is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 2 of the License, or (at your option)
# any later version.
#
# EventGhost is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with EventGhost. If not, see <http://www.gnu.org/licenses/>.

import inspect
import os
import sys
import threading
import time
import wx
from CommonMark import commonmark
from ctypes import c_ulonglong, windll
from da
tetime import datetime as dt, timedelta as td
from docutils.core import publish_parts as ReSTPublishParts
from docutils.writers.html4css1 import Writer
from functools import update_wrapper
from os.path import abspath, dirname, exists, join
from types import ClassType

# Loca
l imports
import eg

__all__ = [
    "Bunch", "NotificationHandler", "LogIt", "LogItWithReturn", "TimeIt",
    "AssertInMainThread", "AssertInActionThread", "ParseString", "SetDefault",
    "EnsureVisible", "VBoxSizer", "HBoxSizer", "EqualizeWidths", "AsTasklet",
    "ExecFile", "GetTopLevelWindow",
]

USER_CLASSES = (type, ClassType)

class Bunch(object):
    """
    Universal collection of a bunch of named stuff.

    Often we want to just collect a bunch of stuff together, naming each
    item of the bunch. A dictionary is OK for that; however, when names are
    constants and to be used just like variables, the dictionary-access syntax
    ("if bunch['squared'] > threshold", etc) is not maximally clear. It takes
    very little effort to build a little class, as in this 'Bunch', that will
    both ease the initialisation task and provide elegant attribute-access
    syntax ("if bunch.squared > threshold", etc).

    Usage is simple::

        point = eg.Bunch(x=100, y=200)

        # and of course you can read/write the named
        # attributes you just created, add others, del
        # some of them, etc, etc:
        point.squared = point.x * point.y
        if point.squared > threshold:
            point.isok = True
    """
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)


class HBoxSizer(wx.BoxSizer):  #IGNORE:R0904
    def __init__(self, *items):
        wx.BoxSizer.__init__(self, wx.HORIZONTAL)
        self.AddMany(items)


class MyHtmlDocWriter(Writer):
    def apply_template(self):
        return """\
%(head_prefix)s
%(head)s
%(stylesheet)s
%(body_prefix)s
%(body_pre_docinfo)s
%(docinfo)s
%(body)s
%(body_suffix)s
""" % self.interpolation_dict()

HTML_DOC_WRITER = MyHtmlDocWriter()


class NotificationHandler(object):
    __slots__ = ["listeners"]

    def __init__(self):
        self.listeners = []


class VBoxSizer(wx.BoxSizer):  #IGNORE:R0904
    def __init__(self, *items):
        wx.BoxSizer.__init__(self, wx.VERTICAL)
        self.AddMany(items)


def AppUrl(description, url):
    if url:
        txt = '<p><div align=right><i><font color="#999999" size=-1>%s <a href="%s">%s</a>.</font></i></div></p>' % (
            eg.text.General.supportSentence,
            url,
            eg.text.General.supportLink
        )
    else:
        return description
    if description.startswith("<md>"):
        description = description[4:]
        description = DecodeMarkdown(description)
    elif description.startswith("<rst>"):
        description = description[5:]
        description = DecodeReST(description)
    return description + txt

def AssertInActionThread(func):
    if not eg.debugLevel:
        return func

    def AssertWrapper(*args, **kwargs):
        if eg.actionThread._ThreadWorker__thread != threading.currentThread():
            raise AssertionError(
                "Called outside ActionThread: %s() in %s" %
                (func.__name__, func.__module__)
            )
        return func(*args, **kwargs)
        return func(*args, **kwargs)

    return update_wrapper(AssertWrapper, func)

def AssertInMainThread(func):
    if not eg.debugLevel:
        return func

    def AssertWrapper(*args, **kwargs):
        if eg.mainThread != threading.currentThread():
            raise AssertionError(
                "Called outside MainThread: %s in %s" %
                (func.__name__, func.__module__)
            )
        return func(*args, **kwargs)

    return update_wrapper(AssertWrapper, func)

def AsTasklet(func):
    def Wrapper(*args, **kwargs):
        eg.Tasklet(func)(*args, **kwargs).run()
    return update_wrapper(Wrapper, func)

def CollectGarbage():
    import gc
    #gc.set_debug(gc.DEBUG_SAVEALL)
    #gc.set_debug(gc.DEBUG_UNCOLLECTABLE)
    from pprint import pprint
    print "threshold:", gc.get_threshold()
    print "unreachable object count:", gc.collect()
    garbageList = gc.garbage[:]
    for i, obj in enumerate(garbageList):
        print "Object Num %d:" % i
        pprint(obj)
        #print "Referrers:"
        #print(gc.get_referrers(o))
        #print "Referents:"
        #print(gc.get_referents(o))
    print "Done."
    #print "unreachable object count:", gc.collect()
    #from pprint import pprint
    #pprint(gc.garbage)

def DecodeMarkdown(source):
    return commonmark(source)

def DecodeReST(source):
    #print repr(source)
    res = ReSTPublishParts(
        source=PrepareDocstring(source),
        writer=HTML_DOC_WRITER,
        settings_overrides={"stylesheet_path": ""}
    )
    #print repr(res)
    return res['body']

def EnsureVisible(window):
    """
    Ensures the given wx.TopLevelWindow is visible on the screen.
    Moves and resizes it if necessary.
    """
    from eg.WinApi.Dynamic import (
        sizeof, byref, GetMonitorInfo, MonitorFromWindow, GetWindowRect,
        MONITORINFO, RECT, MONITOR_DEFAULTTONEAREST,
        # MonitorFromRect, MONITOR_DEFAULTTONULL,
    )

    hwnd = window.GetHandle()
    windowRect = RECT()
    GetWindowRect(hwnd, byref(windowRect))

    #hMonitor = MonitorFromRect(byref(windowRect), MONITOR_DEFAULTTONULL)
    #if hMonitor:
    #    return

    parent = window.GetParent()
    if parent:
        hwnd = parent.GetHandle()
    hMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST)

    monInfo = MONITORINFO()
    monInfo.cbSize = sizeof(MONITORINFO)
    GetMonitorInfo(hMonitor, byref(monInfo))
    displayRect = monInfo.rcWork

    left = windowRect.left
    right = windowRect.right
    top = windowRect.top
    bottom = windowRect.bottom

    # shift the window horizontally into the display area
    if left < displayRect.left:
        right += (displayRect.left - left)
        left = displayRect.left
        if right > displayRect.right:
            right = displayRect.right
    elif right > displayRect.right:
        left += (displayRect.right - right)
        right = displayRect.right
        if left < displayRect.left:
            left = displayRect.left

    # shift the window vertically into the display area
    if top < displayRect.top:
        bottom += (displayRect.top - top)
        top = displayRect.top
        if bottom > displayRect.bottom:
            bottom = displayRect.bottom
    elif bottom > displayRect.bottom:
        top += (displayRect.bottom - bottom)
        bottom = displayRect.bottom
        if top < displayRect.top:
            top = displayRect.top

    # set the new position and size
    window.SetRect((left, top, right - left, bottom - top))

def EqualizeWidths(ctrls):
    maxWidth = max((ctrl.GetBestSize()[0] for ctrl in ctrls))
    for ctrl in ctrls:
        ctrl.SetMinSize((maxWidth, -1))

def ExecFile(filename, globals=None, locals=None):
    """
    Replacement for the Python built-in execfile() function, but handles
    unicode filenames right.
    """
    FSE = sys.getfilesystemencoding()
    flnm = filename.encode(FSE) if isinstance(filename, unicode) else filename
    return execfile(flnm, globals