# -*- coding: utf-8 -*-
r"""Velocidade de cada NPC, MEDIDA no 04 02 do oficial.

    python tools/extrair_velocidade_npc.py            mostra
    python tools/extrair_velocidade_npc.py --aplicar  grava Data/Monsters/MobVelocidade.ini

O `04 02` traz DOIS floats por criatura: [44] e [48]. Na Aranha pequena os dois aparecem
medidos no movimento dela, cada um no seu regime:

    [48] = 10  ->  caminha a 10,5   (p50 de 684 movimentos)
    [44] = 40  ->  corre   a 44,7   (p90 dos mesmos)

Por isso a leitura: [44] e CORRIDA, [48] e CAMINHADA.

PORQUE ESTA FERRAMENTA EXISTE. Ate 31/08 estes dois campos saiam do molde `npc_appear/N.bin`
-- e quem nao tinha molde herdava (40, 10) do generico. Das 69 especies que nascem no mundo,
22 nao tinham molde e 12 dessas estavam erradas:

    Gargula dourada   o oficial da (90, 10)   nos davamos (40, 10)
    Frezzard          (60, 20)
    Henderson         (60, 10)
    nove NPCs de cidade  (0, 0)               nos punhamos a andar

E o MESMO defeito do campo 78 da ficha e da velocidade no 08 01: o molde traz o valor da
criatura de onde foi capturado e o resto fica a mentir. A cura e a mesma -- ler de uma
tabela medida em vez de herdar do molde.
"""
import argparse
import collections
import glob
import os
import struct
import sys

RAIZ = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(RAIZ, "tools", "analise"))
sys.path.insert(0, os.path.join(RAIZ, "server"))
import ler_hook as H                                                # noqa: E402
import data_files as D                                              # noqa: E402

CR = chr(13) + chr(10)
TB = chr(9)
OFF_CORRIDA, OFF_CAMINHADA = 44, 48

NAO_SAO_ESPECIE = frozenset((0, 1))
"""Os `npc_id` que aparecem no 04 02 e NAO sao criaturas.

O zero e a propria personagem. O UM sao OS OUTROS JOGADORES -- descobre-se pelos numeros:
aparece com 71,5/49,7 e com sete pares diferentes, que sao velocidades de gente com botas e
pocoes, nao de bicho. Sem esta excepcao a tabela dava 71,5 de corrida a uma especie
inexistente, e um dia alguem ia procurar que monstro e esse."""

FONTES = (
    r"C:\RoshHook\traffic*.log",
    r"C:\Users\Administrator\Desktop\CapturaKaros\*.log",
    r"C:\Users\Administrator\Desktop\CapturaKaros\*\*.log",
)


def coletar():
    """{npc_id: Counter((corrida, caminhada))}"""
    out = collections.defaultdict(collections.Counter)
    vistos = set()
    for padrao in FONTES:
        for cam in sorted(glob.glob(padrao)):
            k = os.path.abspath(cam).lower()
            if k in vistos:
                continue
            vistos.add(k)
            try:
                for _t, op, c in H.pacotes(cam, "RECV"):
                    if op != bytes([4, 2]) or len(c) < 74:
                        continue
                    npc = struct.unpack_from("<I", c, 70)[0]
                    if npc in NAO_SAO_ESPECIE:
                        continue
                    a = struct.unpack_from("<f", c, OFF_CORRIDA)[0]
                    b = struct.unpack_from("<f", c, OFF_CAMINHADA)[0]
                    # SO NUMEROS POSSIVEIS. Apareceu uma linha com -883,9 num id de
                    # 0x4000000: e leitura de um 04 02 que nao e de criatura nenhuma.
                    # Velocidade negativa nao existe e acima de 200 tambem nao -- o mais
                    # rapido medido no jogo e a Gargula dourada, a 90.
                    if a != a or b != b or not (0.0 <= a <= 200.0) or not (0.0 <= b <= 200.0):
                        continue
                    out[npc][(round(a, 1), round(b, 1))] += 1
            except Exception as e:                                  # noqa: BLE001
                print("[aviso] %s: %s" % (os.path.basename(cam), str(e)[:70]))
    return out


def main() -> int:
    ap = argparse.ArgumentParser(description="Velocidade de NPC, do 04 02 do oficial.")
    ap.add_argument("--aplicar", action="store_true")
    args = ap.parse_args()

    D.carregar()
    nomes = {}
    for r in D.tabela("Mob"):
        try:
            nomes[int(r["mob_id"])] = str(r.get("nome") or "")
        except (TypeError, ValueError, KeyError):
            pass

    dados = coletar()
    linhas = []
    for npc, c in dados.items():
        (corrida, caminhada), n = c.most_common(1)[0]
        # DISCORDANCIA fica anotada: se a especie apareceu com dois pares diferentes, o
        # numero pode depender de estado e nao da especie, e quem ler tem de saber.
        linhas.append((npc, corrida, caminhada, n, len(c), nomes.get(npc, "")))
    linhas.sort(key=lambda x: (-x[3], x[0]))

    print("%-11s %-9s %-11s %-7s %-8s %s"
          % ("npc_id", "corrida", "caminhada", "amostras", "variantes", "nome"))
    print("-" * 76)
    for npc, a, b, n, v, nome in linhas:
        marca = "   <- varia" if v > 1 else ""
        print("%-11d %-9.1f %-11.1f %-8d %-9d %s%s" % (npc, a, b, n, v, nome[:26], marca))
    print()
    print("especies: %d   pacotes lidos: %d"
          % (len(linhas), sum(sum(c.values()) for c in dados.values())))

    if not args.aplicar:
        print()
        print("nada gravado. corra com --aplicar")
        return 0

    destino = os.path.join(RAIZ, "server", "Data", "Monsters", "MobVelocidade.ini")
    with open(destino, "w", encoding="utf-8", newline="") as f:
        w = f.write
        w("; MobVelocidade -- os dois floats de velocidade do 04 02, por especie." + CR)
        w("; MEDIDO nos offsets 44 (corrida) e 48 (caminhada) das capturas do oficial." + CR)
        w("; Gerado por tools/extrair_velocidade_npc.py -- ver o cabecalho de la." + CR)
        w(";" + CR)
        w("; variantes > 1 quer dizer que a especie apareceu com mais de um par: nesse caso" + CR)
        w("; fica o mais visto, e o numero pode depender de estado e nao da especie." + CR)
        w("; Quem nao estiver aqui fica com o que o molde do 04 02 trouxer." + CR)
        w("//npc_id" + TB + "corrida" + TB + "caminhada" + TB + "amostras" + TB
          + "variantes" + TB + "nome" + CR)
        for npc, a, b, n, v, nome in linhas:
            w("%d%s%.1f%s%.1f%s%d%s%d%s%s%s" % (npc, TB, a, TB, b, TB, n, TB, v, TB, nome, CR))
    print()
    print("gravado: %s (%d linhas)" % (destino, len(linhas)))
    return 0


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