# -*- coding: utf-8 -*-
r"""Alinha as colunas das tabelas de Data/ para se lerem sem contar campos.

    python tools/alinhar_tabelas.py                 mostra o que faria
    python tools/alinhar_tabelas.py --aplicar       alinha tudo
    python tools/alinhar_tabelas.py Shop --aplicar  so as que casem com "Shop"

ANTES (tabulacoes)                  DEPOIS (colunas alinhadas)
    //posicao	item_id	nome              //posicao  item_id  nome             preco
    1	1002	1-espada pesada	2115        1          1002     1-espada pesada  2115
    2	1003	2-espada e meia	10100       2          1003     2-espada e meia  10100

POR QUE ISTO NAO PARTE NADA. O separador passa a ser uma corrida de DOIS OU MAIS espacos --
ver `_separar` em server/data_files.py. Um unico espaco nao separa, portanto um nome como
"1-espada pesada" continua inteiro. Os valores sao normalizados na escrita para nunca terem
dois espacos seguidos lá dentro, que e a unica coisa que poderia partir uma linha.

Ficheiros antigos, so com tabulacao, continuam a ser lidos: a tabulacao e sempre separador.

O QUE NAO E TOCADO: ficheiros em formato de seccao ([1] chave = valor), porque esses nao tem
colunas; e valores que ja contenham tabulacao, que sao rejeitados em vez de silenciosamente
truncados.
"""
import glob
import io
import os
import sys

RAIZ = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(RAIZ, "server"))
import data_files as D                                              # noqa: E402
DADOS = os.path.join(RAIZ, "server", "Data")
CR = chr(13) + chr(10)
TAB = chr(9)
LARGURA_MAXIMA = 44        # coluna mais larga que isto deixa de ser paddada
MARGEM = 2                 # espacos de folga depois do valor mais comprido


def alinhar(caminho):
    """Devolve (texto_novo, n_linhas) ou (None, 0) se o ficheiro nao for tabular."""
    try:
        bruto = io.open(caminho, encoding="latin-1").read()
    except OSError:
        return None, 0
    linhas = bruto.replace(chr(13), "").split(chr(10))

    idx_cab = None
    for i, l in enumerate(linhas):
        if l.startswith("//"):
            idx_cab = i
            break
    if idx_cab is None:
        return None, 0                      # sem linha de colunas: nao e tabular

    # SEPARA COMO O SERVIDOR SEPARA. Ler por tabulacao so funcionava na primeira
    # passagem: depois de alinhado o ficheiro deixa de ter tabulacoes, e uma segunda
    # passagem via o cabecalho inteiro como uma coluna so e desistia em silencio.
    cab = [c.strip() for c in D._separar(linhas[idx_cab][2:])]
    if len(cab) < 2:
        return None, 0

    corpo = []
    for l in linhas[idx_cab + 1:]:
        if not l.strip():
            continue
        corpo.append([c.strip() for c in D._separar(l)])
    if not corpo:
        return None, 0

    n = len(cab)
    larguras = []
    for c in range(n):
        maior = len(cab[c])
        for cel in corpo:
            if c < len(cel):
                maior = max(maior, len(cel[c]))
        # o "//" do cabecalho ocupa duas casas na primeira coluna
        maior += 2 if c == 0 else 0
        larguras.append(min(maior + MARGEM, LARGURA_MAXIMA))

    def linha_de(campos, prefixo=""):
        partes = []
        for c in range(n):
            v = campos[c] if c < len(campos) else ""
            # o "//" entra DENTRO da primeira celula, antes do padding: somado por fora,
            # empurrava o cabecalho duas casas a frente das linhas de dados
            if c == 0 and prefixo:
                v = prefixo + v
            # UM valor nunca pode conter dois espacos seguidos: e isso que separa as
            # colunas depois. Colapsa-se aqui, na escrita, em vez de se descobrir mais
            # tarde que um nome partiu uma linha ao meio.
            while "  " in v:
                v = v.replace("  ", " ")
            # CELULA VAZIA LEVA MARCADOR, senao a coluna desaparece e as seguintes deslizam
            # uma casa. Sem isto, o Item.ini partiu-se todo em 27/08 -- a Bengala nao tem
            # `modelo`, e dai para a frente `slot` lia o valor de `classe_req`, o que fez o
            # servidor recusar equipar qualquer coisa. Ver `_separar` em data_files.py.
            if not v:
                v = D.VAZIO
            # DOIS ESPACOS SEMPRE, mesmo quando o valor passa a largura da coluna.
            #
            # `ljust` nao acrescenta nada quando o texto ja e maior que a largura
            # pedida, e ai o valor cola-se ao da coluna seguinte -- o separador
            # desaparece. Com LARGURA_MAXIMA a limitar, isso acontecia em quase
            # todas as linhas do Item.ini, que tem descricoes compridas.
            partes.append(v if c == n - 1
                          else (v.ljust(larguras[c]) if len(v) < larguras[c]
                                else v + "  "))
        return "".join(partes).rstrip()

    saida = linhas[:idx_cab]
    saida.append(linha_de(cab, "//"))
    for cel in corpo:
        saida.append(linha_de(cel))
    return CR.join(saida) + CR, len(corpo)


def main() -> int:
    aplicar = "--aplicar" in sys.argv
    filtro = next((a for a in sys.argv[1:] if not a.startswith("--")), None)

    alvos = sorted(glob.glob(os.path.join(DADOS, "**", "*.ini"), recursive=True))
    if filtro:
        alvos = [a for a in alvos if filtro.lower() in os.path.basename(a).lower()]

    feitos = saltados = 0
    for cam in alvos:
        novo, n = alinhar(cam)
        rel = os.path.relpath(cam, DADOS)
        if novo is None:
            saltados += 1
            continue
        if aplicar:
            io.open(cam, "w", encoding="latin-1", newline="").write(novo)
        feitos += 1
        if feitos <= 12 or filtro:
            print("   %-40s %6d linhas" % (rel, n))
    if feitos > 12 and not filtro:
        print("   ... e mais %d" % (feitos - 12))
    print()
    print("tabulares: %d   em formato de seccao (nao tocadas): %d" % (feitos, saltados))
    if not aplicar:
        print()
        print("nada gravado. rode com --aplicar")
    return 0


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