# -*- coding: utf-8 -*-
r"""Tira uma sequencia REAL de 04 01 de uma captura do oficial, para o servidor a repetir.

    python tools/extrair_replay.py [captura.log]  ->  Data/Monsters/Replay.ini

PORQUE EXISTE. Em 28/08 corrigiram-se seis defeitos medidos no movimento do monstro -- o
relogio de 7 s que o mandava embora, o raio de visao que o fazia ser recriado, o Z fixo, os
teletransportes ao carregar a trilha, o alcance de ataque, o campo do rumo -- e todas as
metricas passaram a bater certo com o oficial. O jogador continuou a ver o bicho a chacoalhar.

Quando as medicoes todas concordam e o olho discorda, deixa de valer a pena medir mais: e
preciso uma experiencia que separe as hipoteses. Esta separa duas, e sao as unicas que
sobram:

    o monstro de replay anda LISO   -> a diferenca esta nos NOSSOS pacotes, e compara-se
                                       campo a campo ate se achar qual
    o monstro de replay CHACOALHA   -> a diferenca nao esta nos pacotes; esta no ambiente
                                       (entrega, latencia, a maquina) e nenhuma correccao
                                       de movimento a resolveria

NAO SE ALTERA NADA. Guardam-se as posicoes absolutas, os tempos entre pacotes, o z e o campo
[36:40] tal como vieram do fio. O mapa e o mesmo (Berneo), portanto as coordenadas sao
validas -- basta o jogador ir ao sitio. Rebasear para perto dele estragaria o teste: o z
deixaria de casar com o terreno e ja nao seria uma reproducao.
"""
import io
import os
import re
import struct
import sys

CAB = re.compile(r"^\[(\d{1,2}):(\d\d):(\d\d)\.(\d+)\] (SEND|RECV)")
HEX = re.compile(r"^[0-9A-Fa-f ]+$")
MINIMO = 25          # so vale a pena uma corrida com material que chegue


def sequencias(caminho):
    """Todas as corridas continuas de 04 01 da mesma entidade, com os campos crus."""
    por = {}
    atual, buf, ts = None, [], 0.0

    def fechar():
        if atual != "RECV":
            return
        try:
            b = bytes.fromhex("".join(buf))
        except ValueError:
            return
        i = 0
        while i + 4 <= len(b):
            k = int.from_bytes(b[i:i + 4], "big")
            if k <= 0 or k > 65535 or i + 4 + k > len(b):
                break
            p = b[i + 4:i + 4 + k]
            if len(p) == 42 and p[0] == 4 and p[1] == 1:
                e = int.from_bytes(p[8:12], "big")
                ox, oy, oz = struct.unpack_from("<3f", p, 12)
                dx, dy, dz = struct.unpack_from("<3f", p, 24)
                f36 = struct.unpack_from("<f", p, 36)[0]
                por.setdefault(e, []).append((ts, p[7], ox, oy, oz, dx, dy, dz, f36))
            i += 4 + k

    with io.open(caminho, encoding="latin-1", errors="ignore") as f:
        for ln in f:
            m = CAB.match(ln)
            if m:
                fechar()
                atual, buf = m.group(5), []
                ts = (int(m.group(1)) * 3600 + int(m.group(2)) * 60 + int(m.group(3))
                      + float("0." + m.group(4)))
            elif atual and HEX.match(ln.strip()):
                buf.append(ln.strip().replace(" ", ""))
    fechar()

    saida = []
    for e, v in por.items():
        corrida = [v[0]]
        for a, b in zip(v, v[1:]):
            if b[0] - a[0] < 3.0:
                corrida.append(b)
            else:
                if len(corrida) >= MINIMO:
                    saida.append((e, corrida))
                corrida = [b]
        if len(corrida) >= MINIMO:
            saida.append((e, corrida))
    return saida


def main() -> int:
    args = [a for a in sys.argv[1:] if a not in ("0", "1", "2")]
    cam = args[0] if args else "captures/oficial_0828_2135.log"
    seqs = sequencias(cam)
    if not seqs:
        print("nenhuma corrida com %d ou mais pacotes em %s" % (MINIMO, cam))
        return 1
    # ESCOLHE PELO ESTADO PEDIDO, nao pelo tamanho.
    #
    # A primeira versao escolhia a corrida mais longa e saiu uma com 39 dos 55 pacotes a
    # CORRER. O jogador via os monstros a chacoalhar a ANDAR, portanto a experiencia
    # comparava o padrao errado: provava que o client desenha bem a corrida do oficial e
    # nao dizia nada sobre o passeio. Passar "0" na linha de comando pede passeio.
    quero = None
    for a in sys.argv[1:]:
        if a in ("0", "1", "2"):
            quero = int(a)
    def pontua(kv):
        if quero is None:
            return len(kv[1])
        return sum(1 for r in kv[1] if r[1] == quero)
    e, seq = max(seqs, key=pontua)
    t0 = seq[0][0]
    dur = seq[-1][0] - t0
    print("%d corridas; escolhida a da entidade %d: %d pacotes, %.1f s"
          % (len(seqs), e, len(seq), dur))
    print("   comeca em (%.0f, %.0f, %.0f)" % (seq[0][2], seq[0][3], seq[0][4]))
    estados = {}
    for r in seq:
        estados[r[1]] = estados.get(r[1], 0) + 1
    print("   estados: %s" % estados)

    destino = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                           "..", "server", "Data", "Monsters", "Replay.ini")
    nl = chr(10)
    with io.open(destino, "w", encoding="utf-8", newline="") as f:
        f.write("; Replay -- uma sequencia de 04 01 REAL do oficial, para o servidor repetir" + nl)
        f.write("; tal e qual. Gerada por tools/extrair_replay.py; ver o cabecalho de la." + nl)
        f.write("; entidade %d, %d pacotes, %.1f s, capturado em %s"
                % (e, len(seq), dur, os.path.basename(cam)) + nl)
        f.write("//ms\testado\tox\toy\toz\tdx\tdy\tdz\tcampo36" + nl)
        for t, est, ox, oy, oz, dx, dy, dz, f36 in seq:
            f.write("%d\t%d\t%.3f\t%.3f\t%.3f\t%.3f\t%.3f\t%.3f\t%.6f%s"
                    % (int((t - t0) * 1000), est, ox, oy, oz, dx, dy, dz, f36, nl))
    print("escrito em %s" % os.path.normpath(destino))
    return 0


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