import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;

/**
 * Compressed General Category and Numeric Value: Compressor
 * <p>
 * Warranty & Liability
 * To the extent permitted by applicable law and unless explicitly
 * otherwise agreed upon, XLOG Technologies AG makes no warranties
 * regarding the provided information. XLOG Technologies AG assumes
 * no liability that any problems might be solved with the information
 * provided by XLOG Technologies AG.
 * <p>
 * Rights & License
 * All industrial property rights regarding the information - copyright
 * and patent rights in particular - are the sole property of XLOG
 * Technologies AG. If the company was not the originator of some
 * excerpts, XLOG Technologies AG has at least obtained the right to
 * reproduce, change and translate the information.
 * <p>
 * Reproduction is restricted to the whole unaltered document. Reproduction
 * of the information is only allowed for non-commercial uses. Selling,
 * giving away or letting of the execution of the library is prohibited.
 * The library can be distributed as part of your applications and libraries
 * for execution provided this comment remains unchanged.
 * <p>
 * Restrictions
 * Only to be distributed with programs that add significant and primary
 * functionality to the library. Not to be distributed with additional
 * software intended to replace any components of the library.
 * <p>
 * Trademarks
 * Jekejeke is a registered trademark of XLOG Technologies AG.
 */
public class Compress {
    public static int PARA_BITS = 3;
    public static int PARA_SIZE = 1 << PARA_BITS;
    public static int MAX_PARA = (Character.MAX_CODE_POINT + 1) / PARA_SIZE;
    public static int BLOCK_BITS = 6;
    public static int BLOCK_SIZE = 1 << BLOCK_BITS;
    public static int MAX_BLOCK = MAX_PARA / BLOCK_SIZE;

    public static ArrayList<String> pool = new ArrayList<>();
    public static ArrayList<String> pool2 = new ArrayList<>();
    public static StringBuilder buf3 = new StringBuilder();

    /**
     * Compress the general category and numeric value.
     */
    public static void compress() {
        pool.clear();
        pool2.clear();
        StringBuilder buf = new StringBuilder();
        StringBuilder buf2 = new StringBuilder();
        buf3.setLength(0);
        for (int k = 0; k < MAX_BLOCK; k++) {
            buf2.setLength(0);
            for (int i = 0; i < BLOCK_SIZE; i++) {
                buf.setLength(0);
                for (int j = 0; j < PARA_SIZE; j++) {
                    int ch = (k * BLOCK_SIZE + i) * PARA_SIZE + j;
                    int type = Character.getType(ch);
                    int val = Character.digit(ch, 36);
                    buf.append((char) (((val + 1) << 5) + type));
                }
                String res = buf.toString();
                int index = register(pool, res);
                buf2.append((char) index);
            }
            String res = buf2.toString();
            int index = register(pool2, res);
            buf3.append((char) index);
        }
    }

    /**
     * Deduplicate an array of usigned 16 bit values.
     *
     * @param pool The pool.
     * @param key  The array.
     * @return The index.
     */
    private static int register(ArrayList<String> pool, String key) {
        int index = pool.indexOf(key);
        if (index == -1) {
            index = pool.size();
            pool.add(key);
        }
        return index;
    }

    public static int LANG_DEFAULT = 0;
    public static int LANG_CSHARP = 1;
    public static int LANG_PYTHON = 2;

    /**
     * Print an array of unsigned 16 bit values.
     *
     * @param str The unsigned 16 bit values.
     */
    public static void show(String str, Writer wr, int lang) throws IOException {
        if (lang == LANG_CSHARP) {
            wr.write("new short[] {");
        } else {
            wr.write('[');
        }
        for (int i = 0; i < str.length(); i++) {
            if (i != 0)
                wr.write(", ");
            if (i != 0 && i % 16 == 0) {
                wr.write("\n");
                wr.write("    ");
            }
            wr.write(Integer.toString(str.charAt(i)));
        }
        if (lang == LANG_CSHARP) {
            wr.write('}');
        } else {
            wr.write(']');
        }
    }

    public static void show(String str, Writer wr) throws IOException {
        show(str, wr, LANG_DEFAULT);
    }

    /**
     * <p>Retrieve the specification version.</p>
     *
     * @return The specification version.
     */
    public static double getVersion() {
        String vers = System.getProperties().getProperty("java.specification.version");
        return Double.parseDouble(vers);
    }

    public static void statistics(long ts, Writer wr, int lang) throws IOException {
        long ts2 = System.currentTimeMillis();
        String lc;
        if (lang == LANG_PYTHON) {
            wr.write("###\n");
            lc = "# ";
        } else {
            wr.write("/**\n");
            lc = " * ";
        }
        wr.write(lc + "Java Specification Version " + Compress.getVersion() + "\n");
        wr.write(lc + "\n");
        int alpha = Compress.pool.size() * Compress.PARA_SIZE;
        int beta = Compress.pool2.size() * Compress.BLOCK_SIZE;
        int gamma = Compress.MAX_BLOCK;
        wr.write(lc + "pool.size()*PARA_SIZE=   " + alpha + "\n");
        wr.write(lc + "pool2.size()*BLOCK_SIZE= " + beta + "\n");
        wr.write(lc + "MAX_BLOCK=               " + gamma + "\n");
        wr.write(lc + "Total=                   " + (alpha + beta + gamma) + "\n");
        if (lang == LANG_PYTHON) {
            wr.write("##\n");
        } else {
            wr.write(" */\n");
        }
        wr.write("\n");
        System.out.println("Compression took " + (ts2 - ts) + " ms");
    }

    public static void statistics(long ts, Writer wr) throws IOException {
        statistics(ts, wr, LANG_DEFAULT);
    }

}