# -*- coding: utf-8 -*-
r"""Mede a regeneracao de HP e MP do jogador, do 08 01 das capturas.

    python tools/medir_regen.py [captura.log ...]

O 08 01 e a barra do proprio heroi e vai a cada mudanca: HP em [15:19] e HP maximo em
[19:23], MP em [23:27] e maximo em [27:31], todos u32 LE.

SEPARA AS POPULACOES, que e onde as medicoes deste projeto costumam falhar. Um degrau de HP
para CIMA pode ser regeneracao, pocao, subida de nivel ou ressurreicao; misturar tudo da uma
media que nao descreve nenhum. Aqui so entram subidas que:

  - venham depois de pelo menos SEM_DANO segundos sem NENHUMA descida de HP (fora de luta)
  - nao mudem o HP maximo (subir de nivel muda, e enche a barra)
  - nao encham a barra de uma vez a partir de pouca vida (pocao ou ressurreicao)

O que sobra e o passo periodico. O intervalo sai da diferenca de tempo entre subidas
seguidas, e o passo do proprio degrau.
"""
import collections
import glob
import os
import re
import struct
import sys

SEM_DANO = 6.0          # segundos sem levar dano para considerar "fora de luta"
OFF_HP, OFF_HP_MAX, OFF_MP, OFF_MP_MAX = 15, 19, 23, 27


def frames(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 > 65535 or pos + 4 + n > len(b):
                break
            yield t, d, bytes(b[pos + 4:pos + 4 + n])
            pos += 4 + n


def seg(t):
    h, m, s = t.split(":")
    return int(h) * 3600 + int(m) * 60 + float(s)


def barras(cam):
    """(tempo, hp, hp_max, mp, mp_max) de cada 08 01, por ordem."""
    saida = []
    for t, d, c in frames(cam):
        if d != "RECV" or len(c) < 31 or c[0:2] != bytes([8, 1]):
            continue
        saida.append((seg(t),
                      struct.unpack_from("<I", c, OFF_HP)[0],
                      struct.unpack_from("<I", c, OFF_HP_MAX)[0],
                      struct.unpack_from("<I", c, OFF_MP)[0],
                      struct.unpack_from("<I", c, OFF_MP_MAX)[0]))
    return saida


def medir(pontos, i_val, i_max, rotulo):
    passos, intervalos = collections.Counter(), []
    ultimo_dano = -1e9
    anterior = None
    for p in pontos:
        t, val, mx = p[0], p[i_val], p[i_max]
        if anterior is None:
            anterior = p
            continue
        t0, v0, m0 = anterior[0], anterior[i_val], anterior[i_max]
        if val < v0:
            ultimo_dano = t
        elif val > v0 and mx == m0:
            # so conta fora de luta e sem encher a barra de uma vez
            encheu = val == mx and v0 < mx * 0.5
            if t - ultimo_dano >= SEM_DANO and not encheu:
                passos[val - v0] += 1
                if anterior[i_val] < val:
                    intervalos.append(round(t - t0, 2))
        anterior = p
    print("=== %s ===" % rotulo)
    if not passos:
        print("   nenhuma subida fora de luta")
        return
    print("   degraus (tamanho -> quantas vezes): %s"
          % ", ".join("%+d x%d" % (k, v) for k, v in passos.most_common(8)))
    if intervalos:
        intervalos.sort()
        n = len(intervalos)
        print("   intervalos: mediana %.2f s   min %.2f   max %.2f   (%d amostras)"
              % (intervalos[n // 2], intervalos[0], intervalos[-1], n))
        curtos = [x for x in intervalos if x < 30]
        if curtos:
            curtos.sort()
            print("   so os abaixo de 30 s: mediana %.2f s  (%d amostras)"
                  % (curtos[len(curtos) // 2], len(curtos)))


def main() -> int:
    caps = sys.argv[1:] or (
        sorted(glob.glob(r"C:\Users\Administrator\Desktop\CapturaKaros\*.log"))
        + sorted(glob.glob(r"C:\RoshHook\traffic*.log")))
    todos = []
    for cam in caps:
        p = barras(cam)
        if len(p) < 5:
            continue
        print("%s: %d barras de heroi" % (os.path.basename(cam), len(p)))
        todos.extend(p)
    print()
    if not todos:
        print("nenhuma captura com 08 01 suficiente")
        return 1
    medir(todos, 1, 2, "HP")
    print()
    medir(todos, 3, 4, "MP")
    return 0


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