import re

from gi.repository import GObject

from pychess.Utils.const import WHITE, FISCHERRANDOMCHESS, UNSUPPORTED
from pychess.ic import (
    BLKCMD_ASSESS,
    VariantGameType,
    DEVIATION,
    GAME_TYPES,
    parse_title_hex,
    BLKCMD_UNSEEK,
    BLKCMD_SEEK,
    type_to_display_text,
    Variants,
    RATING_TYPES,
)
from pychess.ic.FICSObjects import FICSSeek
from pychess.System.Log import log

rated = "(rated|unrated)"
colors = r"(?:\[(white|black)\]\s?)?"
ratings = r"([\d\+\- ]{1,4})"
titleslist = r"(?:GM|IM|FM|WGM|WIM|WFM|TM|SR|TD|CA|C|U|D|B|T|\*)"
titleslist_re = re.compile(titleslist)
titles = r"((?:\(%s\))+)?" % titleslist
names = "([a-zA-Z]+)%s" % titles
mf = r"(?:([mf]{1,2})\s?)?"
whomatch = r"(?:(?:([-0-9+]{1,4})([\^~:\#. &])%s))" % names
whomatch_re = re.compile(whomatch)
rating_re = re.compile("[0-9]{2,}")
type_re = "(Lightning|Blitz|Standard|Suicide|Wild|Crazyhouse|Bughouse|Losers|Atomic)"


class SeekManager(GObject.GObject):
    __gsignals__ = {
        "addSeek": (GObject.SignalFlags.RUN_FIRST, None, (object,)),
        "removeSeek": (GObject.SignalFlags.RUN_FIRST, None, (int,)),
        "clearSeeks": (GObject.SignalFlags.RUN_FIRST, None, ()),
        "our_seeks_removed": (GObject.SignalFlags.RUN_FIRST, None, ()),
        "seek_updated": (GObject.SignalFlags.RUN_FIRST, None, (str,)),
        "assessReceived": (GObject.SignalFlags.RUN_FIRST, None, (object,)),
    }

    def __init__(self, connection):
        GObject.GObject.__init__(self)
        self.connection = connection

        self.connection.expect_line(self.on_seek_clear, "<sc>")
        self.connection.expect_line(self.on_seek_add, "<s(?:n?)> (.+)")
        self.connection.expect_line(self.on_seek_remove, r"<sr> ([\d ]+)")
        self.connection.expect_n_lines(
            self.on_our_seeks_removed,
            r"<sr> ([\d ]+)",
            r"Your seeks have been removed\.",
        )
        self.connection.expect_n_lines(
            self.on_seek_updated,
            r"Updating seek ad (\d+)(?:;?) (.*)\.",
            "",
            r"<sr> (\d+)",
            "",
            "<sn> (.+)",
        )
        self.connection.expect_n_lines(
            self.on_seek_updated,
            r"Updating seek ad (\d+)(?:;?) (.*)\.",
            r"Updating seek ad \d+(?:;?) (.*)\.",
            "",
            r"<sr> (\d+)",
            "",
            "<sn> (.+)",
        )

        self.connection.expect_n_lines(
            self.on_assess,
            r"\s*%s\s*" % type_re,
            r"\s*(\w+)\s+(\w+)\s*",
            r"\s*(\(.+\))\s+(\(.+\))\s*",
            r"\s*Win: .+",
            r"\s*Draw: .+",
            r"\s*Loss: .+",
            r"\s*New RD: .+",
        )

        self.connection.client.run_command("iset seekinfo 1")
        self.connection.client.run_command("iset seekremove 1")
        self.connection.client.run_command("iset showownseek 1")

    def seek(
        self,
        startmin,
        incsec,
        game_type,
        rated,
        ratings=(0, 9999),
        color=None,
        manual=False,
    ):
        log.debug(
            "SeekManager.seek: %s %s %s %s %s %s %s"
            % (startmin, incsec, game_type, rated, str(ratings), color, manual)
        )
        rchar = "r" if rated else "u"
        if color is not None:
            cchar = color == WHITE and "w" or "b"
        else:
            cchar = ""
        manual = "m" if manual else ""
        s = "seek %d %d %s %s %s" % (startmin, incsec, rchar, cchar, manual)
        if isinstance(game_type, VariantGameType):
            s += " " + game_type.seek_text
        if not self.connection.FatICS:
            s += " %d-%d" % (ratings[0], ratings[1])

        self.connection.client.run_command(s, show_reply=True)

    def assess(self, player1, player2, type):
        self.connection.client.run_command(f"assess {player1} {player2} /{type}")

    def on_assess(self, matchlist):
        assess = {}
        assess["type"] = matchlist[0].groups()[0]
        assess["names"] = matchlist[1].groups()[0], matchlist[1].groups()[1]
        assess["oldRD"] = matchlist[2].groups()[0], matchlist[2].groups()[1]
        assess["win"] = matchlist[3].string.split()[1:]
        assess["draw"] = matchlist[4].string.split()[1:]
        assess["loss"] = matchlist[5].string.split()[1:]
        assess["newRD"] = matchlist[6].string.split()[2:]
        self.emit("assessReceived", assess)

    on_assess.BLKCMD = BLKCMD_ASSESS

    def on_seek_add(self, match):
        # The <s> message looks like:
        # <s> index w=name_from ti=titles rt=rating t=time i=increment
        #     r=rated('r')/unrated('u') tp=type("wild/fr", "wild/4","blitz")
        #     c=color rr=rating_range(lower-upper) a=automatic?('t'/'f')
        #     f=formula_checked('t'/f')
        parts = match.groups()[0].split()
        seek = {}
        for key, value in [p.split("=") for p in parts[1:] if p]:
            seek[key] = value

        try:
            index = int(parts[0])
            player = self.connection.players.get(seek["w"])
            player.titles |= parse_title_hex(seek["ti"])
            rated = seek["r"] == "r"
            minutes = int(seek["t"])
            increment = int(seek["i"])
            rmin, rmax = (int(r) for r in seek["rr"].split("-"))
            rating = seek["rt"]
            if rating[-1] in (" ", "P", "E"):
                deviation = DEVIATION[rating[-1]]
                rating = rating[:-1]
            rating = int(rating)
            deviation = None
            automatic = seek["a"] == "t"
            color = None
            if seek["c"] == "W":
                color = "white"
            elif seek["c"] == "B":
                color = "black"
        except KeyError as e:
            log.warning(f"on_seek_add: KeyError: {repr(e)} {repr(seek)}")
            return

        try:
            gametype = GAME_TYPES[seek["tp"]]
        except KeyError:
            if self.connection.FatICS and seek["tp"] == "chess":
                # TODO: remove when fixed in FatICS
                expected_time = minutes + increment * 2 / 3
                if expected_time == 0:
                    gametype = "untimed"
                elif expected_time < 3:
                    gametype = "lightning"
                elif expected_time < 15:
                    gametype = "blitz"
                else:
                    gametype = "standard"
                gametype = GAME_TYPES[gametype]
            else:
                return
        if gametype.variant_type in UNSUPPORTED:
            return

        if (
            gametype.rating_type in RATING_TYPES
            and player.ratings[gametype.rating_type] != rating
        ):
            player.ratings[gametype.rating_type] = rating
            player.deviations[gametype.rating_type] = deviation
            player.emit("ratings_changed", gametype.rating_type, player)

        seek = FICSSeek(
            index,
            player,
            minutes,
            increment,
            rated,
            color,
            gametype,
            rmin=rmin,
            rmax=rmax,
            automatic=automatic,
        )
        self.emit("addSeek", seek)

    on_seek_add.BLKCMD = BLKCMD_SEEK

    def on_seek_clear(self, *args):
        self.emit("clearSeeks")

    def on_seek_remove(self, match):
        for key in match.groups()[0].split():
            if not key:
                continue
            self.emit("removeSeek", int(key))

    on_seek_remove.BLKCMD = BLKCMD_UNSEEK

    def on_our_seeks_removed(self, matchlist):
        self.on_seek_remove(matchlist[0])
        self.emit("our_seeks_removed")

    on_our_seeks_removed.BLKCMD = BLKCMD_UNSEEK

    def on_seek_updated(self, matchlist):
        text = matchlist[0].groups()[1]
        i = 0
        if "Updating seek ad" in matchlist[1].string:
            text += "; " + matchlist[1].groups()[0]
            i = 1
        self.on_seek_remove(matchlist[i + 2])
        self.on_seek_add(matchlist[i + 4])
        self.emit("seek_updated", text)

    on_seek_updated.BLKCMD = BLKCMD_SEEK

    def refresh_seeks(self):
        self.connection.client.run_command("iset seekinfo 1")


if __name__ == "__main__":
    assert (
        type_to_display_text("Loaded from eco/a00")
        == type_to_display_text("eco/a00")
        == "Eco A00"
    )
    assert type_to_display_text("wild/fr") == Variants.variants[FISCHERRANDOMCHESS].name
    assert type_to_display_text("blitz") == GAME_TYPES["blitz"].display_text
