package org.unicode.cldr.util;

import java.io.PrintWriter;
import java.util.BitSet;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import org.unicode.cldr.util.SupplementalDataInfo.PluralInfo;

import com.ibm.icu.dev.util.CollectionUtilities;
import com.ibm.icu.dev.util.Relation;
import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.text.PluralRules;
import com.ibm.icu.text.UnicodeSet;
import com.ibm.icu.util.ULocale;

public class PluralSnapshot implements Comparable<PluralSnapshot> {

    public enum Plurals {
        zero, one, two, few, many, other("x");
        final String abb;

        Plurals(String s) {
            abb = s;
        }

        Plurals() {
            abb = name().substring(0, 1);
        }

        public String abbreviated() {
            return abb;
        }
    }

    public enum Integral {
        integer, fraction
    }

    static final int LEN = 128;

    static Set<Double> zeroOne = new TreeSet<Double>();
    static {
        zeroOne.add(0.0d);
        zeroOne.add(1.0d);
    }

    public final int count;
    public final int count01;

    EnumSet<Plurals> coveredBy01 = EnumSet.noneOf(Plurals.class);
    EnumSet<Plurals> not01 = EnumSet.noneOf(Plurals.class);

    Plurals[] plurals = new Plurals[LEN];

    private Integral integral;

    private BitSet pluralsTransitionAt;

    static NumberFormat nf = NumberFormat.getInstance(ULocale.ENGLISH);

    public static class SnapshotInfo implements Iterable<Entry<PluralSnapshot, Set<String>>> {
        // private Relation<String,String> rulesToLocales = Relation.of(new HashMap<String,Set<String>>(),
        // TreeSet.class);
        private Relation<PluralSnapshot, String> snapshotToLocales = Relation.of(
            new TreeMap<PluralSnapshot, Set<String>>(), TreeSet.class);
        private BitSet pluralsTransitionAt = new BitSet();
        private Integral integral;

        private SnapshotInfo(Integral integral) {
            this.integral = integral;
            SupplementalDataInfo supplementalDataInfo = SupplementalDataInfo.getInstance();
            Map<String, PluralSnapshot> rulesToSnapshot = new HashMap<String, PluralSnapshot>();
            for (String locale : supplementalDataInfo.getPluralLocales()) {
                PluralInfo plurals = supplementalDataInfo.getPlurals(locale);
                String rules = plurals.getRules();
                PluralSnapshot snap = rulesToSnapshot.get(rules);
                if (snap == null) {
                    PluralRules pluralRules = PluralRules.createRules(rules);
                    snap = new PluralSnapshot(pluralRules, integral, pluralsTransitionAt);
                    rulesToSnapshot.put(rules, snap);
                }
                snapshotToLocales.put(snap, locale);
            }
        }

        public Iterator<Entry<PluralSnapshot, Set<String>>> iterator() {
            return snapshotToLocales.keyValuesSet().iterator();
        }

        public String toOverview() {
            StringBuilder result = new StringBuilder();
            result.append("Transitions:\t 0");
            for (int i = pluralsTransitionAt.nextSetBit(0); i >= 0; i = pluralsTransitionAt.nextSetBit(i + 1)) {
                result.append(",").append(i);
            }
            return result.toString();
        }

        public String toHtmlStringHeader() {
            StringBuilder result = new StringBuilder();
            result.append("<tr><th class='h'></th>");
            int next = -2;
            for (int i = pluralsTransitionAt.nextSetBit(0); i >= 0; i = next) {
                next = pluralsTransitionAt.nextSetBit(i + 1);
                result.append("<th class='h'>").append(i);
                if (integral == Integral.fraction) {
                    result.append(".x");
                }
                int vnext = next == -1 ? LEN : next;
                if (vnext > i + 1) {
                    result.append("-").append(String.valueOf(vnext - 1)
                        + (integral == Integral.fraction ? ".x" : ""));
                }
                result.append("</th>");
            }
            result.append("</tr>");
            return result.toString();
        }
    }

    private static SnapshotInfo[] SINGLETON = new SnapshotInfo[2];
    private EnumSet<Plurals> found;

    public static SnapshotInfo getInstance(Integral integral) {
        int item = integral.ordinal();
        if (SINGLETON[item] == null) {
            SINGLETON[item] = new SnapshotInfo(integral);
        }
        return SINGLETON[item];
    }

    PluralSnapshot(PluralRules pluralRules, Integral integral, BitSet pluralsTransitionAt) {
        this.pluralsTransitionAt = pluralsTransitionAt;
        this.integral = integral;
        double offset = integral == Integral.integer ? 0 : 0.5;
        found = EnumSet.noneOf(Plurals.class);
        not01 = EnumSet.noneOf(Plurals.class);
        pluralsTransitionAt.set(0);
        for (int i = 0; i < plurals.length; ++i) {
            final double probe = i + offset;
            final Plurals plural = Plurals.valueOf(pluralRules.select(probe));
            plurals[i] = plural;
            found.add(plural);
            if (probe != 0.0d && probe != 1.0d) {
                not01.add(plural);
            }
            if (i > 0 && plural != plurals[i - 1]) {
                pluralsTransitionAt.set(i);
            }
        }
        coveredBy01.addAll(found);
        coveredBy01.removeAll(not01);
        count = found.size();
        count01 = 2 + not01.size();
    }

    @Override
    public int compareTo(PluralSnapshot other) {
        int diff = count - other.count;
        if (diff != 0) return diff;
        diff = UnicodeSet.compare(found, other.found);
        if (diff != 0) return diff;
        Iterator<Plurals> it = other.not01.iterator();
        for (Plurals p : not01) { // same length, so ok
            Plurals otherOne = it.next();
            diff = p.compareTo(otherOne);
            if (diff != 0) return diff;
        }
        for (int i = 0; i < plurals.length; ++i) {
            diff = plurals[i].compareTo(other.plurals[i]);
            if (diff != 0) return diff;
        }
        return 0;
    }

    public boolean equals(Object other) {
        return compareTo((PluralSnapshot) other) == 0;
    }

    public int hashCode() {
        return count; // brain dead but we don't care.
    }

    public String toString() {
        StringBuilder result = new StringBuilder();
        result.append("Plurals: 0, 1, ").append(CollectionUtilities.join(not01, ", "));
        if (coveredBy01.size() != 0) {
            result.append("\nCovered by {0,1}:\t").append(coveredBy01);
        }
        result.append("\nInt:\t");
        appendItems(result, plurals, 0);
        return result.toString();
    }

    public String toHtmlString() {
        StringBuilder result = new StringBuilder();
        result.append("<tr>");

        Plurals lastItem = null;
        int colSpan = 0;
        for (int i = pluralsTransitionAt.nextSetBit(0); i >= 0; i = pluralsTransitionAt.nextSetBit(i + 1)) {
            Plurals item = plurals[i];
            if (item == lastItem) {
                colSpan += 1;
                continue;
            }
            if (lastItem != null) {
                appendCell(result, colSpan, lastItem);
                colSpan = 0;
            }
            colSpan += 1;
            lastItem = item;
        }
        appendCell(result, colSpan, lastItem);
        result.append("</tr>");
        return result.toString();
    }

    private void appendCell(StringBuilder result, int colSpan, Plurals item) {
        result.append("<td class='").append(item.abbreviated());
        if (coveredBy01.contains(item)) {
            result.append(" c01");
        }
        result.append("'");
        if (colSpan != 1) {
            result.append(" colSpan='" + colSpan + "'");
        }
        result.append(" title='").append(item.toString()).append("'>")
            .append(item.abbreviated()).append("</td>");
    }

    private static <T> void appendItems(StringBuilder result, T[] plurals3, double offset) {
        int start = 0;
        result.append(plurals3[0]).append("=").append(nf.format(start + offset));
        for (int i = 1; i < plurals3.length; ++i) {
            if (!plurals3[i].equals(plurals3[i - 1])) {
                if (i - 1 != start) {
                    result.append("-").append(nf.format(i - 1 + offset));
                }
                result.append("; ").append(plurals3[i]).append("=").append(nf.format(i + offset));
                start = i;
            }
        }
        if (plurals3.length - 1 != start) {
            result.append("-").append(nf.format(plurals3.length - 1 + offset));
        }
    }

    public static String getDefaultStyles() {
        return "<style type='text/css'>\n"
            +
            "td.l, td.z, td.o, td.t, td.f, td.m, td.x, th.h, table.pluralComp {border: 1px solid #666; font-size: 8pt}\n"
            +
            "table.pluralComp {border-collapse:collapse}\n" +
            "th.h {background-color:#EEE; border-top: 2px solid #000; border-bottom: 2px solid #000;}\n" +
            "td.l {background-color:#C0C; border-top: 2px solid #000; color:white; font-weight: bold}\n" +
            "td.z {background-color:#F00}\n" +
            "td.o {background-color:#DD0}\n" +
            "td.t {background-color:#0F0}\n" +
            "td.f {background-color:#0DD}\n" +
            "td.m {background-color:#99F}\n" +
            "td.x {background-color:#CCC}\n" +
            "td.c01 {text-decoration:underline}\n";
    }

    public static void writeTables(CLDRFile english, PrintWriter out) {
        for (Integral integral : Integral.values()) {
            SnapshotInfo info = PluralSnapshot.getInstance(integral);

            System.out.println("\n" + integral + "\n");
            System.out.println(info.toOverview());

            out.println("<h3>" + integral + "</h3>");
            out.println("<table class='pluralComp'>");
            int lastCount = -1;
            int lastCount01 = -1;

            out.println(info.toHtmlStringHeader());

            for (Entry<PluralSnapshot, Set<String>> ruleEntry : info) {
                PluralSnapshot ss = ruleEntry.getKey();
                Set<String> locales = ruleEntry.getValue();
                System.out.println();
                System.out.println(locales);
                System.out.println(ss);
                // if (ss.count != lastCount) {
                // out.println(info.toHtmlStringHeader());
                // lastCount = ss.count;
                // lastCount01 = ss.count01;
                // }
                Map<String, String> fullLocales = new TreeMap<String, String>();
                for (String localeId : locales) {
                    String name = english.getName(localeId);
                    fullLocales.put(name, localeId);
                }
                out.print("<tr><td rowSpan='2'>" + ss.count +
                    "</td><td class='l' colSpan='121'>");
                int count = 0;
                for (Entry<String, String> entry : fullLocales.entrySet()) {
                    String code = entry.getValue();
                    out.print("<span title='" + code + "'>"
                        + (count == 0 ? "" : ", ")
                        + CldrUtility.getDoubleLinkedText(code + "-comp", entry.getKey())
                        + "</span>");
                    count++;
                }
                out.println("</td></tr>");
                out.println(ss.toHtmlString());
                out.println(info.toHtmlStringHeader());
            }
            out.println("</table>");
        }
    }
}
