# -*- coding: utf-8 -*-

# Copyright(C) 2010-2011 Romain Bignon
#
# This file is part of weboob.
#
# weboob is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# weboob 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with weboob. If not, see <http://www.gnu.org/licenses/>.

import sys
import logging
import re
from threading import Event
from copy import copy
from PyQt4.QtCore import QTimer, SIGNAL, QObject, QString, QSize, QVariant, QMutex
from PyQt4.QtGui import QMainWindow, QApplication, QStyledItemDelegate, \
                        QStyleOptionViewItemV4, QTextDocument, QStyle, \
                        QAbstractTextDocumentLayout, QPalette, QMessageBox, \
                        QSpinBox, QLineEdit, QComboBox, QCheckBox, QInputDialog, \
                        QLineEdit

from weboob.core.ouiboube import Weboob
from weboob.core.scheduler import IScheduler
from weboob.tools.browser import BrowserUnavailable, BrowserIncorrectPassword
from weboob.tools.value import ValueInt, ValueBool, ValueBackendPassword

from ..base import BaseApplication

__all__ = ['QtApplication', 'QtMainWindow', 'QtDo', 'HTMLDelegate']

class QtScheduler(IScheduler):
    def __init__(self, app):
        self.app = app
        self.count = 0
        self.timers = {}

    def schedule(self, interval, function, *args):
        timer = QTimer()
        timer.setInterval(interval * 1000)
        timer.setSingleShot(True)

        count = self.count
        self.count += 1

        timer.start()
        self.app.connect(timer, SIGNAL("timeout()"), lambda: self.timeout(count, None, function, *args))
        self.timers[count] = timer

    def repeat(self, interval, function, *args):
        timer = QTimer()
        timer.setSingleShot(False)

        count = self.count
        self.count += 1

        timer.start(0)
        self.app.connect(timer, SIGNAL("timeout()"), lambda: self.timeout(count, interval, function, *args))
        self.timers[count] = timer

    def timeout(self, _id, interval, function, *args):
        function(*args)
        if interval is None:
            self.timers.pop(_id)
        else:
            self.timers[_id].setInterval(interval * 1000)

    def want_stop(self):
        self.app.quit()

    def run(self):
        self.app.exec_()

class QCallbacksManager(QObject):
    class Request(object):
        def __init__(self):
            self.event = Event()
            self.answer = None

        def __call__(self):
            raise NotImplementedError()

    class LoginRequest(Request):
        def __init__(self, backend_name, value):
            QCallbacksManager.Request.__init__(self)
            self.backend_name = backend_name
            self.value = value

        def __call__(self):
            password, ok = QInputDialog.getText(None,
                                                'Password request',
                                                'Please enter password for %s' % self.backend_name,
                                                QLineEdit.Password)
            return password

    def __init__(self, weboob, parent=None):
        QObject.__init__(self, parent)
        self.weboob = weboob
        self.weboob.callbacks['login'] = self.callback(self.LoginRequest)
        self.mutex = QMutex()
        self.requests = []
        self.connect(self, SIGNAL('new_request'), self.do_request)

    def callback(self, klass):
        def cb(*args, **kwargs):
            return self.add_request(klass(*args, **kwargs))
        return cb

    def do_request(self):
        self.mutex.lock()
        request = self.requests.pop()
        request.answer = request()
        request.event.set()
        self.mutex.unlock()

    def add_request(self, request):
        self.mutex.lock()
        self.requests.append(request)
        self.mutex.unlock()
        self.emit(SIGNAL('new_request'))
        request.event.wait()
        return request.answer

class QtApplication(QApplication, BaseApplication):
    def __init__(self):
        QApplication.__init__(self, sys.argv)
        self.setApplicationName(self.APPNAME)

        BaseApplication.__init__(self)
        self.cbmanager = QCallbacksManager(self.weboob, self)

    def create_weboob(self):
        return Weboob(scheduler=QtScheduler(self))

class QtMainWindow(QMainWindow):
    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)

class QtDo(QObject):
    def __init__(self, weboob, cb, eb=None):
        QObject.__init__(self)

        if not eb:
            eb = self.default_eb

        self.weboob = weboob
        self.process = None
        self.cb = cb
        self.eb = eb

        self.connect(self, SIGNAL('cb'), self.local_cb)
        self.connect(self, SIGNAL('eb'), self.local_eb)

    def do(self, *args, **kwargs):
        self.process = self.weboob.do(*args, **kwargs)
        self.process.callback_thread(self.thread_cb, self.thread_eb)

    def default_eb(self, backend, error, backtrace):
        msg = unicode(error)
        if isinstance(error, BrowserIncorrectPassword):
            if not msg:
                msg = 'Invalid login/password.'
        elif isinstance(error, BrowserUnavailable):
            if not msg:
                msg = 'website is unavailable.'
        elif isinstance(error, NotImplementedError):
            msg = 'This feature is not supported by this backend.\n\n' \
                  'To help the maintainer of this backend implement this feature, please contact: %s <%s>' % (backend.MAINTAINER, backend.EMAIL)
        elif logging.root.level == logging.DEBUG:
            msg += '<br />'
            ul_opened = False
            for line in backtrace.split('\n'):
                m = re.match('  File (.*)', line)
                if m:
                    if not ul_opened:
                        msg += '<ul>'
                        ul_opened = True
                    else:
                        msg += '</li>'
                    msg += '<li><b>%s</b>' % m.group(1)
                else:
                    msg += '<br />%s' % line
            if ul_opened:
                msg += '</li></ul>'
            print >>sys.stderr, error
            print >>sys.stderr, backtrace
        QMessageBox.critical(None, unicode(self.tr('Error with backend %s')) % backend.name,
                             msg, QMessageBox.Ok)

    def local_cb(self, backend, data):
        self.cb(backend, data)
        if not backend:
            self.disconnect(self, SIGNAL('cb'), self.local_cb)
            self.disconnect(self, SIGNAL('eb'), self.local_eb)

    def local_eb(self, backend, error, backtrace):
        self.eb(backend, error, backtrace)
        self.disconnect(self, SIGNAL('cb'), self.local_cb)
        self.disconnect(self, SIGNAL('eb'), self.local_eb)

    def thread_cb(self, backend, data):
        self.emit(SIGNAL('cb'), backend, data)

    def thread_eb(self, backend, error, backtrace):
        self.emit(SIGNAL('eb'), backend, error, backtrace)

class HTMLDelegate(QStyledItemDelegate):
    def paint(self, painter, option, index):
        optionV4 = QStyleOptionViewItemV4(option)
        self.initStyleOption(optionV4, index)

        style = optionV4.widget.style() if optionV4.widget else QApplication.style()

        doc = QTextDocument()
        doc.setHtml(optionV4.text)

        # painting item without text
        optionV4.text = QString()
        style.drawControl(QStyle.CE_ItemViewItem, optionV4, painter)

        ctx = QAbstractTextDocumentLayout.PaintContext()

        # Hilight text if item is selected
        if optionV4.state & QStyle.State_Selected:
            ctx.palette.setColor(QPalette.Text, optionV4.palette.color(QPalette.Active, QPalette.HighlightedText))

        textRect = style.subElementRect(QStyle.SE_ItemViewItemText, optionV4)
        painter.save()
        painter.translate(textRect.topLeft())
        painter.setClipRect(textRect.translated(-textRect.topLeft()))
        doc.documentLayout().draw(painter, ctx)
        painter.restore()

    def sizeHint(self, option, index):
        optionV4 = QStyleOptionViewItemV4(option)
        self.initStyleOption(optionV4, index)

        doc = QTextDocument()
        doc.setHtml(optionV4.text)
        doc.setTextWidth(optionV4.rect.width())

        return QSize(doc.idealWidth(), max(doc.size().height(), optionV4.decorationSize.height()))

class _QtValueStr(QLineEdit):
    def __init__(self, value):
        QLineEdit.__init__(self)
        self._value = value
        if value.default:
            self.setText(unicode(value.default))
        if value.masked:
            self.setEchoMode(self.Password)

    def set_value(self, value):
        self._value = value
        self.setText(self._value.get())

    def get_value(self):
        self._value.set(unicode(self.text()))
        return self._value

class _QtValueBackendPassword(_QtValueStr):
    def get_value(self):
        self._value._domain = None
        return _QtValueStr.get_value(self)

class _QtValueBool(QCheckBox):
    def __init__(self, value):
        QCheckBox.__init__(self)
        self._value = value
        if value.default:
            self.setChecked(True)

    def set_value(self, value):
        self._value = value
        self.setChecked(self._value.get())

    def get_value(self):
        self._value.set(self.isChecked())
        return self._value

class _QtValueInt(QSpinBox):
    def __init__(self, value):
        QSpinBox.__init__(self)
        self._value = value
        if value.default:
            self.setValue(int(value.default))

    def set_value(self, value):
        self._value = value
        self.setValue(self._value.get())

    def get_value(self):
        self._value.set(self.getValue())
        return self._value

class _QtValueChoices(QComboBox):
    def __init__(self, value):
        QComboBox.__init__(self)
        self._value = value
        for k, l in value.choices.iteritems():
            self.addItem(l, QVariant(k))
            if value.default == k:
                self.setCurrentIndex(self.count()-1)

    def set_value(self, value):
        self._value = value
        for i in xrange(self.count()):
            if unicode(self.itemData(i).toString()) == self._value.get():
                self.setCurrentIndex(i)
                return

    def get_value(self):
        self._value.set(unicode(self.itemData(self.currentIndex()).toString()))
        return self._value

def QtValue(value):
    if isinstance(value, ValueBool):
        klass = _QtValueBool
    elif isinstance(value, ValueInt):
        klass = _QtValueInt
    elif isinstance(value, ValueBackendPassword):
        klass = _QtValueBackendPassword
    elif value.choices is not None:
        klass = _QtValueChoices
    else:
        klass = _QtValueStr

    return klass(copy(value))
