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

ITALIC = r'''\w*\.\w+\s+{[^}]+(?:(?:font-weight\s*:\s*bold)|font-size|text-align|text-indent|margin|padding)(*SKIP)(*FAIL)|
(\w*\.\w+)\s+{[^}]*(?:font-family\s*:\s*["\s\w-]+\s*;?\s+)?(?:font-style\s*:\s*italic\s*;?\s+)[^}]*(?:font-family\s*:\s*["\s\w-]+\s*;?\s*)?[^}]*}'''

BOLD = r'''\w*\.\w+\s+{[^}]+(?:(?:font-weight\s*:\s*italic)|font-size|text-align|text-indent|margin|padding)(*SKIP)(*FAIL)
|(\w*\.\w+)\s+{[^}]*(?:font-family\s*:\s*["\s\w-]+\s*;?\s+)?(?:font-style\s*:\s*bold\s*;?\s+)[^}]*(?:font-family\s*:\s*["\s\w-]+\s*;?\s*)?[^}]*}'''

BOLD1 = r'''@font-face\s+{[^}]+font-style\s*:\s*italic(*SKIP)(*FAIL)|@font-face\s+{[^}]+(?:font-family\s*:\s*([\w-]+))
[^}]+(?:font-weight\s*:\s*bold)|@font-face\s+{[^}]+(?:font-weight\s*:\s*bold)[^}]+(?:font-family\s*:\s*([\w-]+))'''

BOLD2 = r'''\w*\.\w+\s+{[^}]+(?:(?:font-style\s*:\s*italic)|font-size|text-align|text-indent|margin|padding)(*SKIP)(*FAIL)
|(\w*\.\w+)\s+{[^}]*(?:font-family\s*:\s*["\s\w-]+\s*;?\s+)?(?:font-weight\s*:\s*bold\s*;?\s+)[^}]*
(?:font-family\s*:\s*["\s\w-]+\s*;?\s*)?[^}]*}|(\w*\.\w+)\s+{\s+font-family\s*:\s*{0};\s+}'''

BOLD3 = r'''\w*\.\w+\s+{[^}]+(?:(?:font-style\s*:\s*italic)|font-size|text-align|text-indent|margin|padding)(*SKIP)(*FAIL)|
(\w*\.\w+)\s+{[^}]*(?:font-family\s*:\s*["\s\w-]+\s*;?\s+)?(?:font-weight\s*:\s*bold\s*;?\s+)[^}]*(?:font-family\s*:\s*["\s\w-]+\s*;?\s*)?[^}]*}'''

SUP = r'''\w*\.(\w+)\s+{\s*(?:font-size\s*:\s*\d\d\s*%\s*;\s*)?(?:vertical-align\s*:\s*(?:text-)?top\s*;?\s*)?(?:font-size\s*:\s*\d\d\s*%\s*;?\s*)?}'''

ATTR = r'\.(\w+)\s+{[^}]+font-variant\s?:\s*small-caps|\.(\w+)\s+{[^}]+text-transform\s?:'

import regex, time
from functools import wraps
from calibre.utils.config_base import prefs as cal_prefs
from calibre.ebooks.oeb.polish.container import OEB_DOCS, OEB_STYLES, OEB_FONTS
from calibre.ebooks.oeb.base import NCX_MIME
from calibre.ebooks.oeb.polish.utils import guess_type
from calibre.spell.dictionary import best_locale_for_language
from calibre.gui2.tweak_book import editors, dictionaries
from calibre.utils.localization import canonicalize_lang
from calibre_plugins.typex import ICON_FILE
from calibre_plugins.typex.utils import log, get_icon

try :
    from PyQt5.Qt import Qt, QDialogButtonBox, QLabel, QPixmap, QGridLayout, QDialog, QVBoxLayout
except ImportError:
    from PyQt4.Qt import Qt, QDialogButtonBox, QLabel, QPixmap, QGridLayout, QDialog, QVBoxLayout

FLAGS = regex.VERSION1 | regex.WORD | regex.FULLCASE | regex.MULTILINE | regex.UNICODE | regex.IGNORECASE
TAG_LANG = r'<([a-z]+)[^<]*lang="([a-zA-Z-]{2,5})"[^>]*>'
CALIBRE_LOCALE = cal_prefs['language']  # 'fr'


def timethis (func):
    "decorator for the execution time"

    @wraps(func)
    def wrapper (*args, **kwargs):
        "something to prevent pylint whining"
        fStart = time.time()
        result = func(*args, **kwargs)
        fEnd = time.time()
        log(func.__name__, fEnd - fStart)
        return result

    return wrapper


class ThisBook(object):

    @timethis
    def __init__(self, parent):
        self.parent = parent
        self.cont = parent.current_container
        self.boss = parent.boss
        self.docs = [name for name, mime in self.cont.mime_map.items() if mime in OEB_DOCS]
        self.css = [name for name, mime in self.cont.mime_map.items() if mime in OEB_STYLES]
        self.ncx = [name for name, mime in self.cont.mime_map.items() if mime in NCX_MIME]
        self.raw_data_map = {}
        self.aliens = self.other_lang()

    @property
    def version(self):
        return self.cont.opf_version_parsed.major

    @property
    def nav(self):
        if self.version == 2:
            return self.ncx[0]
        else:
            return next(self.cont.manifest_items_with_property('nav'))

    @property
    @timethis
    def style_attr(self):
        ans = {}
        style = regex.compile("<meta name=\"calibre:cover\".+(*SKIP)(*FAIL)|(<style type=\")", FLAGS)
        for file in self.docs:
            _, n = regex.subn(style, style.group())
            if n > 0:
                ans[file] = n
        return ans

    @property
    @timethis
    def italicstyles(self):
        it = []
        f = set()
        itl = regex.compile(ITALIC, FLAGS)
        for file in self.css :
            for m in itl.finditer(self.get_raw(file)):
                it.append(m.group(1))
                f.add(file)
        return it, f

    @property
    @timethis
    def iem(self):
        inum = emnum = 0
        for file in self.docs:
            data = self.get_raw(file)
            _, ireg = regex.subn(r'(<i\b)', '\1', data)
            _, emreg = regex.subn(r'(<em\b)', '\1', data)
            inum += ireg
            emnum += emreg
        return inum, emnum

    @property
    @timethis
    def boldstyles(self):
        bd1 = []
        bd2 = []
        f = set()
        bd1reg = regex.compile(BOLD1, FLAGS)
        for file in self.css :
            for m in bd1reg.finditer(self.get_raw(file)):
                for i in (1, 2):
                    if m.group(i):
                        bd1.append(m.group(i))
        bd1 = set(bd1)
        if len(bd1) == 1:
            bd1 = list(bd1)
            bd2reg = regex.compile(BOLD2.format(bd1[0]), FLAGS)
            for file in self.css :
                for m in bd2reg.finditer(self.get_raw(file)):
                    bd2.append(m.group(1))
                    f.add(file)
        else:
            bd3reg = regex.compile(BOLD3, FLAGS)
            for file in self.css :
                for m in bd3reg.finditer(self.get_raw(file)):
                    bd2.append(m.group(1))
                    f.add(file)
        return bd2, f

    @property
    @timethis
    def bstrong(self):
        bnum = strongnum = 0
        for file in self.docs:
            data = self.get_raw(file)
            _, breg = regex.subn(r'(<b\b)', '\1', data)
            _, strongreg = regex.subn(r'(<strong\b)', '\1', data)
            bnum += breg
            strongnum += strongreg
        return bnum, strongnum

    @property
    @timethis
    def dubious_attr(self):
        variant = set()
        transform = set()
        dub_attr = regex.compile(ATTR, FLAGS)
        for file in self.css:
            for m in dub_attr.finditer(self.get_raw(file)):
                if m.group(1):
                    variant.add(m.group(1))
                if m.group(2):
                    transform.add(m.group(2))
        return variant, transform

    @property
    @timethis
    def unused_fonts(self):
        from calibre.ebooks.oeb.polish.stats import StatsCollector
        fontlist = [name for name, mt in self.cont.mime_map.items() if \
                   ((mt in OEB_FONTS or name.rpartition('.')[-1].lower() in {'otf', 'ttf'}) and mt != guess_type('a.woff'))]
        if len(fontlist):
            stats = StatsCollector(self.cont).font_stats
        uuf = list()
        for name in fontlist:
            chars = stats.get(name, set())
            if not chars:
                uuf.append(name)
                continue
        return uuf

    @property
    @timethis
    def opf_lang(self):
        opf_data = self.get_raw(self.cont.opf_name)
        lg = regex.search(r"<dc:language.*>(\w\w).*<\/dc:language>", opf_data, FLAGS)
        if lg:
            if len(lg.group()) > 1:
                # TODO vérifier unicité de la langue
                pass
            return lg.group(1)
        else :
            return _("Non définie")

    @timethis
    def other_lang(self):
        d = {}
        for file in self.docs + self.ncx:
            ols = []
            data = self.get_raw(file)
            ol_reg = regex.compile(TAG_LANG, FLAGS)
            # ppat = regex.compile(r'.*', flags=FLAGS | regex.DOTALL)
            for ol in ol_reg.finditer(data):
                # log(ol)
                if (ol.group(2)[0:2].lower() != CALIBRE_LOCALE) :
                    # log('g2', ol.group())
                    # log('!=?', ol.group(2)[0:2].lower() != CALIBRE_LOCALE)
                    closing = regex.search(r'<\/{}'.format(ol.group(1)), data, FLAGS, pos=ol.end())
                    # log('clos', closing)
                    ols.append((ol.end(), closing.start()))
                    # p = ppat.search(data, pos=ol.end(), endpos=closing.start())
                    # log(p)
            if len(ols) and 'titlepage.xhtml' not in file:
                d[file] = ols
                # log('d', d)
        return d

    def has_rtl(self, file):
        return True if 'dir="rtl"' in self.get_raw(file) else False

    def get_raw(self, file):
        if self.parent.cache.get(file, False):
            raw = self.parent.cache[file]
        else :
            raw = self.parent.current_container.raw_data(file, decode=True, normalize_to_nfc=True)
            self.parent.cache[file] = raw
        return raw


class InfoDialog(QDialog):

    def __init__(self, container, tc):
        self.cont = container
        self.tc = tc
        QDialog.__init__(self)
        self.setWindowIcon(get_icon(ICON_FILE))
        self.setWindowTitle(_("Infos sur cet epub"))
        vl = QVBoxLayout(self)
        self.setLayout(vl)

        layout = QGridLayout(self)
        vl.addLayout(layout)
        italicstyles = self.cont.italicstyles
        lbit = QLabel(_("Style italique"))
        lb2it = QLabel(', '.join(italicstyles[0]) + _(' dans {}').format(' '.join(italicstyles[1]))\
                        if italicstyles[0] else '')
        layout.addWidget(lbit, 1, 0)
        layout.addWidget(lb2it, 1, 2)

        lbiem = QLabel(_("Balise italique (i ou em) :"))
        lbiem2 = QLabel('')
        if self.cont.iem[0] > 0:
            lt = 'i'
        else :
            if self.cont.iem[1] > 0:
                lt = 'em'
            else:
                lt = _('Non')
        lbiem2.setText(lt)
        layout.addWidget(lbiem, 2, 0)
        layout.addWidget(lbiem2, 2, 2)

        lbbd = QLabel(_("Style gras"))
        boldstyles = self.cont.boldstyles
        lb2bd = QLabel(', '.join(boldstyles[0]) + _(' dans {}').format(' '.join(boldstyles[1]))\
                        if boldstyles[0] else _("aucun"))
        layout.addWidget(lbbd, 3, 0)
        layout.addWidget(lb2bd, 3, 2)

        lbbstrong = QLabel(_("Balise gras (b ou strong)"))
        lb2bstrong = QLabel('')
        lbs = _('Non')
        strong = self.cont.bstrong
        if strong[0] > 0:
            lbs = 'b'
        elif strong[1] > 0 :
            lbs = 'strong'
        lb2bstrong.setText(lbs)
        layout.addWidget(lbbstrong, 4, 0)
        layout.addWidget(lb2bstrong, 4, 2)

        lbattr = QLabel(_("Souvent incompris des liseuses :"))
        lbattricon = QLabel()
        self.set_green(lbattricon)
        variant, transform = self.cont.dubious_attr
        desc = ''
        if variant:
            desc += _('petites majuscules présent ')
            self.set_red(lbattricon)
        if transform:
            desc += _('text-transform présent')
            self.set_red(lbattricon)
        if not (variant or transform):
            desc += _('aucun')
        lbattr2 = QLabel(desc)
        layout.addWidget(lbattr, 5, 0)
        layout.addWidget(lbattricon, 5, 1)
        layout.addWidget(lbattr2, 5, 2)

        lbtag = QLabel(_("Balises dans les commentaires :"))
        layout.addWidget(lbtag, 6, 0)
        lb2tag = QLabel(_('dans {}').format('\n'.join(self.tc)) if len(self.tc) else _('aucune'))
        layout.addWidget(lb2tag, 6, 2)
        lbtagicon = QLabel()
        layout.addWidget(lbtagicon, 6, 1)
        self.set_green(lbtagicon)
        if lb2tag.text() != _('aucune'):
            self.set_red(lbtagicon)

        lblg1 = QLabel(_("Langue principale"))
        oeb_opf_lang = canonicalize_lang(self.cont.opf_lang)
        lb2lg1 = QLabel(oeb_opf_lang)
        lg1icon = QLabel()
        self.set_green(lg1icon)
        # log(['canonicalize_lang', canonicalize_lang(CALIBRE_LOCALE)])
        # log(['canonicalize_lang=opf_lang', oeb_opf_lang == canonicalize_lang(CALIBRE_LOCALE)])
        if oeb_opf_lang != canonicalize_lang(CALIBRE_LOCALE):
            self.set_red(lg1icon)
        layout.addWidget(lblg1, 7, 0)
        layout.addWidget(lg1icon, 7, 1)
        layout.addWidget(lb2lg1, 7, 2)

        lblg2 = QLabel(_("Autres langues"))
        layout.addWidget(lblg2, 8, 0)
        i = 0
        aliens = list(self.cont.aliens.keys())
        lb2lg2 = QLabel(_('dans {}').format('\n'.join(aliens)) if len(aliens) else _('aucune'))
        layout.addWidget(lb2lg2, 8, 2)
        lg2icon = QLabel()
        layout.addWidget(lg2icon, 8, 1)
        self.set_green(lg2icon)
        if lb2lg2.text() != _('aucune'):
            self.set_red(lg2icon)

        lbversion = QLabel(_('epub version'))
        lbnav = QLabel(_('Table des Matières'))
        lbversion2 = QLabel('{}'.format(self.cont.version))
        lbnav2 = QLabel('{}'.format(self.cont.nav))
        layout.addWidget(lbversion, 9, 0)
        layout.addWidget(lbversion2, 9, 2)
        layout.addWidget(lbnav, 10, 0)
        layout.addWidget(lbnav2, 10, 2)

        lbfont = QLabel(_('Polices inutilisées'))
        unused_fonts = self.cont.unused_fonts
        lbfont2 = QLabel(_(', '.join(unused_fonts) if len(unused_fonts) else 'aucune'))
        layout.addWidget(lbfont, 11, 0)
        layout.addWidget(lbfont2, 11, 2)

        d1 = QLabel(_('Dictionnaire installé :'))
        dicon = QLabel()
        locale = best_locale_for_language(canonicalize_lang(CALIBRE_LOCALE))
        d2 = QLabel(_('Non'))
        if dictionaries.dictionary_for_locale(locale) :
            d2 = QLabel(dictionaries.dictionary_for_locale(locale).primary_locale.langcode)
            self.set_green(dicon)
        else:
            self.set_red(dicon)
        layout.addWidget(d1, 12, 0)
        layout.addWidget(dicon, 12, 1)
        layout.addWidget(d2, 12, 2)

        bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        layout.addWidget(bb, 13 + i, 0, 2, 3, Qt.AlignHCenter)
        bb.accepted.connect(self.accept)
        bb.rejected.connect(self.reject)

    def set_green(self, label):
        pxm = QPixmap(I('dot_green.png')).scaled(12, 12)
        label.setPixmap(pxm)

    def set_red(self, label):
        pxm = QPixmap(I('dot_red.png')).scaled(12, 12)
        label.setPixmap(pxm)

