/*
 * Copyright 2009 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */

package build.tools.charsetmapping;
import java.io.*;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Scanner;
import java.util.Formatter;
import java.util.regex.*;
import java.nio.charset.*;
import static build.tools.charsetmapping.CharsetMapping.*;

public class GenerateDBCS {
    // pattern used by this class to read in mapping table
    static Pattern mPattern = Pattern.compile("(?:0x)?(\\p{XDigit}++)\\s++(?:0x)?(\\p{XDigit}++)(?:\\s++#.*)?");
    public static void genDBCS(String args[]) throws Exception {

        Scanner s = new Scanner(new File(args[0], args[2]));
        while (s.hasNextLine()) {
            String line = s.nextLine();
            if (line.startsWith("#") || line.length() == 0)
                continue;
            String[] fields = line.split("\\s+");
            if (fields.length < 10) {
                System.err.println("Misconfiged sbcs line <" + line + ">?");
                continue;
            }
            String clzName = fields[0];
            String csName  = fields[1];
            String hisName = ("null".equals(fields[2]))?null:fields[2];
            String type = fields[3].toUpperCase();
            if ("BASIC".equals(type))
                type = "";
            else
                type = "_" + type;
            String pkgName  = fields[4];
            boolean isASCII = Boolean.valueOf(fields[5]);
            int    b1Min = toInteger(fields[6]);
            int    b1Max = toInteger(fields[7]);
            int    b2Min    = toInteger(fields[8]);
            int    b2Max    = toInteger(fields[9]);
            System.out.printf("%s,%s,%s,%b,%s%n", clzName, csName, hisName, isASCII, pkgName);
            genClass(args[0], args[1], "DoubleByte-X.java.template",
                    clzName, csName, hisName, pkgName,
                    isASCII, type,
                    b1Min, b1Max, b2Min, b2Max);
        }
    }

    private static int toInteger(String s) {
        if (s.startsWith("0x") || s.startsWith("0X"))
            return Integer.valueOf(s.substring(2), 16);
        else
            return Integer.valueOf(s);
    }

    private static void outString(Formatter out,
                                  char[] cc, int off, int end,
                                  String closure)
    {
        while (off < end) {
            out.format("        \"");
            for (int j = 0; j < 8; j++) {
                if (off == end)
                    break;
                char c = cc[off++];
                switch (c) {
                case '\b':
                    out.format("\\b"); break;
                case '\t':
                    out.format("\\t"); break;
                case '\n':
                    out.format("\\n"); break;
                case '\f':
                    out.format("\\f"); break;
                case '\r':
                    out.format("\\r"); break;
                case '\"':
                    out.format("\\\""); break;
                case '\'':
                    out.format("\\'"); break;
                case '\\':
                    out.format("\\\\"); break;
                default:
                    out.format("\\u%04X", c & 0xffff);
                }
            }
            if (off == end)
                out.format("\" %s%n", closure);
            else
                out.format("\" + %n");
        }
    }

    private static void outString(Formatter out,
                                  char[] db,
                                  int b1,
                                  int b2Min, int b2Max,
                                  String closure)
    {
        char[] cc = new char[b2Max - b2Min + 1];
        int off = 0;
        for (int b2 = b2Min; b2 <= b2Max; b2++) {
            cc[off++] = db[(b1 << 8) | b2];
        }
        outString(out, cc, 0, cc.length, closure);
    }

    private static void genClass(String srcDir, String dstDir, String template,
                                 String clzName,
                                 String csName,
                                 String hisName,
                                 String pkgName,
                                 boolean isASCII,
                                 String type,
                                 int b1Min, int b1Max,
                                 int b2Min, int b2Max)
        throws Exception
    {

        StringBuilder b2cSB = new StringBuilder();
        StringBuilder b2cNRSB = new StringBuilder();
        StringBuilder c2bNRSB = new StringBuilder();

        char[] db = new char[0x10000];
        char[] c2bIndex = new char[0x100];
        int c2bOff = 0x100;    // first 0x100 for unmappable segs

        Arrays.fill(db, UNMAPPABLE_DECODING);
        Arrays.fill(c2bIndex, UNMAPPABLE_DECODING);

        char[] b2cIndex = new char[0x100];
        Arrays.fill(b2cIndex, UNMAPPABLE_DECODING);

        // (1)read in .map to parse all b->c entries
        FileInputStream in = new FileInputStream(new File(srcDir, clzName + ".map"));
        Parser p = new Parser(in, mPattern);
        Entry  e = null;
        while ((e = p.next()) != null) {
            db[e.bs] = (char)e.cp;

            if (e.bs > 0x100 &&    // db
                b2cIndex[e.bs>>8] == UNMAPPABLE_DECODING) {
                b2cIndex[e.bs>>8] = 1;
            }

            if (c2bIndex[e.cp>>8] == UNMAPPABLE_DECODING) {
                c2bOff += 0x100;
                c2bIndex[e.cp>>8] = 1;
            }
        }
        Formatter fm = new Formatter(b2cSB);
        fm.format("%n    static final String b2cSBStr =%n");
        outString(fm, db, 0x00, 0x100,  ";");

        fm.format("%n        static final String[] b2cStr = {%n");
        for (int i = 0; i < 0x100; i++) {
            if (b2cIndex[i] == UNMAPPABLE_DECODING) {
                fm.format("            null,%n");  //unmappable segments
            } else {
                outString(fm, db, i, b2Min, b2Max, ",");
            }
        }

        fm.format("        };%n");
        fm.close();

        // (2)now parse the .nr file which includes "b->c" non-roundtrip entries
        File f = new File(srcDir, clzName + ".nr");
        if (f.exists()) {
            StringBuilder sb = new StringBuilder();
            in = new FileInputStream(f);
            p = new Parser(in, mPattern);
            e = null;
            while ((e = p.next()) != null) {
                // A <b,c> pair
                sb.append((char)e.bs);
                sb.append((char)e.cp);
            }
            char[] nr = sb.toString().toCharArray();
            fm = new Formatter(b2cNRSB);
            fm.format("String b2cNR =%n");
            outString(fm, nr, 0, nr.length,  ";");
            fm.close();
        } else {
            b2cNRSB.append("String b2cNR = null;");
        }

        // (3)finally the .c2b file which includes c->b non-roundtrip entries
        f = new File(srcDir, clzName + ".c2b");
        if (f.exists()) {
            StringBuilder sb = new StringBuilder();
            in = new FileInputStream(f);
            p = new Parser(in, mPattern);
            e = null;
            while ((e = p.next()) != null) {
                // A <b,c> pair
                if (c2bIndex[e.cp>>8] == UNMAPPABLE_DECODING) {
                    c2bOff += 0x100;
                    c2bIndex[e.cp>>8] = 1;
                }
                sb.append((char)e.bs);
                sb.append((char)e.cp);
            }
            char[] nr = sb.toString().toCharArray();
            fm = new Formatter(c2bNRSB);
            fm.format("String c2bNR =%n");
            outString(fm, nr, 0, nr.length,  ";");
            fm.close();
        } else {
            c2bNRSB.append("String c2bNR = null;");
        }

        // (4)it's time to generate the source file
        String b2c = b2cSB.toString();
        String b2cNR = b2cNRSB.toString();
        String c2bNR = c2bNRSB.toString();

        Scanner s = new Scanner(new File(srcDir, template));
        PrintStream out = new PrintStream(new FileOutputStream(
                              new File(dstDir, clzName + ".java")));
        if (hisName == null)
            hisName = "";

        while (s.hasNextLine()) {
            String line = s.nextLine();
            if (line.indexOf("$") == -1) {
                out.println(line);
                continue;
            }
            line = line.replace("$PACKAGE$" , pkgName)
                       .replace("$IMPLEMENTS$", (hisName == null)?
                                "" : "implements HistoricallyNamedCharset")
                       .replace("$NAME_CLZ$", clzName)
                       .replace("$NAME_ALIASES$",
                                "sun.nio.cs".equals(pkgName) ?
                                "StandardCharsets.aliases_" + clzName :
                                "ExtendedCharsets.aliasesFor(\"" + csName + "\")")
                       .replace("$NAME_CS$" , csName)
                       .replace("$CONTAINS$",
                                "MS932".equals(clzName)?
                                "return ((cs.name().equals(\"US-ASCII\")) || (cs instanceof JIS_X_0201) || (cs instanceof " + clzName + "));":
                                (isASCII ?
                                 "return ((cs.name().equals(\"US-ASCII\")) || (cs instanceof " + clzName + "));":
                                 "return (cs instanceof " + clzName + ");"))
                       .replace("$HISTORICALNAME$",
                                (hisName == null)? "" :
                                "    public String historicalName() { return \"" + hisName + "\"; }")
                       .replace("$DECTYPE$", type)
                       .replace("$ENCTYPE$", type)
                       .replace("$B1MIN$"   , "0x" + Integer.toString(b1Min, 16))
                       .replace("$B1MAX$"   , "0x" + Integer.toString(b1Max, 16))
                       .replace("$B2MIN$"   , "0x" + Integer.toString(b2Min, 16))
                       .replace("$B2MAX$"   , "0x" + Integer.toString(b2Max, 16))
                       .replace("$B2C$", b2c)
                       .replace("$C2BLENGTH$", "0x" + Integer.toString(c2bOff, 16))
                       .replace("$NONROUNDTRIP_B2C$", b2cNR)
                       .replace("$NONROUNDTRIP_C2B$", c2bNR);

            out.println(line);
        }
        out.close();
    }
}
