
package org.marc4j.converter.impl;

import java.io.PrintStream;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.Vector;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;

/**
 * Invoked at build time to generate a java source file (named ReverseCodeTableGenerated.java for MARC-8, and
 * UnimarcReverseCodeTableGenerated.java for UNIMARC) which when compiled will
 * extend the ReverseCodeTable abstract class (primarily through switch statements) and which can be used by the
 * UnicodeToAnsel or UnicodeToUnimarc converter which will produce the same results as the object ReverseCodeTableHash.
 *
 * The following routines are only used in the code generation process, and are not available to be called from within
 * an application that uses MARC4J.
 *
 * The routines generated for converting unicode characters to MARC8 or UNIMARC multibyte characters are split into several
 * routines to workaround a limitation in java that a method can only contain 64k of code when it is compiled.
 *
 * @author Robert Haschart
 *
 */
public class ReverseCodeTableGenerator {

    private ReverseCodeTableGenerator() {
    }

    /**
     * The main class for the reverse code table generator.
     *
     * @param args - the command line arguments for the ReverseCodeTableGenerator program
     */
    public static void main(final String[] args) {
        if (args.length != 1) {
            throw new IllegalArgumentException("ReverseCodeTableGenerator must be called with an argument, either MARC8 or UNIMARC");
        }
        String type = args[0];
        String resource;
        if ("MARC8".equals(type)) {
            resource = "resources/codetables.xml";
        } else if ("UNIMARC".equals(type)) {
            resource = "resources/unimarc.xml";
        } else {
            throw new IllegalArgumentException("ReverseCodeTableGenerator argument must be MARC8 or UNIMARC");
        }

        Hashtable<Character, Hashtable<Integer, char[]>> charsets = null;

        try {
            final SAXParserFactory factory = SAXParserFactory.newInstance();
            factory.setNamespaceAware(true);
            factory.setValidating(false);
            final SAXParser saxParser = factory.newSAXParser();
            final XMLReader rdr = saxParser.getXMLReader();

            final InputSource src = new InputSource(ReverseCodeTableHandler.class
                    .getResourceAsStream(resource));

            final ReverseCodeTableHandler saxUms = new ReverseCodeTableHandler();

            rdr.setContentHandler(saxUms);
            rdr.parse(src);
            charsets = saxUms.getCharSets();
            final Vector<Character> combining = saxUms.getCombiningChars();
            final Object charsetsKeys[] = charsets.keySet().toArray();
            Arrays.sort(charsetsKeys);

            dumpTablesAsSwitchStatement(type, combining, charsets, System.out);

        } catch (final Exception details) {
            details.printStackTrace(System.out);
            System.err.println("Exception: " + details);
        }
    }

    private static void dumpTablesAsSwitchStatement(final String type, final Vector<Character> combining,
            final Hashtable<Character, Hashtable<Integer, char[]>> charsets, final PrintStream out) {
        out.println("package org.marc4j.converter.impl;");
        out.println("");
        out.println("import java.util.Hashtable;");
        out.println("");
        out.println("/**");
        out.println(" * An implementation of ReverseCodeTable that is used in converting Unicode");
        out.println(" * data to " + type + " data, that doesn't rely on any data files or resources or");
        out.println(" * data structures");
        out.println(" *");
        out.println(" * Warning: This file is generated by running the main routine in the file");
        out.println(" * ReverseCodeTableHandler.java");
        out.println(" *");
        out.println(" * Warning: Do not edit this file, or all edits will be lost at the next");
        out.println(" * build.");
        out.println(" */");
        out.println("");
        if ("MARC8".equals(type)) {
            out.println("public class ReverseCodeTableGenerated extends ReverseCodeTable {");
        } else {
            out.println("public class UnimarcReverseCodeTableGenerated extends ReverseCodeTable {");
        }
        out.println("");
        out.println("\tpublic boolean isCombining(Character c) {");
        out.println("\t\tswitch ((int)c.charValue()) {");

        final Character combineArray[] = combining.toArray(new Character[0]);
        Arrays.sort(combineArray);
        Character prevc = null;

        for (int i = 0; i < combineArray.length; i++) {
            final Character c = combineArray[i];
            if (!c.equals(prevc)) {
                out.println("\t\t\tcase 0x" + Integer.toHexString(c.charValue()) + ":");
            }
            prevc = c;
        }

        out.println("\t\t\t\treturn true;");
        out.println("\t\t\tdefault: return false;");
        out.println("\t\t}");
        out.println("\t}");
        out.println("");
        out.println("\tpublic Hashtable<Integer, char[]> getCharTable(Character c) {");
        out.println("\t\tString resultStr1 = getCharTableCharSet(c);");
        out.println("\t\tString resultStr2 = getCharTableCharString(c);");
        out.println("\t\tif (resultStr2 == null)  return(null);");
        out.println("\t\tHashtable<Integer, char[]> result = new Hashtable<Integer, char[]>(resultStr1.length());");
        out.println("\t\tString res2[] = resultStr2.split(\" \");");
        out.println("\t\tfor (int i = 0; i < resultStr1.length(); i++) {");
        out.println("\t\t\tresult.put(new Integer(resultStr1.charAt(i)), deHexify(res2[(res2.length==1) ? 0 : i]));");
        out.println("\t\t}");
        out.println("\t\treturn(result);");
        out.println("\t}");
        out.println("");

        final Character charsetsKeys[] = charsets.keySet().toArray(new Character[0]);
        Arrays.sort(charsetsKeys);
        final StringBuffer buffer = new StringBuffer();

        out.println("\tprivate String getCharTableCharSet(Character c)");
        out.println("\t{");
        out.println("\t\tint cVal = (int)c.charValue();");
        out.println("\t\tswitch(cVal) {");

        for (int sel = 0; sel < charsetsKeys.length; sel++) {
            final Hashtable<Integer, char[]> table = charsets.get(charsetsKeys[sel]);
            final Object tableKeys[] = table.keySet().toArray();
            Arrays.sort(tableKeys);
            final StringBuffer sb = new StringBuffer();

            for (int i = 0; i < tableKeys.length; i++) {
                sb.append((char) ((Integer) tableKeys[i]).intValue());
            }
            final String charset = sb.toString().trim();

            if (!charset.equals("1")) {
                out.println("\t\t\tcase 0x" + Integer.toHexString(charsetsKeys[sel].charValue()) + ": return \"" + charset + "\";");
            }
        }
        out.println("\t\t}");
        out.println("\t\treturn \"1\";");
        out.println("\t}");

        //  Break charset into pieces to keep below the 64K max size per Java method.
        int start = 0;
        while (charsetsKeys.length - start > 0) {
            int endOffset = start + 3500;
            if (charsetsKeys.length < endOffset) {
                endOffset = charsetsKeys.length;
            }
            dumpPartialCharTableCharString(out, buffer, charsetsKeys, charsets, start, endOffset);
            start += 3500;
        }

        out.println("\tprivate String getCharTableCharString(Character c)");
        out.println("\t{");
        out.println("\t\tint cVal = (int)c.charValue();");
        out.println(buffer.toString());
        out.println("\t\treturn null;");
        out.println("\t}");
        out.println("}");

    }

    static private void dumpPartialCharTableCharString(final PrintStream out, final StringBuffer buffer,
            final Object charsetsKeys[], final Hashtable<Character, Hashtable<Integer, char[]>> charsets,
            final int startOffset, final int endOffset) {
        final String startByteStr = "0x" + Integer.toHexString(((Character) charsetsKeys[startOffset]).charValue());
        final String endByteStr = "0x" + Integer.toHexString(((Character) charsetsKeys[endOffset - 1]).charValue());
        buffer.append("\t\tif (cVal >= " + startByteStr + " && cVal <= " + endByteStr + ")  return (getCharTableCharString_" + startByteStr + "_" + endByteStr + "(c));\n");

        out.println("\tprivate String getCharTableCharString_" + startByteStr + "_" + endByteStr + "(Character c) {");
        out.println("\t\tswitch((int)c.charValue()) {");

        for (int sel = startOffset; sel < charsetsKeys.length && sel < endOffset; sel++) {
            final Hashtable<Integer, char[]> table = charsets.get(charsetsKeys[sel]);
            final Object tableKeys[] = table.keySet().toArray();
            Arrays.sort(tableKeys);
            final StringBuffer sb1 = new StringBuffer();
            final StringBuffer sb2 = new StringBuffer();
            boolean useSB1 = false;
            char prevcharArray[] = null;

            for (int i = 0; i < tableKeys.length; i++) {
                final Object value = table.get(tableKeys[i]);
                final char valarray[] = (char[]) value;
                sb1.append(hexify(valarray));

                if (i == 0) {
                    sb2.append(hexify(valarray));
                }

                if (i > 0 && valarray.length == 1 && prevcharArray != null && prevcharArray.length == 1 && valarray[0] != prevcharArray[0]) {
                    useSB1 = true;
                }
                sb1.append(" ");
                prevcharArray = valarray;
            }
            final String returnVal = useSB1 ? sb1.toString().trim() : sb2.toString().trim();
            out.println("\t\t\tcase 0x" + Integer.toHexString(((Character) charsetsKeys[sel])
                    .charValue()) + ": return \"" + returnVal + "\";");
        }

        out.println("\t\t\tdefault: return null;");
        out.println("\t\t}");
        out.println("\t}");
        out.println("");
    }

    /**
     * Utility function for translating an array of characters to a two character hex string of the character values.
     *
     * @param aValArray The array of characters to encode
     * @return A string representation of the hex code
     */
    private static String hexify(final char[] valarray) {
        String result = "";

        for (int i = 0; i < valarray.length; i++) {
            result += Integer.toHexString(valarray[i]);
        }

        return result;
    }

}
