import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;

/**
 * Externe toets van de tokenizer tegen een referentiebestand &mdash; de qwen3.8-variant
 * van {@code TokenizerVsOllama}.
 *
 * <p>Voor qwen2.5 was ollama de onafhankelijke referentie, maar die vergelijking kan
 * alleen met een model dat ollama ook echt kan dr&aacute;&aacute;ien. Voor een tokenizer
 * alleen is dat niet nodig: de offici&euml;le {@code tokenizers}-bibliotheek van
 * HuggingFace leest hetzelfde {@code tokenizer.json} en is een volledig onafhankelijke
 * implementatie. Een klein hulpscript codeert daar een corpus mee en schrijft de
 * uitkomsten als TSV; dit programma codeert hetzelfde corpus met &oacute;nze tokenizer
 * en eist dat elke tokenreeks <em>volledig</em> gelijk is &mdash; strenger dan de
 * ollama-toets, die alleen aantallen en grenzen kon vergelijken.
 *
 * <p>Het TSV-formaat: per regel {@code base64(tekst) TAB id,id,id}. Base64 houdt
 * regeleindes en rare tekens in de teksten buiten het bestandsformaat.
 *
 * <pre>java src/TokenizerVsReferentie.java tokenizer.gguf referentie.tsv</pre>
 */
public final class TokenizerVsReferentie {

    public static void main(String[] args) throws Exception {
        if (args.length != 2) {
            System.err.println("gebruik: java src/TokenizerVsReferentie.java <model.gguf> <referentie.tsv>");
            System.exit(2);
        }

        try (Gguf g = Gguf.open(Path.of(args[0]))) {
            Tokenizer tk = new Tokenizer(g);
            List<String> regels = Files.readAllLines(Path.of(args[1]), StandardCharsets.UTF_8);

            System.out.println("tokenizer  : " + tk.preTokenizerName() + ", "
                    + String.format("%,d", tk.vocabSize()) + " tokens");
            System.out.println("referentie : " + args[1] + " (" + regels.size() + " regels)");
            System.out.println();

            int gelijk = 0, anders = 0, getoond = 0, regelNr = 0;
            for (String regel : regels) {
                regelNr++;
                // let op: isEmpty, niet isBlank — de lege toetstekst is een regel
                // met alleen een tab, en die hoort gewoon meegetoetst te worden
                if (regel.isEmpty() || regel.startsWith("#")) continue;
                int tab = regel.indexOf('\t');
                if (tab < 0) {
                    System.err.println("regel " + regelNr + " bevat geen tab; is dit wel een referentie-TSV?");
                    System.exit(2);
                }
                String tekst;
                int[] verwacht;
                try {
                    tekst = new String(Base64.getDecoder().decode(regel.substring(0, tab)),
                            StandardCharsets.UTF_8);
                    verwacht = regel.length() == tab + 1 ? new int[0]
                            : Arrays.stream(regel.substring(tab + 1).split(","))
                                    .mapToInt(Integer::parseInt).toArray();
                } catch (IllegalArgumentException e) {   // kapotte base64 of niet-numerieke ids
                    System.err.println("regel " + regelNr + " is onleesbaar: " + e.getMessage());
                    System.exit(2);
                    return;
                }

                int[] onze = tk.encode(tekst, true);
                if (Arrays.equals(onze, verwacht)) {
                    gelijk++;
                } else {
                    anders++;
                    if (getoond++ < 5) {
                        System.out.println("  [!=] " + toon(tekst));
                        System.out.println("       wij : " + Arrays.toString(onze));
                        System.out.println("       ref : " + Arrays.toString(verwacht));
                    }
                }
            }

            System.out.println("== RESULTAAT ==");
            System.out.printf("  volledig identieke tokenreeksen  %d van %d%n", gelijk, gelijk + anders);
            if (anders == 0) {
                System.out.println("\nGESLAAGD - elke tokenreeks is gelijk aan de referentie-implementatie");
            } else {
                System.out.println("\nMISLUKT - onderzoek de afwijkingen hierboven");
                System.exit(1);
            }
        }
    }

    static String toon(String s) {
        String t = s.replace("\n", "\\n").replace("\t", "\\t");
        return '"' + (t.length() > 60 ? t.substring(0, 57) + "..." : t) + '"';
    }
}
