# -*- coding: utf-8 -*-
"""Mede o drop no oficial e tira o molde do pacote 06 04.

    python tools/medir_drop.py                 mede as capturas do Desktop
    python tools/medir_drop.py --molde          grava tambem captures/molde_drop_06_04.bin

O QUE SE MEDE

  raio de recolha   distancia entre o jogador e o saco no instante em que o saco sai
  tempo no chao     quanto tempo o saco fica la ate ser apanhado
  itens por abate   quantos sacos saem de cada morte

LAYOUT DO 06 04 (saco aparece, 187 bytes, servidor -> client)

    [  0:  2]  06 04
    [  2:  6]  sequencia BE
    [  6: 10]  DONO, id de jogador BE. 0xFFFFFFFF = livre para todos.
    [ 10: 14]  id do saco BE -- cresce ao longo da sessao (197 de 202 passos)
    [ 28: 32]  item_id LE -- 258 de 258 batem no catalogo
    [ 36: 40]  id de instancia LE, unico em cada saco
    [ 42: 46]  quantidade LE
    [173:185]  x, y, z, tres floats LE

O resto sao zeros e constantes que nao decifrei -- por isso se usa MOLDE, e nao um pacote
montado do zero: copia-se um pacote real e trocam-se so os campos conhecidos. Foi o que se
fez no golpe e no dano, e e o que evita que um byte esquecido parta o client em silencio.

LAYOUT DO 06 03 (saco sai, 20 bytes)

    [ 0: 2]  06 03
    [ 2: 6]  sequencia BE
    [ 6:10]  0xFFFFFFFF em todas as 202 amostras
    [10:14]  id do saco BE -- o MESMO do 06 04, casou 202 de 202
    [14:18]  id de quem apanhou BE
    [18:20]  0x005a em todas as 202

NAO HA PEDIDO DE APANHAR. Procurei o SEND que precede cada um dos 202 saques e e sempre
movimento (04 01, 71 vezes) ou outra coisa sem relacao. O jogador passa por cima e o servidor
recolhe sozinho. A classe CItemPickupPacket do client e o lado de RECECAO do 06 03.

O CARAT E UM ITEM. item_id 999999, com quantidade em [42:46]. Nao ha pacote de dinheiro
separado, apesar de existir a classe CDropMoney -- essa e so o modelo 3D do saco.
"""
import collections
import glob
import math
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"))
from ler_hook import blocos                                         # noqa: E402
import data_files as D                                              # noqa: E402

DESKTOP = os.path.join(os.path.expanduser("~"), "Desktop", "CapturaKaros")
MOLDE = os.path.join(RAIZ, "captures", "molde_drop_06_04.bin")


def eventos(caminho):
    for t, d, _sock, dados in blocos(caminho):
        i, n = 0, len(dados)
        while i + 4 <= n:
            tam = int.from_bytes(dados[i:i + 4], "big")
            if tam <= 0 or tam > 65535 or i + 4 + tam > n:
                break
            corpo = dados[i + 4:i + 4 + tam]
            if len(corpo) >= 2:
                yield t, d, corpo[:2], corpo
            i += 4 + tam


def main() -> int:
    D.carregar()
    itens = dict((r["item_id"], str(r.get("nome") or "")) for r in D.tabela("Item"))
    # filtrar as opcoes ANTES de decidir se ha alvos: com `or` a seguir ao filtro, um
    # `--molde` sozinho contava como alvo e a lista ficava vazia sem ninguem reparar.
    alvos = [a for a in sys.argv[1:] if not a.startswith("--")]
    if not alvos:
        alvos = sorted(glob.glob(os.path.join(DESKTOP, "*.log")))

    raios, no_chao, por_abate = [], [], collections.Counter()
    itens_vistos = collections.Counter()
    qtds = collections.defaultdict(collections.Counter)
    molde = None
    total = 0

    for alvo in alvos:
        sacos = {}          # id -> (t, x, y, z)
        jogador = None      # ultima posicao conhecida do proprio jogador
        for t, d, op, c in eventos(alvo):
            if d == "SEND" and op == bytes([4, 1]) and len(c) >= 24:
                jogador = struct.unpack_from("<3f", c, 12)
            elif d == "RECV" and op == bytes([6, 4]) and len(c) == 187:
                total += 1
                if molde is None:
                    molde = c
                sid = struct.unpack_from(">I", c, 10)[0]
                item = struct.unpack_from("<I", c, 28)[0]
                qtd = struct.unpack_from("<I", c, 42)[0]
                pos = struct.unpack_from("<3f", c, 173)
                sacos[sid] = (t, pos)
                itens_vistos[item] += 1
                qtds[item][qtd] += 1
            elif d == "RECV" and op == bytes([6, 3]) and len(c) == 20:
                sid = struct.unpack_from(">I", c, 10)[0]
                if sid in sacos:
                    t0, pos = sacos.pop(sid)
                    no_chao.append(t - t0)
                    if jogador:
                        raios.append(math.hypot(pos[0] - jogador[0], pos[1] - jogador[1]))

    print("capturas lidas: %d" % len(alvos))
    print("sacos vistos: %d | apanhados: %d" % (total, len(no_chao)))
    print()
    if raios:
        raios.sort()
        print("RAIO DE RECOLHA (jogador ao saco no instante em que sai), %d amostras:" % len(raios))
        print("   minimo %.1f   mediana %.1f   p90 %.1f   maximo %.1f"
              % (raios[0], raios[len(raios) // 2], raios[int(len(raios) * 0.9)], raios[-1]))
        print("   NOTA: a posicao do jogador vem do ultimo 04 01 dele, que nao e o instante")
        print("   exato do saque -- por isso o maximo e maior do que o raio verdadeiro.")
    print()
    if no_chao:
        no_chao.sort()
        print("TEMPO NO CHAO ate ser apanhado, %d amostras:" % len(no_chao))
        print("   minimo %.2f s   mediana %.2f s   maximo %.2f s"
              % (no_chao[0], no_chao[len(no_chao) // 2], no_chao[-1]))
    print()
    print("ITENS QUE CAIRAM: %d especies distintas em %d sacos" % (len(itens_vistos), total))
    print("%-11s %-34s %-6s %s" % ("item_id", "nome", "sacos", "quantidades"))
    for item, n in itens_vistos.most_common(20):
        q = sorted(qtds[item].items(), key=lambda x: -x[1])[:4]
        print("%-11d %-34s %-6d %s" % (item, itens.get(item, "?")[:34], n, q))

    if "--molde" in sys.argv and molde is not None:
        with open(MOLDE, "wb") as fp:
            fp.write(molde)
        print()
        print("molde gravado: %s (%d bytes)" % (MOLDE, len(molde)))
    return 0


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