import functools
from functools import reduce

import trelby.misc as misc
import trelby.pdf as pdf
import trelby.pml as pml
import trelby.screenplay as screenplay
import trelby.util as util
from trelby.pagelist import PageList


class CharacterReport:
    def __init__(self, sp):

        self.sp = sp

        ls = sp.lines

        # key = character name, value = CharInfo
        chars = {}

        name = None
        scene = "(NO SCENE NAME)"

        # how many lines processed for current speech
        curSpeechLines = 0

        for i in range(len(ls)):
            line = ls[i]

            if (line.lt == screenplay.SCENE) and (line.lb == screenplay.LB_LAST):
                scene = util.upper(line.text)

            elif (line.lt == screenplay.CHARACTER) and (line.lb == screenplay.LB_LAST):
                name = util.upper(line.text)
                curSpeechLines = 0

            elif line.lt in (screenplay.DIALOGUE, screenplay.PAREN) and name:
                ci = chars.get(name)
                if not ci:
                    ci = CharInfo(name, sp)
                    chars[name] = ci

                if scene:
                    ci.scenes[scene] = ci.scenes.get(scene, 0) + 1

                if curSpeechLines == 0:
                    ci.speechCnt += 1

                curSpeechLines += 1

                # PAREN lines don't count as spoken words
                if line.lt == screenplay.DIALOGUE:
                    ci.lineCnt += 1

                    words = util.splitToWords(line.text)

                    ci.wordCnt += len(words)
                    ci.wordCharCnt += reduce(lambda x, y: x + len(y), words, 0)

                ci.pages.addPage(sp.line2page(i))

            else:
                name = None
                curSpeechLines = 0

        # list of CharInfo objects
        self.cinfo = []
        for v in list(chars.values()):
            self.cinfo.append(v)

        self.cinfo = sorted(self.cinfo, key=functools.cmp_to_key(cmpLines))

        self.totalSpeechCnt = self.sum("speechCnt")
        self.totalLineCnt = self.sum("lineCnt")
        self.totalWordCnt = self.sum("wordCnt")
        self.totalWordCharCnt = self.sum("wordCharCnt")

        # information types and what to include
        self.INF_BASIC, self.INF_PAGES, self.INF_LOCATIONS = list(range(3))
        self.inf = []
        for s in ["Basic information", "Page list", "Location list"]:
            self.inf.append(misc.CheckBoxItem(s))

    # calculate total sum of self.cinfo.{name} and return it.
    def sum(self, name):
        return reduce(lambda tot, ci: tot + getattr(ci, name), self.cinfo, 0)

    def generate(self) -> bytes:
        tf = pml.TextFormatter(
            self.sp.cfg.paperWidth, self.sp.cfg.paperHeight, 20.0, 12
        )

        for ci in self.cinfo:
            if not ci.include:
                continue

            tf.addText(ci.name, fs=14, style=pml.BOLD | pml.UNDERLINED)

            if self.inf[self.INF_BASIC].selected:
                tf.addText(
                    "Speeches: %d, Lines: %d (%.2f%%),"
                    " per speech: %.2f"
                    % (
                        ci.speechCnt,
                        ci.lineCnt,
                        util.pctf(ci.lineCnt, self.totalLineCnt),
                        util.safeDiv(ci.lineCnt, ci.speechCnt),
                    )
                )

                tf.addText(
                    "Words: %d, per speech: %.2f,"
                    " characters per: %.2f"
                    % (
                        ci.wordCnt,
                        util.safeDiv(ci.wordCnt, ci.speechCnt),
                        util.safeDiv(ci.wordCharCnt, ci.wordCnt),
                    )
                )

            if self.inf[self.INF_PAGES].selected:
                tf.addWrappedText(
                    "Pages: %d, list: %s" % (len(ci.pages), ci.pages), "       "
                )

            if self.inf[self.INF_LOCATIONS].selected:
                tf.addSpace(2.5)

                for it in util.sortDict(ci.scenes):
                    tf.addText("%3d %s" % (it[1], it[0]), x=tf.margin * 2.0, fs=10)

            tf.addSpace(5.0)

        return pdf.generate(tf.doc)


# information about one character
class CharInfo:
    def __init__(self, name, sp):
        self.name = name

        self.speechCnt = 0
        self.lineCnt = 0
        self.wordCnt = 0
        self.wordCharCnt = 0
        self.scenes = {}
        self.include = True
        self.pages = PageList(sp.getPageNumbers())


def cmpfunc(a, b):
    return (a > b) - (a < b)


def cmpLines(c1, c2):
    ret = cmpfunc(c2.lineCnt, c1.lineCnt)

    if ret != 0:
        return ret
    else:
        return cmpfunc(c1.name, c2.name)
