# -*- coding: utf-8 -*-
"""Compara o RITMO da entrada no mundo: rajadas nossas contra as do oficial.

    python tools/ritmo_entrada.py <captura_nossa.log> <captura_oficial.log>

O jogador relatou que no nosso servidor o jogo trava cerca de um segundo quando o
personagem entra, e no oficial entra liso. Contagem de pacotes nao explica isso -- as duas
sequencias batem. O que explica e COMO os bytes chegam: o client processa o que recebe no
mesmo laco que desenha o quadro, entao um bloco grande de uma vez congela a tela enquanto
ele desempilha.

Este script mede, do charinfo (02 05) ate o jogador se mexer:
  - quantos bytes o servidor manda por segundo
  - a maior rajada: bytes entregues sem pausa entre eles
  - quantos frames vem colados no mesmo milissegundo
"""
import os
import re
import struct
import sys

MARCO = (2, 5)


def eventos(caminho):
    """(hora_em_segundos, direcao, opcode, tamanho) de cada frame da captura."""
    atual, blocos = None, []
    for linha in open(caminho, encoding="latin-1", errors="replace"):
        m = re.match(r"\[(\d+):(\d+):([\d.]+)\] (SEND|RECV) sock=", linha)
        if m:
            t = int(m.group(1)) * 3600 + int(m.group(2)) * 60 + float(m.group(3))
            atual = [t, m.group(4), bytearray()]
            blocos.append(atual)
            continue
        if atual is not None and re.fullmatch(r"(?:[0-9A-Fa-f]{2} )+\s*", linha):
            atual[2].extend(bytes.fromhex(linha.strip()))
    for t, direcao, dados in blocos:
        pos = 0
        while pos + 4 <= len(dados):
            (n,) = struct.unpack_from(">I", dados, pos)
            if n == 0 or n > 16384 or pos + 4 + n > len(dados):
                break
            corpo = dados[pos + 4:pos + 4 + n]
            if len(corpo) >= 2:
                yield t, direcao, (corpo[0], corpo[1]), n + 4
            pos += 4 + n


def entrada(caminho):
    """So o trecho de entrada: do charinfo ate o jogador andar."""
    saida, ligado = [], False
    for t, direcao, op, tam in eventos(caminho):
        if not ligado:
            if direcao == "RECV" and op == MARCO:
                ligado = True
            else:
                continue
        if direcao == "SEND" and op == (4, 1):
            break
        if direcao == "RECV":
            saida.append((t, op, tam))
    return saida


def analisar(rotulo, caminho):
    pacotes = entrada(caminho)
    if not pacotes:
        print("%-10s sem trecho de entrada em %s" % (rotulo, os.path.basename(caminho)))
        return
    t0, t1 = pacotes[0][0], pacotes[-1][0]
    total = sum(p[2] for p in pacotes)
    dur = max(t1 - t0, 0.001)

    # rajada = frames separados por menos de 50 ms
    rajadas, atual = [], [pacotes[0]]
    for ant, p in zip(pacotes, pacotes[1:]):
        if p[0] - ant[0] < 0.050:
            atual.append(p)
        else:
            rajadas.append(atual)
            atual = [p]
    rajadas.append(atual)
    maior = max(rajadas, key=lambda r: sum(x[2] for x in r))
    bytes_maior = sum(x[2] for x in maior)
    dur_maior = max(maior[-1][0] - maior[0][0], 0.001)

    print("%s  (%s)" % (rotulo, os.path.basename(caminho)))
    print("   pacotes na entrada    : %d" % len(pacotes))
    print("   bytes totais          : %s" % "{:,}".format(total))
    print("   duracao               : %.2f s" % dur)
    print("   media                 : %s bytes/s" % "{:,.0f}".format(total / dur))
    print("   rajadas (pausa>50ms)  : %d" % len(rajadas))
    print("   MAIOR RAJADA          : %s bytes em %d frames, em %.3f s  ->  %s bytes/s"
          % ("{:,}".format(bytes_maior), len(maior), dur_maior,
             "{:,.0f}".format(bytes_maior / dur_maior)))
    print("      opcodes dela       : %s" % " ".join(
        sorted({"%02x %02x" % x[1] for x in maior})[:12]))
    print()


def main() -> int:
    if len(sys.argv) < 3:
        print(__doc__)
        return 2
    analisar("NOSSO  ", sys.argv[1])
    analisar("OFICIAL", sys.argv[2])
    print("Leitura: rajada grande = o client recebe muito byte junto e para de desenhar")
    print("enquanto desempilha. E ai que a tela congela.")
    return 0


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