# -*- coding: utf-8 -*-
"""Guarda o 04 02 REAL de cada NPC, tirado das capturas do oficial.

    python tools/extrair_molde_npc.py            mostra
    python tools/extrair_molde_npc.py --aplicar  grava em captures/npc_appear/

Existe porque montar a aparicao de um NPC a partir do molde de ARANHA nao funciona. Em 25/08
os 30 NPCs entraram no mundo e o client desenhou aranha em todos, mesmo com o npc_id certo em
[70:74]. Comparando o nosso pacote com o Domingo real do oficial, 22 bytes diferiam -- entre
eles [36:38] e [40:42], que no real valem 1400 e no nosso 100, que e o HP da aranha do molde.

Adivinhar o resto dos offsets seria repetir o erro que ja custou caro neste projeto. O molde
real nao precisa de adivinhacao: o que o servidor oficial mandou e, por definicao, o que o
client entende. Trocamos so o id da entidade e a posicao.
"""
import collections
import glob
import os
import re
import struct
import sys

RAIZ = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DESTINO = os.path.join(RAIZ, "captures", "npc_appear")
OFF_NPC_ID = 70
sys.path.insert(0, os.path.join(RAIZ, "server"))
import data_files as D                                              # noqa: E402


def frames_de(cam):
    atual, blocos = None, []
    for l in open(cam, encoding="latin-1", errors="replace"):
        m = re.match(r"\[([\d:.]+)\] (SEND|RECV) sock=", l)
        if m:
            atual = [m.group(1), m.group(2), bytearray()]
            blocos.append(atual)
            continue
        if atual is not None and re.fullmatch(r"(?:[0-9A-Fa-f]{2} )+\s*", l):
            atual[2].extend(bytes.fromhex(l.strip()))
    for t, d, b in blocos:
        pos = 0
        while pos + 4 <= len(b):
            (n,) = struct.unpack_from(">I", b, pos)
            if n == 0 or n > 16384 or pos + 4 + n > len(b):
                break
            yield d, bytes(b[pos + 4:pos + 4 + n])
            pos += 4 + n


def main() -> int:
    aplicar = "--aplicar" in sys.argv
    D.carregar()
    cat = D.indice("Npc", "npc_id")
    quero = {int(r["npc_id"]) for r in D.tabela("NpcSpawn")}

    vistos = collections.defaultdict(list)
    for cam in sorted(glob.glob(r"C:\RoshHook\traffic*.log")):
        for d, c in frames_de(cam):
            if d != "RECV" or c[:2] != bytes([4, 2]) or len(c) < OFF_NPC_ID + 4:
                continue
            nid = struct.unpack_from("<I", c, OFF_NPC_ID)[0]
            if nid in quero:
                vistos[nid].append(c)

    print("NPCs no spawn: %d | com molde real na captura: %d" % (len(quero), len(vistos)))
    faltam = sorted(quero - set(vistos))
    if faltam:
        print("SEM MOLDE (vao usar o generico e podem sair errados):")
        for nid in faltam:
            print("   %-10s %s" % (nid, (cat.get(nid) or {}).get("nome")))
    print()
    for nid in sorted(vistos):
        cs = vistos[nid]
        tam = collections.Counter(len(c) for c in cs)
        print("   %-10s %-26s x%-4d tamanhos=%s"
              % (nid, str((cat.get(nid) or {}).get("nome"))[:26], len(cs), dict(tam)))

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

    if not os.path.isdir(DESTINO):
        os.makedirs(DESTINO)
    for nid, cs in vistos.items():
        # o mais comprido, que e o que traz mais campo preenchido
        melhor = max(cs, key=len)
        with open(os.path.join(DESTINO, "%d.bin" % nid), "wb") as fp:
            fp.write(melhor)
    print()
    print("gravados %d moldes em %s" % (len(vistos), DESTINO))
    return 0


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