/*******************************************************************************
 * Copyright (C) 2026, Leo Galambos
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * 3. All advertising materials mentioning features or use of this software must
 *    display the following acknowledgement:
 *    This product includes software developed by the Egothor project.
 *
 * 4. Neither the name of the copyright holder nor the names of its contributors
 *    may be used to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ******************************************************************************/
package zeroecho.core.util;

import java.io.File;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import zeroecho.core.Capability;
import zeroecho.core.CryptoAlgorithm;
import zeroecho.core.CryptoAlgorithms;
import zeroecho.sdk.util.BouncyCastleActivator;

/**
 * Emits an HTML fragment with a table that summarizes discovered cryptographic
 * algorithms. The table includes subcolumns for algorithm families and key
 * usages found in capabilities.
 *
 * <h2>Columns</h2>
 * <ul>
 * <li>Algorithm ID</li>
 * <li>Display Name</li>
 * <li>Family:* (one column per family present)</li>
 * <li>Key usage:* (one column per role present)</li>
 * <li>Default spec</li>
 * </ul>
 *
 * <p>
 * Usage:
 * </p>
 * <pre>{@code
 *   java zeroecho.tools.GenerateCryptoCatalogTable /abs/path/to/table.html
 * }</pre>
 */
public final class GenerateCryptoCatalogTable {

    /**
     * Generates a standalone HTML fragment (no &lt;html&gt; or &lt;head&gt;) to the
     * given file.
     *
     * @param args the first argument must be the output file path for the generated
     *             fragment
     * @throws Exception if writing fails or discovery finds invalid providers
     */
    public static void main(String[] args) throws Exception { // NOPMD
        if (args.length != 1) { // NOPMD
            System.err.println("Expected 1 argument: output fragment path");
            System.exit(2);
        }

        BouncyCastleActivator.init();

        File out = new File(args[0]);
        out.getParentFile().mkdirs();

        Set<String> algorithmIds = CryptoAlgorithms.available();
        validate(algorithmIds);

        // Enumerate subcolumns actually present.
        SortedSet<String> families = new TreeSet<>();
        SortedSet<String> roles = new TreeSet<>();
        for (String algorithmId : algorithmIds) {
            CryptoAlgorithm a = CryptoAlgorithms.require(algorithmId);
            for (Capability c : a.listCapabilities()) {
                families.add(c.family().name());
                roles.add(c.role().name());
            }
        }

        try (Writer w = Files.newBufferedWriter(Paths.get(out.toURI()))) {
            // Optional minimal style and anchor for local TOC links.
            w.write("<!-- BEGIN: generated by GenerateCryptoCatalogTable -->\n");
            w.write("<p id=\"crypto-catalog-desc\" class=\"sr-only\">");
            w.write("Table of registered cryptographic algorithms with grouped columns for families and key usages.");
            w.write("</p>\n");

            // Open table
            w.write("<table class=\"zecat\" aria-describedby=\"crypto-catalog-desc\">");
            w.write("<caption>Registered cryptographic algorithms</caption>");

            // THEAD row 1: group headers
            w.write("<thead><tr>");
            w.write("<th scope=\"col\" rowspan=\"2\" class=\"id-col\">Algorithm ID</th>");
            w.write("<th scope=\"col\" rowspan=\"2\" class=\"display-col\">Display Name</th>");
            w.write("<th class=\"group\" colspan=\"" + families.size() + "\">Family</th>");
            w.write("<th class=\"group\" colspan=\"" + roles.size() + "\">Key usage</th>");
            w.write("<th scope=\"col\" rowspan=\"2\" class=\"default-col\">Default spec</th>");
            w.write("</tr>");

            // THEAD row 2: subheaders (vertical labels)
            w.write("<tr>");
            int fi = 0;
            for (String f : families) {
                String cls = "vcol narrow fam" + (fi == 0 ? " fam-first" : "");
                w.write("<th scope=\"col\" class=\"" + cls + "\"><span class=\"vlabel\">" + esc(f) + "</span></th>");
                fi++;
            }
            int ri = 0;
            for (String r : roles) {
                String cls = "vcol narrow usage" + (ri == 0 ? " usage-first" : "");
                w.write("<th scope=\"col\" class=\"" + cls + "\"><span class=\"vlabel\">" + esc(r) + "</span></th>");
                ri++;
            }
            w.write("</tr></thead>");

            // TBODY same as before, but make family/role cells narrow and centered
            w.write("<tbody>");
            for (String id : algorithmIds) {
                CryptoAlgorithm a = CryptoAlgorithms.require(id);

                Set<String> famHit = new HashSet<>(); // NOPMD
                Set<String> roleHit = new HashSet<>(); // NOPMD
                LinkedHashSet<String> defaultSpecs = new LinkedHashSet<>(); // NOPMD

                for (Capability c : a.listCapabilities()) {
                    famHit.add(c.family().name());
                    roleHit.add(c.role().name());
                    if (c.defaultSpec() != null) {
                        try {
                            Object ds = c.defaultSpec();
                            if (ds != null) {
                                defaultSpecs.add(esc(labelOf(ds)));
                            }
                        } catch (Exception ignore) { // NOPMD
                        }
                    }
                }

                w.write("<tr>");
                w.write("<td class=\"zetag id-td\"><code>" + esc(a.id()) + "</code></td>");
                w.write("<td class=\"display-td\">" + esc(a.displayName()) + "</td>");

                int i = 0;
                for (String f : families) {
                    String cls = "zecenter narrow fam-td" + (i == 0 ? " fam-first-td" : "");
                    w.write("<td class=\"" + cls + "\">" + (famHit.contains(f) ? "&#10003;" : "") + "</td>");
                    i++;
                }
                int j = 0;
                for (String r : roles) {
                    String cls = "zecenter narrow usage-td" + (j == 0 ? " usage-first-td" : "");
                    w.write("<td class=\"" + cls + "\">" + (roleHit.contains(r) ? "&#10003;" : "") + "</td>");
                    j++;
                }
                w.write("<td class=\"default-td\">" + String.join("<br>", defaultSpecs) + "</td>");
                w.write("</tr>");
            }
            w.write("</tbody></table>\n");
            w.write("<!-- END: generated by GenerateCryptoCatalogTable -->\n");
        }
    }

    private static void validate(Set<String> algorithmIds) {
        StringBuilder sb = null;
        for (String algorithmId : algorithmIds) {
            CryptoAlgorithm a = CryptoAlgorithms.require(algorithmId);
            boolean hasCaps = !a.listCapabilities().isEmpty();
            boolean hasKeyOperations = !a.keyOperations().isEmpty();
            if (!hasCaps && !hasKeyOperations) {
                if (sb == null) {
                    sb = new StringBuilder(); // NOPMD
                }
                sb.append("Algorithm ").append(a.id()).append(" has no capabilities nor key builders.\n");
            }
        }
        if (sb != null) {
            throw new IllegalStateException(sb.toString());
        }
    }

    private static String labelOf(Object o) {
        if (o == null) {
            return "null";
        }
        if (o instanceof zeroecho.core.annotation.Describable) {
            return ((zeroecho.core.annotation.Describable) o).description();
        }
        Class<?> c = (o instanceof Class<?>) ? (Class<?>) o : o.getClass();
        // Match CryptoCatalog.labelOf semantics as closely as possible.
        zeroecho.core.annotation.DisplayName dn = c.getAnnotation(zeroecho.core.annotation.DisplayName.class);
        if (dn != null) {
            return dn.value();
        }
        return c.getSimpleName();
    }

    private static String esc(String v) {
        if (v == null) {
            return "";
        }
        return v.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;");
    }

    private GenerateCryptoCatalogTable() {
        /* no instances */ }
}
