#!/usr/bin/env python
import sys
#begin dependent modules
#sys.path.insert(0, "../")
import yobot_interfaces
import yobotproto
from client_support import YCAccount, SimpleNotice
from gui import gui_util
from gui.gui_util import signal_connect, ConnectionWidget
#end
import triviadb
import PyQt4
from PyQt4.QtGui import (QComboBox, QMainWindow, QStandardItemModel, QStandardItem,
                         QIcon, QPixmap, QImage, QPainter, QDialog, QMessageBox,
                         QApplication, QFont, QTextEdit, QColorDialog, QPalette,
                         QListWidget, QListWidgetItem, QStyledItemDelegate,
                         QStyleOptionViewItem, QRegion, QWidget, QBrush, QStyle,
                         QPushButton, QStyleOption, QMenu, QAction, QCursor,
                        QLineEdit, QFileDialog, QErrorMessage,
                         QFontDialog, QColor, QDockWidget, QSizePolicy,
                         QStackedWidget,
                         QGridLayout, QLayout, QFrame,
                         )
from PyQt4.QtCore import (QPoint, QSize, QModelIndex, Qt, QObject, SIGNAL, QVariant,
                          QAbstractItemModel, QRect, QRectF, QPointF, QT_VERSION)
from debuglog import log_debug, log_info, log_err, log_crit, log_warn
import sqlite3.dbapi2 as sqlite3
import pickle
import lxml.html
import re
from time import time
from collections import defaultdict
import random
from gui.html_fmt import point_to_html
import os.path
import yobotops
from cgi import escape as html_escape
import datetime
import gui_new
#trivia types
TYPE_ANAGRAMS, TYPE_TRIVIA, TYPE_BOTH = range(1, 4)
TRIVIA_ROOT = "/home/mordy/src/purple/py/triviabot"
article_start_re = re.compile("^(the|a) ")
def get_categories_list(dbname):
    dbconn = sqlite3.connect(dbname)
    ret = []
    for r in dbconn.cursor().execute("select distinct category from questions"):
        ret.append(r[0])
    return ret
def scramble_word(word):
    return "".join(random.sample(word, len(word)))
class _BlackHole(str):
    def __init__(self, *args, **kwargs):
        pass
    def __getattr__(self, name):
        return _BlackHole()
    def __setattr__(self, name, value):
        pass
    def __call__(self, *args, **kwargs):
        pass
    def __bool__(self):
        return False
    def __str__(self):
        return ""
class TriviaGui(gui_new.TGui):
    def __init__(self, parent=None):
        gui_new.TGui.__init__(self, parent)
        self.widgets = self
        w = self.widgets
        self.model = yobot_interfaces.component_registry.get_component("account-model")
        self.client_ops = yobot_interfaces.component_registry.get_component("client-operations")
        assert self.client_ops
        
        def _handle_connect(username, password, improto, **proxy_params):
            self.client_ops.connect(username, password, improto, **proxy_params)
        
        def offset_pos_fn():
            return QPoint(0, self.menubar.height())
        self.connwidget = gui_util.OverlayConnectionWidget(offset_pos_fn, _handle_connect, self)
        signal_connect(w.actionConnect, SIGNAL("toggled(bool)"), self.connwidget.setVisible)
        signal_connect(self.connwidget.widgets.conn_close, SIGNAL("clicked()"),
                       lambda: w.actionConnect.setChecked(False))
        if not self.model:
            w.actionConnect.setChecked(True)
            self.model = gui_util.AccountModel(None)
            self.menubar.show()
        else:
            self.connwidget.hide()
        #notification widgets:
        qdw = QFrame(self)
        if QT_VERSION >= 0x040600:
            from PyQt4.QtGui import QGraphicsDropShadowEffect
            self.notification_shadow = QGraphicsDropShadowEffect(qdw)
            self.notification_shadow.setBlurRadius(10.0)
            qdw.setGraphicsEffect(self.notification_shadow)
        qdw.setFrameShadow(qdw.Raised)
        qdw.setFrameShape(qdw.StyledPanel)
        qdw.setAutoFillBackground(True)
        qsw = QStackedWidget(qdw)
        qdw.setLayout(QGridLayout())
        qdw.layout().setSizeConstraint(QLayout.SetMinimumSize)
        qdw.layout().addWidget(qsw)
        self._notification_dlg = qdw
        self.qdw = qdw
        self.qsw = qsw
        self.notifications = gui_util.NotificationBox(qdw, qsw, noTitleBar=False)
        #self.qdw.show()
        
        #resize/show events
        def _force_bottom(event, superclass_fn):
            log_err("")
            superclass_fn(self.qdw, event)
            self.qdw.move(0, self.height()-self.qdw.height())
        self.qdw.resizeEvent = lambda e: _force_bottom(e, QWidget.resizeEvent)
        self.qdw.showEvent = lambda e: _force_bottom(e, QWidget.showEvent)
        
        #set up account menu
        w.account.setModel(self.model)
        
        for a in ("start", "stop", "pause", "next"):
            gui_util.signal_connect(getattr(w, a), SIGNAL("clicked()"),
                                    lambda cls=self, a=a: getattr(cls, a + "_requested")())
            getattr(w, a).setEnabled(False)
        w.start.setEnabled(True)
                
        self.anagrams_prefix_blacklist = set()
        self.anagrams_suffix_blacklist = set()
        
        #listWidgetItems
        def _add_nfix(typestr):
            txt = getattr(w, typestr + "_input").text()
            if not txt:
                return
            txt = str(txt)
            st = getattr(self, "anagrams_" + typestr + "_blacklist")
            target = getattr(w, typestr + "_list")
            if not txt in st:
                target.addItem(txt)
                st.add(txt)
            getattr(w, typestr + "_input").clear()
        def _remove_nfix(typestr):
            target = getattr(w, typestr + "_list")
            st = getattr(self, "anagrams_" + typestr + "_blacklist")
            item = target.currentItem()
            if item:
                txt = str(item.text())
                assert txt in st
                target.takeItem(target.row(item))
                st.remove(txt)
            else:
                log_warn("item is None")
        for nfix in ("suffix", "prefix"):
            signal_connect(getattr(w, nfix + "_add"), SIGNAL("clicked()"),
                lambda typestr=nfix: _add_nfix(typestr))
            signal_connect(getattr(w, nfix + "_del"), SIGNAL("clicked()"),
                lambda typestr=nfix: _remove_nfix(typestr))
            
        #hide the extended options
        w.questions_categories_params.hide()
        w.suffix_prefix_options.hide()
        
        self.resize(self.minimumSizeHint())
    
        #connect signals for enabling the start button
        signal_connect(w.account, SIGNAL("currentIndexChanged(int)"), self._enable_start)
        signal_connect(w.room, SIGNAL("activated(int)"), self._enable_start)
        signal_connect(w.room, SIGNAL("editTextchanged(QString)"), self._enable_start)
        signal_connect(w.questions_database, SIGNAL("textChanged(QString)"), self.questions_dbfile_changed)
        signal_connect(w.questions_database, SIGNAL("textChanged(QString)"), self._validate_questions_db)
        signal_connect(w.anagrams_database, SIGNAL("textChanged(QString)"), self._validate_anagrams_db)
        
        signal_connect(w.questions_database, SIGNAL("textChanged(QString)"), self._enable_start)
        signal_connect(w.anagrams_database, SIGNAL("textChanged(QString)"), self._enable_start)
        
        #category list for questions:
        self.selected_questions_categories = set()
        def _unselect(lwitem):
            row = w.selected_categories.row(lwitem)
            self.selected_questions_categories.remove(str(lwitem.text()))
            self.widgets.selected_categories.takeItem(row)
        def _select(lwitem):
            category = str(lwitem.text())
            if not category in self.selected_questions_categories:
                log_debug("Adding", category)
                self.selected_questions_categories.add(category)
                w.selected_categories.addItem(category)
        signal_connect(w.questions_categories, SIGNAL("itemDoubleClicked(QListWidgetItem*)"), _select)
        signal_connect(w.selected_categories, SIGNAL("itemDoubleClicked(QListWidgetItem*)"), _unselect)
        
        
        self.anagrams_db_is_valid = False
        self.questions_db_is_valid = False
        
        #profile stuff..
        signal_connect(w.actionLoad, SIGNAL("activated()"), lambda: self.profile_handler(load=True))
        signal_connect(w.actionSave, SIGNAL("activated()"), lambda: self.profile_handler(save=True))
        signal_connect(w.actionSave_As, SIGNAL("activated()"), lambda: self.profile_handler(save_as=True))
        self.current_profile_name = ""
                
        w.suffix_prefix_options.sizeHint = lambda: QSize(1,1)
        w.questions_categories_params.sizeHint = lambda: QSize(1,1)
                
        self.show()
            
    def _validate_anagrams_db(self, db):
        dbconn = None
        db = str(db)
        try:
            assert os.path.exists(db)
            dbconn = sqlite3.connect(db)
            cursor = dbconn.cursor()
            cursor.execute("select word from words limit 1").fetchone()[0]
            self.anagrams_db_is_valid = True
        except Exception, e:
            log_err(e)
            self.anagrams_db_is_valid = False
            QErrorMessage(self).showMessage("Anagrams database is invalid: " + str(e))
        finally:
            if dbconn:
                dbconn.close()
    def _validate_questions_db(self, db):
        dbconn = None
        db = str(db)
        try:
            assert os.path.exists(db)
            dbconn = sqlite3.connect(db)
            cursor = dbconn.cursor()
            cursor.execute("select id, frequency, question, answer, alt_answers from questions limit 1").fetchone()[0]
            self.questions_db_is_valid = True
        except Exception, e:
            log_err(e)
            self.questions_db_is_valid = False
            QErrorMessage(self).showMessage("Questions database is invalid: " + str(e))
        finally:
            if dbconn:
                dbconn.close()    
    
    def _dbs_are_valid(self):
        type = str(self.widgets.questions_type.currentText()).lower()
        if type == "mix" and not ( self.anagrams_db_is_valid and self.questions_db_is_valid):
            return False
        elif type == "anagrams" and not self.anagrams_db_is_valid:
            return False
        elif type == "trivia" and not self.questions_db_is_valid:
            return False        
        return True
        
    def _enable_start(self, *args):
        w = self.widgets
        if w.account.currentText() and w.room.currentText() and self._dbs_are_valid():
            w.start.setEnabled(True)
        else:
            w.start.setEnabled(False)        
            
    #some hooks
    def questions_dbfile_changed(self, dbname):
        self.widgets.questions_categories.clear()
        try:
            l = get_categories_list(str(dbname))
        except Exception, e:
            log_err(e)
            return
        for s in l:
            if s:
                self.widgets.questions_categories.addItem(str(s))
    @staticmethod
    def create_profile_mappings():
        #make a tuple.
        #format: (cast_fn, get_fn, set_fn)
        d = {}
        
        #integers
        for a in ("post_interval", "answer_timeout", "percent_anagrams", "percent_trivia",
                  "amount", "anagrams_letters_min", "anagrams_letters_max"):
            d[a] = ("int", "value", "setValue")
        
        #strings
        for a in ("anagrams_database", "questions_database"):
            d[a] = ("str", "text", "setText")
        
        #booleans
        for a in ("updatedb_bool", "anagrams_caps_hint", "questions_blacklist",
                  "questions_use_categories", "anagrams_use_nfixes"):
            d[a] = ("bool", "isChecked", "setChecked")
        
        #room combobox
        d["room"] = ("str", "currentText", "addItem")
        
        return d
        #for accounts, we need to do some special handling because they are
        #referenced by index
    
    def save_profile(self, profile_name):
        try:
            f = open(profile_name, "w")
            f.write("#Yobot Trivia Profile Settings automatically generated on %s\n" %
                    str(datetime.datetime.now()))
            f.write("#Configuration is case-sensitive. Use 'True' and 'False' for boolean values\n")
            f.write("#this file is parsed directly using python's eval\n")
            
            d = TriviaGui.create_profile_mappings()
            for k, v in d.items():
                #k is the attribute
                field = getattr(self.widgets, k)
                cast, getter, setter = v
                value = getattr(field, getter)() if getter else field
                
                if cast == "str":
                    value = str(value)
                #if not value and cast == "bool":
                #    value = int(value)
                if not value and cast == "str":
                    value = ""
                    
                f.write(k + "=" + repr(value) + "\n")
            
            #for account..
            acct_index = self.widgets.account.currentIndex()
            acct_index = self.model.index(acct_index)
            account = acct_index.internalPointer()
            if account:
                f.write("account_username=" + account.user + "\n")
                f.write("account_improto=" + yobotops.imprototostr(account.improto) + "\n")
            
            #for complex types
            for c in ("anagrams_suffix_blacklist", "anagrams_prefix_blacklist",
                      "selected_questions_categories"):
                log_info(getattr(self, c))
                f.write(c + "=" + repr(getattr(self, c)) + "\n")
            
            #for font and color:
            if self.font:
                f.write("font=" + self.font.toString() + "\n")
            if self.color:
                f.write("color=" + self.color.name() + "\n")
            #for type, just write the current type
            f.write("questions_type=" + self.widgets.questions_type.currentText() + "\n")
            #for the blacklists/whitelists..
            
            f.close()
            return True
        except Exception, e:
            QErrorMessage(self).showMessage(str(e))
            return False
    def load_profile(self, profile_name):
        d = TriviaGui.create_profile_mappings()
        try:
            f = open(profile_name, "r")
            for l in f.readlines():
                if l.strip()[0] in ("#", ";"):
                    continue
                k, v = [s.strip() for s in l.split("=")]
                dkey = d.get(k, None)
                if not dkey:
                    #complex handling
                    if k in ("anagrams_prefix_blacklist", "anagrams_suffix_blacklist"):
                        tmp = k.split("_")[1]
                        getattr(self, k).clear()
                        getattr(self, k).update([str(s) for s in eval(v)])
                        getattr(self.widgets, tmp + "_list").clear()
                        getattr(self.widgets, tmp + "_list").addItems(list(getattr(self, k)))
                    elif k == "selected_questions_categories":
                        getattr(self, k).clear()
                        getattr(self, k).update(eval(v))
                        getattr(self.widgets, "selected_categories").clear()
                        getattr(self.widgets, "selected_categories").addItems(list(getattr(self, k)))
                    elif k == "font":
                        self.font = QFont()
                        self.font.fromString(v)
                        self._gen_font_stylesheet()
                        self._update_fmtstr()
                    elif k == "color":
                        self.color = QColor(v)
                        self._gen_font_stylesheet()
                        self._update_fmtstr()
                    else:
                        log_warn("unknown key", k)
                    continue
                cast, getter, setter = dkey
                field = getattr(self.widgets, k)
                #getattr(field, setter)(eval(cast)(v))
                getattr(field, setter)(eval(v))
            f.close()
            return True
        except Exception, e:
            QErrorMessage(self).showMessage(str(e))
            return False
        
    def profile_handler(self, load=False, save=False, save_as=False):
        if load:
            profile = QFileDialog.getOpenFileName(self, "Select Profile", TRIVIA_ROOT)
            if profile and self.load_profile(profile):
                self.current_profile_name = profile
        elif save:
            if self.current_profile_name:
                self.save_profile(self.current_profile_name)
        elif save_as:
            profile = QFileDialog.getSaveFileName(self, "Save Profile", TRIVIA_ROOT)
            if profile:
                self.save_profile(profile)
                
    def start_requested(self):
        log_err("implement me")
    def stop_requested(self):
        log_err("implement me")
    def pause_requested(self):
        log_err("implement me")
    def next_requested(self):
        log_err("implement me")
        
    def got_notification(self, notification_object):
        self.notifications.addItem(notification_object)
        self._notification_dlg.show()
    def del_notification(self, notification_object):
        self.notifications.delItem(notification_object)
class _QAData(object):
    def __init__(self):
        self.question = None
        self.answers = []
        self.id = -1
        self.category = None
        self.type = None
    def ask_string(self):
        pass
    def hint_string(self):
        pass
    def is_correct(self, answer):
        for a in self.answers:
            if a.lower() in answer.lower():
                return True
        return False
class _QuestionData(_QAData):
    def ask_string(self):
        return "Category %s: %s" % (self.category, self.question)
    def hint_string(self):
        ret = ""


Answer:         longest = max(self.answers)