# -*- coding: utf-8 -*-
r"""Mede as SKILLS do jogador nas capturas do oficial.

    python tools/medir_skills.py  ->  server/Data/Skills/SkillMedida.ini

PORQUE E MEDIDO E NAO LIDO. A dica do client mostra tudo o que precisamos -- o jogador
mandou a imagem de "Golpe de ombro" em 29/08: Hits 2, Ataque fisico Bonus 4~11, Recuperacao
4,0s, mana 6, area 1,5m. Esses valores vivem numa tabela .lod do client (skillBase/skillData)
que NAO esta nesta maquina; o Skill.ini que temos veio do String.lod e so tem texto.

E o texto nao chega, o que se verificou antes de ir por este caminho: das 461 skills com
varios niveis, 431 tem a descricao IDENTICA em todos eles, e nenhuma traz numero de dano.
Diz o que a skill faz, nao quanto faz.

O CAMPO DO ID e [12:16] BE do 05 01. Cheguei la por eliminacao, contando quantos valores
distintos cada offset toma em 5.665 pacotes:

    [36:38]  246 valores, 3 batem com o Skill.ini   <- e um contador, nao um id
    [12:16]   26 valores, 8 batem com o Skill.ini   <- este

Vinte e seis e o numero certo de skills para um personagem; 246 nao e.

O QUE SE MEDE, e o que cada coisa vale:

    dano       byte [27] do 05 02 que vem a seguir. E o byte BAIXO -- dano acima de 255
               da a volta, entao os valores grandes nao sao de confianca.
    hits       quantos 05 02 chegam dentro de 2,5 s do mesmo 05 01. Bate com o campo
               "Hits" da dica.
    recuperacao  MENOR intervalo observado entre dois usos da mesma skill. E um limite
               superior do tempo real: so vale se o jogador tiver carregado no botao
               assim que pode. Onde o uso foi esporadico, o numero mede a paciencia dele
               e nao a skill.
"""
import collections
import os
import sys

sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "analise"))
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "server"))
import metricas as M                                                # noqa: E402

OP_CLI = bytes([5, 1])
OP_DANO = bytes([5, 2])
JANELA_HITS = 2.5


def capturas():
    base = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "captures")
    a = [os.path.join(base, f) for f in sorted(os.listdir(base))
         if f.startswith("oficial") and f.endswith(".log")]
    d2 = os.path.join(os.environ.get("USERPROFILE", ""), "Desktop", "CapturaKaros")
    if os.path.isdir(d2):
        a += [os.path.join(d2, f) for f in sorted(os.listdir(d2))
              if f.endswith(".log") and "2209" not in f]
    return a


def main() -> int:
    import data_files as D
    D.carregar()
    desc = {}
    for r in D.tabela("Skill"):
        s = r.get("skill_id")
        if s and int(s) not in desc:
            desc[int(s)] = (r.get("descricao") or "").replace(chr(10), " ").strip()

    usos = collections.Counter()
    danos = collections.defaultdict(list)
    hits = collections.defaultdict(list)
    interv = collections.defaultdict(list)
    for cam in capturas():
        ev = []
        try:
            for t, op, c in M.pacotes(cam, direcao="SEND"):
                if op == OP_CLI and len(c) >= 16:
                    ev.append((t, "skill", int.from_bytes(c[12:16], "big"), 0))
            for t, op, c in M.pacotes(cam, direcao="RECV"):
                if op == OP_DANO and len(c) == 51:
                    ev.append((t, "dano", c[26], c[27]))
        except Exception:                                           # noqa: BLE001
            continue
        ev.sort(key=lambda x: x[0])
        ult = None
        conta = 0
        visto = {}
        for t, k, a, b in ev:
            if k == "skill":
                if ult:
                    hits[ult[1]].append(conta)
                if a in visto and 0.1 < t - visto[a] < 40:
                    interv[a].append(t - visto[a])
                visto[a] = t
                usos[a] += 1
                ult = (t, a)
                conta = 0
            elif ult and t - ult[0] < JANELA_HITS:
                conta += 1
                if b:
                    danos[ult[1]].append(b)
        if ult:
            hits[ult[1]].append(conta)

    linhas = []
    for sid, n in sorted(usos.items()):
        v = sorted(danos.get(sid, []))
        h = sorted(hits.get(sid, []))
        i = sorted(interv.get(sid, []))
        linhas.append((sid, n,
                       v[len(v) // 2] if v else 0,
                       v[0] if v else 0, v[-1] if v else 0,
                       h[len(h) // 2] if h else 0,
                       ("%.1f" % i[0]) if i else "",
                       desc.get(sid, "")))
    print("%d skills medidas" % len(linhas))

    destino = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                           "..", "server", "Data", "Skills", "SkillMedida.ini")
    nl, tb = chr(10), chr(9)
    with open(destino, "w", encoding="utf-8", newline="") as f:
        f.write("; SkillMedida -- dano, golpes e recuperacao, MEDIDOS no fio do oficial." + nl)
        f.write("; Gerado por tools/medir_skills.py. Ver o cabecalho dessa ferramenta:" + nl)
        f.write("; a recuperacao e um LIMITE SUPERIOR e o dano acima de 255 da a volta." + nl)
        f.write(tb.join(["//skill_id", "usos", "dano", "dano_min", "dano_max",
                         "hits", "recuperacao", "descricao"]) + nl)
        for sid, n, d50, dmin, dmax, h, rec, ds in linhas:
            f.write(tb.join([str(sid), str(n), str(d50), str(dmin), str(dmax),
                             str(h), rec, ds[:90]]) + nl)
    print("escrito em %s" % os.path.normpath(destino))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
