package org.unicode.cldr.test;

import java.io.IOException;
import java.io.PrintWriter;

import org.unicode.cldr.util.CLDRTransforms;
import org.unicode.cldr.util.CldrUtility;

import com.ibm.icu.dev.test.TestFmwk;
import com.ibm.icu.dev.test.util.BagFormatter;
import com.ibm.icu.text.Normalizer;
import com.ibm.icu.text.StringTransform;
import com.ibm.icu.text.Transliterator;
import com.ibm.icu.text.UnicodeSet;
import com.ibm.icu.text.UnicodeSetIterator;

public class TestTransformsSimple extends TestFmwk {

  public static void main(String[] args) throws Exception {
    CLDRTransforms.registerCldrTransforms(null, null, out);
    new TestTransformsSimple().run(args);
  }

  private static final boolean verbose = CldrUtility.getProperty("verbose", false);
  private static PrintWriter out = verbose ? new PrintWriter(System.out, true) : null;

  public void TestChinese() {
    //CLDRTransforms.registerCldrTransforms(null, ".*(Han|Pinyin).*", out);
    Transliterator hanLatin = Transliterator.getInstance("Han-Latin");
    assertTransform("Transform", "zào Unicode", hanLatin, "造Unicode");
    assertTransform("Transform", "zài chuàng zào Unicode zhī qián", hanLatin, "在創造Unicode之前");
  }

  public void TestHangul2() {
    //CLDRTransforms.registerCldrTransforms(null, ".*(Hangul|Jamo).*", out);

    Transliterator lh = Transliterator.getInstance("Latin-Hangul");
    Transliterator hl = lh.getInverse();

    //assertRoundTripTransform("Transform", "\uAC0D\uD0C0", lh, hl);
    //assertRoundTripTransform("Transform", "\uAC0D\uB530", lh, hl);

    final UnicodeSet representativeHangul = getRepresentativeHangul();
    for (UnicodeSetIterator it = new UnicodeSetIterator(representativeHangul); it.next();) {
      assertRoundTripTransform("Transform", it.getString(), lh, hl);
    }

    assertTransform("Transform", "츠", lh, "ch");

    assertTransform("Transform", "아따", lh, hl, "atta", "a-tta");
    assertTransform("Transform", "아빠", lh, hl, "appa", "a-ppa");
    assertTransform("Transform", "아짜", lh, hl, "ajja", "a-jja");
    assertTransform("Transform", "아까", lh, hl, "akka", "a-kka");
    assertTransform("Transform", "아싸", lh, hl, "assa", "a-ssa");
    assertTransform("Transform", "아차", lh, hl, "acha", "a-cha");
    assertTransform("Transform", "악사", lh, hl, "agsa", "ag-sa");
    assertTransform("Transform", "안자", lh, hl, "anja", "an-ja");
    assertTransform("Transform", "안하", lh, hl, "anha", "an-ha");
    assertTransform("Transform", "알가", lh, hl, "alga", "al-ga");
    assertTransform("Transform", "알마", lh, hl, "alma", "al-ma");
    assertTransform("Transform", "알바", lh, hl, "alba", "al-ba");
    assertTransform("Transform", "알사", lh, hl, "alsa", "al-sa");
    assertTransform("Transform", "알타", lh, hl, "alta", "al-ta");
    assertTransform("Transform", "알파", lh, hl, "alpa", "al-pa");
    assertTransform("Transform", "알하", lh, hl, "alha", "al-ha");
    assertTransform("Transform", "압사", lh, hl, "absa", "ab-sa");
    assertTransform("Transform", "안가", lh, hl, "anga", "an-ga");
    assertTransform("Transform", "악싸", lh, hl, "agssa", "ag-ssa");
    assertTransform("Transform", "안짜", lh, hl, "anjja", "an-jja");
    assertTransform("Transform", "알싸", lh, hl, "alssa", "al-ssa");
    assertTransform("Transform", "알따", lh, hl, "altta", "al-tta");
    assertTransform("Transform", "알빠", lh, hl, "alppa", "al-ppa");
    assertTransform("Transform", "압싸", lh, hl, "abssa", "ab-ssa");
    assertTransform("Transform", "앆카", lh, hl, "akkka", "akk-ka");
    assertTransform("Transform", "았사", lh, hl, "asssa", "ass-sa");

    //    1. Latin->Hangul transliterator maps 'ch' to '킇' (splitting the sequence
    //            into
    //            'c' and 'h' and inserting an implicit vowel 'ㅡ'). It'd be better to map a
    //            *stand-alone* 'ch' to '츠'  
    //
    //            2.  As mentioned in http://www.unicode.org/cldr/transliteration_guidelines.html
    //            (Korean section),  
    //
    //            - altta = alt-ta  앑타   should be ' al-tta 알따'
    //
    //            - alppa = alp-pa  앒파   : should be 'al-ppa  알빠'

  }

  private void assertRoundTripTransform(String message, String source, Transliterator lh, Transliterator hl) {
    String to = hl.transform(source);
    String back = lh.transform(to);
    String to2 = hl.transform(source.replaceAll("(.)", "$1 ").trim());
    String to3 = hl.transform(back.replaceAll("(.)", "$1 ").trim());
    assertEquals(message + " " + source + " [" + to + "/"+ to2 + "/"+ to3 + "]", source, back);
  }

  private void assertTransform(String message, String expected, StringTransform t, String source) {
    assertEquals(message + " " + source, expected, t.transform(source));
  }


  private void assertTransform(String message, String expected, StringTransform t, StringTransform back, String... source) {
    for (String s : source) {
      assertEquals(message + " " + s, expected, t.transform(s));
    }
    assertEquals(message + " " + expected, source[0], back.transform(expected));
  }

  public void xTestTamil() throws IOException {
    {
      //CLDRTransforms.registerCldrTransforms(null, ".*(Tamil).*", out);
      String name = "Tamil-Devanagari";
      Transliterator tamil_devanagari = Transliterator.getInstance(name);
      Transliterator devanagari_tamil = Transliterator.getInstance(name, Transliterator.REVERSE);
      writeFile(name, new UnicodeSet("[[:block=tamil:]-[ௗ]]"), null, tamil_devanagari, devanagari_tamil, false, null, null);
    }
  }

  public void TestJamo() throws IOException {
    {
      //CLDRTransforms.registerCldrTransforms(null, ".*(Jamo).*", out);
      String name = "Latin-ConjoiningJamo";
      Transliterator fromLatin = Transliterator.getInstance(name);
      Transliterator toLatin = Transliterator.getInstance(name, Transliterator.REVERSE);
      UnicodeSet sourceSet = getRepresentativeHangul();
      logln(sourceSet.size() + "\t" + sourceSet.toPattern(false));

      Transliterator nfd = Transliterator.getInstance("nfd");

      UnicodeSet multiply = new UnicodeSet(sourceSet);
//      for (UnicodeSetIterator it = new UnicodeSetIterator(sourceSet); it.next();) {
//        for (UnicodeSetIterator it2 = new UnicodeSetIterator(sourceSet); it2.next();) {
//          String source1 = it.getString() + it2.getString(); // try all combinations.
//          multiply.add(source1);
//        }
//      }

      //    latin.addAll(toTarget.getSourceSet())
      //    .addAll(toTarget.getTargetSet())
      //    .addAll(fromTarget.getSourceSet())
      //    .addAll(fromTarget.getTargetSet());
      //    latin.retainAll(new UnicodeSet("[[:latin:][:common:][:inherited:]]"));

      //Transliterator.DEBUG = true;  

      UnicodeSet specials = null; // new UnicodeSet("[{ch}]");
      writeFile(name, multiply, nfd, toLatin, fromLatin, true, null, specials);
    }
  }

  private int writeFile(String title, UnicodeSet sourceSet, Transliterator nfd, Transliterator toLatin, 
          Transliterator fromLatin, boolean doLatin, UnicodeSet nativeSpecials, UnicodeSet latinSpecials) throws IOException {
    int errorCount = 0;
    PrintWriter out = BagFormatter.openUTF8Writer(org.unicode.cldr.util.CldrUtility.GEN_DIRECTORY + "transTest/", title + ".html");
    out.println("<html><head>");
    out.println("<meta http-equiv='Content-Type' content='text/html; charset=utf-8'></head><body>");
    if (nativeSpecials != null) {
      out.println("<h1>Specials</h1><table border='1' cellpadding='2' cellspacing='0' style='border-collapse: collapse'>");
      showItems(out, true, "Source", "ToLatin", "FromLatin", "BackToLatin");
      for (UnicodeSetIterator it = new UnicodeSetIterator(nativeSpecials); it.next();) {
        String item = it.getString();
        errorCount = checkString(out, item, nfd, fromLatin, toLatin, errorCount, null);
        errorCount = checkString(out, item, nfd, fromLatin, toLatin, errorCount, "-");
      }
      out.println("</table><p>Special failures:\t" + errorCount + "</p>");
      if (errorCount != 0) {
        errln("Special failures:\t" + errorCount);
        errorCount = 0;
      }
    }

    if (latinSpecials != null) {
      out.println("<h1>Specials</h1><table border='1' cellpadding='2' cellspacing='0' style='border-collapse: collapse'>");
      showItems(out, true, "Latin", "ToNative", "BackToLatin", "BackToNative");
      for (UnicodeSetIterator it = new UnicodeSetIterator(latinSpecials); it.next();) {
        String item = it.getString();
        errorCount = checkString(out, item, nfd, toLatin, fromLatin, errorCount, null);
        //errorCount = checkString(out, item, nfd, fromLatin, toLatin, errorCount, "-");
      }
      out.println("</table><p>Special failures:\t" + errorCount + "</p>");
      if (errorCount != 0) {
        errln("Special failures:\t" + errorCount);
        errorCount = 0;
      }
    }

    if (doLatin) {
      out.println("<h1>Latin failures</h1><table border='1' cellpadding='2' cellspacing='0' style='border-collapse: collapse'>");
      showItems(out, true, "Latin", "Target", "BackToLatin", "BackToTarget");
      errorCount = checkLatin(out, fromLatin, toLatin);
      out.println("</table><p>Latin failures:\t" + errorCount + "</p>");
      if (errorCount != 0) {
        errln("Latin failures:\t" + errorCount);
        errorCount = 0;
      }

    }

    out.println("<h1>Not Reversible</h1><table border='1' cellpadding='2' cellspacing='0' style='border-collapse: collapse'>");
    errorCount = showMappings(out, sourceSet, null, nfd, fromLatin, toLatin);
    out.println("</table><p>Reversible failures:\t" + errorCount + "</p>");
    if (errorCount != 0) {
      errln("Reversible failures:\t" + errorCount);
      errorCount = 0;
    }


    out.println("<h1>Has Unneeded Separator</h1><table border='1' cellpadding='2' cellspacing='0' style='border-collapse: collapse'>");
    errorCount = showMappings(out, sourceSet, "-", nfd, fromLatin, toLatin);
    out.println("</table><p>Separator failures:\t" + errorCount + "</p>");
    if (errorCount != 0) {
      logln("Unneeded Separators:\t" + errorCount);
      errorCount = 0;
    }

    out.print("</body></html>");
    out.close();
    return errorCount;
  }

  static UnicodeSet latin = new UnicodeSet("[a-z]");

  private static int checkLatin(PrintWriter out, Transliterator fromLatin, Transliterator toLatin) {
    int errorCount = 0;
    for (UnicodeSetIterator it = new UnicodeSetIterator(latin); it.next();) {
      String source = it.getString();
      String to = fromLatin.transliterate(source);
      if (latin.containsSome(to)) {
        String from = toLatin.transliterate(to);
        String backto = toLatin.transliterate(from);
        errorCount += showItems(out, false, source, to, from, backto);
      }
    }
    return errorCount;
  }

  private static int showMappings(PrintWriter out, UnicodeSet testSet, String separator, Transliterator nfd, Transliterator fromLatin, Transliterator toLatin) {
    int errorCount = 0;
    if (separator == null) {
      showItems(out, true, "Source", "ToLatin", "FromLatin", "BackToLatin");
    } else {
      showItems(out, true, "Source", "ToLatin", "FromLatin", "BackToLatin", "WithoutSeparator");
    }
    for (UnicodeSetIterator it = new UnicodeSetIterator(testSet); it.next();) {
      errorCount = checkString(out, it.getString(), nfd, fromLatin, toLatin, errorCount, separator);
    }
    return errorCount;
  }

  private static int checkString(PrintWriter out, String source1, Transliterator nfd, Transliterator fromLatin, Transliterator toLatin, int errorCount, String separator) {
    String source = nfd == null ? source1 : nfd.transliterate(source1);
    String to = toLatin.transliterate(source);
    String from = fromLatin.transliterate(to);
    if (separator == null) {
      final boolean bad = !source.equals(from);
      if (bad || verbose) {
        String backto = toLatin.transliterate(from);
        if (bad) {
          errorCount += 1;
        }
        showItems(out, false, source, to, from, backto, bad ? "FAIL" : null);
      } else {
        //showItems(out, source, to, from, "OK");
      }
    } else {
      if (to.contains(separator)) { // check separators, only put in when needed
        String otherTo = to.replace("-","");
        String otherFrom =  fromLatin.transliterate(otherTo);
        final boolean bad = otherFrom.equals(from);
        if (bad) {
          //String backto = toLatin.transliterate(from);
          errorCount += 1;
          showItems(out, false, source, to, from, otherTo, otherFrom, bad ? "FAIL" : null);
        }
      }
    }
    return errorCount;
  }

  private static int showItems(PrintWriter out, boolean th, String... sourceList) {
    out.println("<tr>");
    for (String source : sourceList) {
      if (source == null) continue;
      //System.out.print("<" + source + ">\t");
      out.println((th ? "<th>" : "<td>") + pretty(source) + (th ? "</th>" : "</td>"));
    }
    //System.out.println();
    out.println("</tr>");
    out.flush();
    return 1;
  }

  static UnicodeSet lead = new UnicodeSet("[:Hangul_Syllable_Type=Leading_Jamo:]");
  static UnicodeSet vowel = new UnicodeSet("[:Hangul_Syllable_Type=Vowel_Jamo:]");
  static UnicodeSet trail = new UnicodeSet("[:Hangul_Syllable_Type=Trailing_Jamo:]");

  private static String pretty(String source) {
    StringBuilder result = new StringBuilder();
    for (int i = 0; i < source.length(); ++i) {
      char c = source.charAt(i);
      String color = lead.contains(c) ? "FFcccc" 
              : vowel.contains(c) ? "ccFFcc" 
                      : trail.contains(c) ? "ccccFF" : "FFFFFF";
      result.append("<span style='background-color: #"+ color + "'>"  + c + "</span>");
    }
    return result.toString();
  }


  //  public static UnicodeSet concatenate(UnicodeSet a, UnicodeSet b) {
  //    UnicodeSet c = new UnicodeSet();
  //    for (UnicodeSetIterator ai = new UnicodeSetIterator(a); ai.next();) {
  //      for (UnicodeSetIterator bi = new UnicodeSetIterator(b); bi.next();) {
  //        c.add(ai.getString() + bi.getString());
  //      }
  //    }
  //    return c;
  //  }
  //  
  //  static interface Process {
  //    public Process process(String s);
  //    public UnicodeSet getResults();
  //  }
  //  
  //  static class Processes implements Process {
  //    private Process[] processes;
  //    public Processes(Process[] processes) {
  //      this.processes = processes;
  //    }
  //    public Process process(String s) {
  //      for (int i = 0; i < processes.length; ++i) {
  //        processes[i].process(s);
  //      }
  //      return this;
  //    }
  //    public UnicodeSet getResults() {
  //      UnicodeSet result = new UnicodeSet();
  //      for (int i = 0; i < processes.length; ++i) {
  //        result.addAll(processes[i].getResults());
  //      }
  //      return result;
  //    }
  //  }
  //
  //  public static UnicodeSet ProcessDecomposed(Process process, UnicodeSet source) {
  //    for (UnicodeSetIterator ai = new UnicodeSetIterator(source); ai.next();) {
  //      String normalized;
  //      if (ai.codepoint == UnicodeSetIterator.IS_STRING) {
  //        normalized = Normalizer.normalize(ai.codepoint, Normalizer.NFD); 
  //      } else {
  //        normalized = Normalizer.normalize(ai.string, Normalizer.NFD); 
  //      }
  //      process.process(normalized);
  //    }
  //    return process.getResults();
  //  }

  public static UnicodeSet getRepresentativeHangul() {
    UnicodeSet extraSamples = new UnicodeSet("[\uCE20{\uAD6C\uB514}{\uAD73\uC774}{\uBB34\uB837}{\uBB3C\uC5FF}{\uC544\uAE4C}{\uC544\uB530}{\uC544\uBE60}{\uC544\uC2F8}{\uC544\uC9DC}{\uC544\uCC28}{\uC545\uC0AC}{\uC545\uC2F8}{\uC546\uCE74}{\uC548\uAC00}{\uC548\uC790}{\uC548\uC9DC}{\uC548\uD558}{\uC54C\uAC00}{\uC54C\uB530}{\uC54C\uB9C8}{\uC54C\uBC14}{\uC54C\uBE60}{\uC54C\uC0AC}{\uC54C\uC2F8}{\uC54C\uD0C0}{\uC54C\uD30C}{\uC54C\uD558}{\uC555\uC0AC}{\uC555\uC2F8}{\uC558\uC0AC}{\uC5C5\uC12F\uC501}{\uC5C6\uC5C8\uC2B5}]");
    UnicodeSet sourceSet = new UnicodeSet();
    addRepresentativeHangul(sourceSet, 2, false);
    addRepresentativeHangul(sourceSet, 3, false);
    addRepresentativeHangul(sourceSet, 2, true);
    addRepresentativeHangul(sourceSet, 3, true);
    // add the boundary cases; we want an example of each case of V + L and one example of each case of T+L

    UnicodeSet more = getRepresentativeBoundaryHangul();
    sourceSet.addAll(more);
    sourceSet.addAll(extraSamples);
    return sourceSet;
  }

  private static UnicodeSet getRepresentativeBoundaryHangul() {
    UnicodeSet resultToAddTo = new UnicodeSet();
    // U+1100 ( ᄀ ) HANGUL CHOSEONG KIYEOK
    // U+1161 ( ᅡ ) HANGUL JUNGSEONG A
    UnicodeSet L = new UnicodeSet("[:hst=L:]");
    UnicodeSet V = new UnicodeSet("[:hst=V:]");
    UnicodeSet T = new UnicodeSet("[:hst=T:]");

    String prefixLV = "\u1100\u1161";
    String prefixL = "\u1100";
    String suffixV = "\u1161";
    String nullL = "\u110B";

    UnicodeSet L0 = new UnicodeSet("[\u1100\u110B]");

    // do all combinations of L0 + V + nullL + V

    for (UnicodeSetIterator iL0 = new UnicodeSetIterator(L0); iL0.next();) {
      for (UnicodeSetIterator iV = new UnicodeSetIterator(V); iV.next();) {
          for (UnicodeSetIterator iV2 = new UnicodeSetIterator(V); iV2.next();) {
            String sample = iL0.getString() + iV.getString() + nullL + iV2.getString();
            String trial = Normalizer.compose(sample, false);
            if (trial.length() == 2) {
              resultToAddTo.add(trial);
            }
        }
      }
    }

    for (UnicodeSetIterator iL = new UnicodeSetIterator(L); iL.next();) {
      // do all combinations of "g" + V + L + "a"
      final String suffix = iL.getString() + suffixV;
      for (UnicodeSetIterator iV = new UnicodeSetIterator(V); iV.next();) {
        String sample = prefixL + iV.getString() + suffix;
        String trial = Normalizer.compose(sample, false);
        if (trial.length() == 2) {
          resultToAddTo.add(trial);
        }
      }
      // do all combinations of "ga" + T + L + "a"
      for (UnicodeSetIterator iT = new UnicodeSetIterator(T); iT.next();) {
        String sample = prefixLV + iT.getString() + suffix;
        String trial = Normalizer.compose(sample, false);
        if (trial.length() == 2) {
          resultToAddTo.add(trial);
        }
      }
    }
    return resultToAddTo;
  }

  private static void addRepresentativeHangul(UnicodeSet resultToAddTo, int leng, boolean noFirstConsonant) {
    UnicodeSet notYetSeen = new UnicodeSet();
    for (char c = '\uAC00'; c <  '\uD7AF'; ++c) {
      String charStr = String.valueOf(c);
      String decomp = Normalizer.decompose(charStr, false);
      if (decomp.length() != leng) {
        continue; // only take one length at a time
      }
      if (decomp.startsWith("ᄋ") != noFirstConsonant) {
        continue;
      }
      if (!notYetSeen.containsAll(decomp)) {
        resultToAddTo.add(c);
        notYetSeen.addAll(decomp);
      }
    }
  }
}