import os
import time
import inspect
import logging
import logging.config

from PyQt5 import QtCore, QtGui, QtWidgets

import ostools

_datadir = ostools.getDataDir()
logging.config.fileConfig(_datadir + "logging.ini")
PchumLog = logging.getLogger('pchumLogger')

#try:
#    import pynotify
#except:
#    pynotify = None

# Pynotify is broken.
pynotify = None

class DefaultToast(object):
    def __init__(self, machine, title, msg, icon):
        self.machine = machine
        self.title   = title
        self.msg     = msg
        self.icon    = icon
    def show(self):
        print(self.title, self.msg, self.icon)
        self.done()
    def done(self):
        t = self.machine.toasts[0]
        if t.title == self.title and t.msg == self.msg and t.icon == self.icon:
            self.machine.toasts.pop(0)
            self.machine.displaying = False
            PchumLog.info("Done")

class ToastMachine(object):
    class __Toast__(object):
        def __init__(self, machine, title, msg, time=3000, icon="", importance=0):
            self.machine    = machine
            self.title      = title
            self.msg        = msg
            self.time       = time
            if icon:
                icon = os.path.abspath(icon)
            self.icon       = icon
            self.importance = importance
            if inspect.ismethod(self.title) or inspect.isfunction(self.title):
                self.title = self.title()

        def titleM(self, title=None):
            if title:
                self.title = title
                if inspect.ismethod(self.title) or inspect.isfunction(self.title):
                    self.title = self.title()
            else:     return self.title
        def msgM(self, msg=None):
            if msg:   self.msg = msg
            else:     return self.msg
        def timeM(self, time=None):
            if time:  self.time = time
            else:     return self.time
        def iconM(self, icon=None):
            if icon:  self.icon = icon
            else:     return self.icon
        def importanceM(self, importance=None):
            if importance != None: self.importance = importance
            else:                  return self.importance

        def show(self):
            if self.machine.on:
                # Use libnotify's queue if using libnotify
                if self.machine.type == "libnotify" or self.machine.type == "twmn":
                    self.realShow()
                elif self.machine.toasts:
                    self.machine.toasts.append(self)
                else:
                    self.machine.toasts.append(self)
                    self.realShow()

        def realShow(self):
            self.machine.displaying = True
            t = None
            for (k,v) in self.machine.types.items():
                if self.machine.type == k:
                    try:
                        args = inspect.getargspec(v.__init__).args
                    except:
                        args = []

                    extras = {}
                    if 'parent' in args:
                        extras['parent'] = self.machine.parent
                    if 'time' in args:
                        extras['time'] = self.time
                    if k == "libnotify" or k == "twmn":
                        t = v(self.title, self.msg, self.icon, **extras)
                    else:
                        t = v(self.machine, self.title, self.msg, self.icon, **extras)
                    # Use libnotify's urgency setting
                    if k == "libnotify":
                        if self.importance < 0:
                            t.set_urgency(pynotify.URGENCY_CRITICAL)
                        elif self.importance == 0:
                            t.set_urgency(pynotify.URGENCY_NORMAL)
                        elif self.importance > 0:
                            t.set_urgency(pynotify.URGENCY_LOW)
                    break
            if not t:
                if 'default' in self.machine.types:
                    if 'parent' in inspect.getargspec(self.machine.types['default'].__init__).args:
                        t = self.machine.types['default'](self.machine, self.title, self.msg, self.icon, self.machine.parent)
                    else:
                        t = self.machine.types['default'](self.machine, self.title, self.msg, self.icon)
                else:
                    t = DefaultToast(self.title, self.msg, self.icon)
            t.show()

    def __init__(self, parent, name, on=True, type="default",
                                              types=({'default'  : DefaultToast,
                                                      'libnotify': pynotify.Notification}
                                                      if pynotify else
                                                      {'default' : DefaultToast}),
                                              extras={}):
        self.parent     = parent
        self.name       = name
        self.on         = on
        types.update(extras)
        self.types      = types
        self.type       = "default"
        self.quit       = False
        self.displaying = False

        self.setCurrentType(type)

        self.toasts = []

    def Toast(self, title, msg, icon="", time=3000):
        return self.__Toast__(self, title, msg, time=time, icon=icon)

    def setEnabled(self, on):
        self.on = (on is True)

    def currentType(self):
        return self.type

    def availableTypes(self):
        return sorted(self.types.keys())

    def setCurrentType(self, type):
        if type in self.types:
            if type == "libnotify":
                if not pynotify or not pynotify.init("ToastMachine"):
                    PchumLog.info("Problem initilizing pynotify")
                    return
                    #self.type = type = "default"
            elif type == "twmn":
                import pytwmn
                try:
                    pytwmn.init()
                except pytwmn.ERROR as e:
                    PchumLog.error("Problem initilizing pytwmn: " + str(e))
                    return
                    #self.type = type = "default"
            self.type = type

    def appName(self):
        if inspect.ismethod(self.name) or inspect.isfunction(self.name):
            return self.name()
        else:
            return self.name

    def showNext(self):
        if not self.displaying and self.toasts:
            self.toasts.sort(key=lambda x: x.importance)
            self.toasts[0].realShow()

    def showAll(self):
        while self.toasts:
            self.showNext()

    def run(self):
        while not self.quit:
            if self.on and self.toasts:
                self.showNext()


class PesterToast(QtWidgets.QWidget, DefaultToast):
    def __init__(self, machine, title, msg, icon, time=3000, parent=None):
        PchumLog.info(isinstance(parent, QtWidgets.QWidget))
        kwds = dict(machine=machine, title=title, msg=msg, icon=icon)
        super().__init__(parent, **kwds)

        self.machine = machine
        self.time = time

        if ostools.isWin32():
            self.setWindowFlags(QtCore.Qt.ToolTip)
        else:
            self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint | QtCore.Qt.X11BypassWindowManagerHint | QtCore.Qt.ToolTip)

        self.m_animation = QtCore.QParallelAnimationGroup()
        anim = QtCore.QPropertyAnimation(self)
        anim.setTargetObject(self)
        self.m_animation.addAnimation(anim)
        anim.setEasingCurve(QtCore.QEasingCurve.OutBounce)
        anim.setDuration(1000)
        anim.finished.connect(self.reverseTrigger)

        self.m_animation.setDirection(QtCore.QAnimationGroup.Forward)

        self.title = QtWidgets.QLabel(title, self)
        self.msg = QtWidgets.QLabel(msg, self)
        self.content = msg
        if icon:
            self.icon = QtWidgets.QLabel("")
            self.icon.setPixmap(QtGui.QPixmap(icon).scaledToWidth(30))
        else:
            self.icon = QtWidgets.QLabel("")
            self.icon.setPixmap(QtGui.QPixmap(30, 30))
            self.icon.pixmap().fill(QtGui.QColor(0,0,0,0))

        layout_0 = QtWidgets.QVBoxLayout()
        layout_0.setContentsMargins(0, 0, 0, 0)
        layout_0.setContentsMargins(0, 0, 0, 0)

        if self.icon:
            layout_1 = QtWidgets.QGridLayout()
            layout_1.addWidget(self.icon, 0,0, 1,1)
            layout_1.addWidget(self.title, 0,1, 1,7)
            layout_1.setAlignment(self.msg, QtCore.Qt.AlignTop)
            layout_0.addLayout(layout_1)
        else:
            layout_0.addWidget(self.title)
        layout_0.addWidget(self.msg)

        self.setMaximumWidth(self.parent().theme["toasts/width"])
        self.msg.setMaximumWidth(self.parent().theme["toasts/width"])
        self.title.setMinimumHeight(self.parent().theme["toasts/title/minimumheight"])

        self.setLayout(layout_0)

        self.setGeometry(0,0, self.parent().theme["toasts/width"], self.parent().theme["toasts/height"])
        self.setStyleSheet(self.parent().theme["toasts/style"])
        self.title.setStyleSheet(self.parent().theme["toasts/title/style"])
        if self.icon:
            self.icon.setStyleSheet(self.parent().theme["toasts/icon/style"])
        self.msg.setStyleSheet(self.parent().theme["toasts/content/style"])
        self.layout().setSpacing(0)

        self.msg.setText(PesterToast.wrapText(self.msg.font(), str(self.msg.text()), self.parent().theme["toasts/width"], self.parent().theme["toasts/content/style"]))

        p = QtWidgets.QApplication.desktop().availableGeometry(self).bottomRight()
        o = QtWidgets.QApplication.desktop().screenGeometry(self).bottomRight()
        anim.setStartValue(p.y() - o.y())
        anim.setEndValue(100)
        anim.valueChanged[QtCore.QVariant].connect(self.updateBottomLeftAnimation)

        self.byebye = False

    @QtCore.pyqtSlot()
    def show(self):
        self.m_animation.start()

    @QtCore.pyqtSlot()
    def done(self):
        QtWidgets.QWidget.hide(self)
        t = self.machine.toasts[0]
        if t.title == str(self.title.text()) and \
           t.msg == str(self.content):
            self.machine.toasts.pop(0)
            self.machine.displaying = False
        if self.machine.on:
            self.machine.showNext()
        del self

    @QtCore.pyqtSlot()
    def reverseTrigger(self):
        if self.time >= 0:
            QtCore.QTimer.singleShot(self.time, self.reverseStart)

    @QtCore.pyqtSlot()
    def reverseStart(self):
        if not self.byebye:
            self.byebye = True
            anim = self.m_animation.animationAt(0)
            self.m_animation.setDirection(QtCore.QAnimationGroup.Backward)
            anim.setEasingCurve(QtCore.QEasingCurve.InCubic)
            anim.finished.disconnect(self.reverseTrigger)
            anim.finished.connect(self.done)
            self.m_animation.start()

    @QtCore.pyqtSlot(QtCore.QVariant)
    def updateBottomLeftAnimation(self, value):
        p = QtWidgets.QApplication.desktop().availableGeometry(self).bottomRight()
        val = (self.height())/100
        # Does type casting this to an int have any negative consequences?
        self.move(int(p.x()-self.width()), int(p.y() - (value * val) +1))
        self.layout().setSpacing(0)
        QtWidgets.QWidget.show(self)

    def mousePressEvent(self, event):
        if event.button() == QtCore.Qt.RightButton:
            self.reverseStart()
        elif event.button() == QtCore.Qt.LeftButton:
            pass

    @staticmethod
    def wrapText(font, text, maxwidth, css=""):
        ret = []
        metric = QtGui.QFontMetrics(font)
        if "padding" in css:
            if css[css.find("padding")+7] != "-":
                colon = css.find(":", css.find("padding"))
                semicolon = css.find(";", css.find("padding"))
                if semicolon < 0:
                    stuff = css[colon+1:]
                else:
                    stuff = css[colon+1:semicolon]
                stuff = stuff.replace("px", "").lstrip().rstrip()
                stuff = stuff.split(" ")
                if len(stuff) == 1:
                    maxwidth -= int(stuff[0])*2
                elif len(stuff) == 2:
                    maxwidth -= int(stuff[1])*2
                elif len(stuff) == 3:
                    maxwidth -= int(stuff[1])*2
                elif len(stuff) == 4:
                    maxwidth -= int(stuff[1]) + int(stuff[3])
            else:
                if "padding-left" in css:
                    colon = css.find(":", css.find("padding-left"))
                    semicolon = css.find(";", css.find("padding-left"))
                    if semicolon < 0:
                        stuff = css[colon+1:]
                    else:
                        stuff = css[colon+1:semicolon]
                    stuff = stuff.replace("px", "").lstrip().rstrip()
                    if stuff.isdigit():
                        maxwidth -= int(stuff)
                if "padding-right" in css:
                    colon = css.find(":", css.find("padding-right"))
                    semicolon = css.find(";", css.find("padding-right"))
                    if semicolon < 0:
                        stuff = css[colon+1:]
                    else:
                        stuff = css[colon+1:semicolon]
                    stuff = stuff.replace("px", "").lstrip().rstrip()
                    if stuff.isdigit():
                        maxwidth -= int(stuff)

        if metric.width(text) < maxwidth:
            return text
        while metric.width(text) > maxwidth:
            lastspace = text.find(" ")
            curspace = lastspace
            while metric.width(text, curspace) < maxwidth:
                lastspace = curspace
                curspace = text.find(" ", lastspace+1)
                if curspace == -1:
                    break
            if (metric.width(text[:lastspace]) > maxwidth) or \
               len(text[:lastspace]) < 1:
                for i in range(len(text)):
                    if metric.width(text[:i]) > maxwidth:
                        lastspace = i-1
                        break
            ret.append(text[:lastspace])
            text = text[lastspace+1:]
        ret.append(text)
        return "\n".join(ret)


class PesterToastMachine(ToastMachine, QtCore.QObject):
    def __init__(self, parent, name, on=True, type="default",
                 types=({'default'  : DefaultToast,
                        'libnotify' : pynotify.Notification}
                        if pynotify else
                        {'default' : DefaultToast}),
                 extras={}):
        ToastMachine.__init__(self, parent, name, on, type, types, extras)
        QtCore.QObject.__init__(self, parent)

    def setEnabled(self, on):
        oldon = self.on
        ToastMachine.setEnabled(self, on)
        if oldon != self.on:
            self.parent.config.set('notify', self.on)
            if self.on:
                self.timer.start()
            else:
                self.timer.stop()

    def setCurrentType(self, type):
        oldtype = self.type
        ToastMachine.setCurrentType(self, type)
        if oldtype != self.type:
            self.parent.config.set('notifyType', self.type)

    @QtCore.pyqtSlot()
    def showNext(self):
        ToastMachine.showNext(self)

    def run(self):
        pass
        #~ self.timer = QtCore.QTimer(self)
        #~ self.timer.setInterval(1000)
        #~ self.connect(self.timer, QtCore.SIGNAL('timeout()'),
                     #~ self, QtCore.SLOT('showNext()'))
        #~ if self.on:
            #~ self.timer.start()
