/**
 *******************************************************************************
 * Copyright (C) 2001-2011, International Business Machines Corporation and    *
 * others. All Rights Reserved.                                                *
 *******************************************************************************
 */
package com.ibm.icu.dev.test.collator;

import java.util.Iterator;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.Vector;

import com.ibm.icu.dev.test.ModuleTest;
import com.ibm.icu.dev.test.TestDataModule.DataMap;
import com.ibm.icu.dev.test.TestFmwk;
import com.ibm.icu.impl.LocaleUtility;
import com.ibm.icu.impl.Utility;
import com.ibm.icu.lang.UCharacter;
import com.ibm.icu.text.CollationElementIterator;
import com.ibm.icu.text.CollationKey;
import com.ibm.icu.text.Collator;
import com.ibm.icu.text.RawCollationKey;
import com.ibm.icu.text.RuleBasedCollator;
import com.ibm.icu.text.UTF16;

public class CollationTest extends ModuleTest{
    // public methods --------------------------------------------------------

    public static void main(String[] args) throws Exception{
        new CollationTest().run(args);
    }

    public CollationTest() {
        super("com/ibm/icu/dev/data/testdata/", "DataDrivenCollationTest");
    }
    
    public void processModules() {
        for (Iterator iter = t.getSettingsIterator(); iter.hasNext();) {
            DataMap setting = (DataMap) iter.next();
            processSetting(setting);
        }
    }
    
    // package private methods ----------------------------------------------
    
    static void doTest(TestFmwk test, RuleBasedCollator col, String source, 
                       String target, int result)
    {
        doTestVariant(test, col, source, target, result);
        if (result == -1) {
            doTestVariant(test, col, target, source, 1);
        } 
        else if (result == 1) {
            doTestVariant(test, col, target, source, -1);
        }
        else {
            doTestVariant(test, col, target, source, 0);
        }

        CollationElementIterator iter = col.getCollationElementIterator(source);
        backAndForth(test, iter);
        iter.setText(target);
        backAndForth(test, iter);
    }
    
    /**
     * Return an integer array containing all of the collation orders
     * returned by calls to next on the specified iterator
     */
    static int[] getOrders(CollationElementIterator iter) 
    {
        int maxSize = 100;
        int size = 0;
        int[] orders = new int[maxSize];
        
        int order;
        while ((order = iter.next()) != CollationElementIterator.NULLORDER) {
            if (size == maxSize) {
                maxSize *= 2;
                int[] temp = new int[maxSize];
                System.arraycopy(orders, 0, temp,  0, size);
                orders = temp;
            }
            orders[size++] = order;
        }
        
        if (maxSize > size) {
            int[] temp = new int[size];
            System.arraycopy(orders, 0, temp,  0, size);
            orders = temp;
        }
        return orders;
    }
    
    static void backAndForth(TestFmwk test, CollationElementIterator iter) 
    {
        // Run through the iterator forwards and stick it into an array
        iter.reset();
        int[] orders = getOrders(iter);
    
        // Now go through it backwards and make sure we get the same values
        int index = orders.length;
        int o;
    
        // reset the iterator
        iter.reset();
    
        while ((o = iter.previous()) != CollationElementIterator.NULLORDER) {
            if (o != orders[--index]) {
                if (o == 0) {
                    index ++;
                } else {
                    while (index > 0 && orders[index] == 0) {
                        index --;
                    } 
                    if (o != orders[index]) {
                        test.errln("Mismatch at index " + index + ": 0x" 
                            + Integer.toHexString(orders[index]) + " vs 0x" + Integer.toHexString(o));
                        break;
                    }
                }
            }
        }
    
        while (index != 0 && orders[index - 1] == 0) {
          index --;
        }
    
        if (index != 0) {
            String msg = "Didn't get back to beginning - index is ";
            test.errln(msg + index);
    
            iter.reset();
            test.err("next: ");
            while ((o = iter.next()) != CollationElementIterator.NULLORDER) {
                String hexString = "0x" + Integer.toHexString(o) + " ";
                test.err(hexString);
            }
            test.errln("");
            test.err("prev: ");
            while ((o = iter.previous()) != CollationElementIterator.NULLORDER) {
                String hexString = "0x" + Integer.toHexString(o) + " ";
                 test.err(hexString);
            }
            test.errln("");
        }
    }
    
    // private data members --------------------------------------------------

    private String m_sequence_;
    private int m_sequenceIndex_;
    private String m_source_;
    private StringBuffer m_target_ = new StringBuffer();
    private int m_nextRelation_;
    private int m_relation_;

    // private methods -------------------------------------------------------

    private void processSetting(DataMap settings) {
        RuleBasedCollator col = null;
        // ok i have to be careful here since it seems like we can have
        // multiple locales for each test
        String locale = settings.getString("TestLocale");
        
        if (locale != null) {
            // this is a case where we have locale
            try {
                Locale l = LocaleUtility.getLocaleFromName(locale);
                col = (RuleBasedCollator)Collator.getInstance(l);
            }catch (MissingResourceException e){
                warnln("Could not load the locale data for locale " + locale);
            }catch (Exception e) {
                errln("Error creating collator for locale " + locale);
            }
            logln("Testing collator for locale " + locale);
            processSetting2(settings, col);
        }
        String rules = settings.getString("Rules");
        // ok i have to be careful here since it seems like we can have
        // multiple rules for each test
        if (rules != null) {
            // here we deal with rules
            try {
                col = new RuleBasedCollator(rules);
            }catch (MissingResourceException e){
        warnln("Could not load the locale data: " + e.getMessage());
            } catch (Exception e) {
                errln("Error creating collator for rules " + rules);
            }
            processSetting2(settings, col);
        }
    }

    private void processSetting2(DataMap settings,RuleBasedCollator col)
    {

        // ok i have to be careful here since it seems like we can have
        // multiple rules for each test
        String arguments = settings.getString("Arguments");
        if (arguments != null) {
            handleArguments(col, arguments);
        }
        processTestCases(col);
    }

    /**
     * Reads the options string and sets appropriate attributes in collator
     */
    private void handleArguments(RuleBasedCollator col, String argument) {
        int i = 0;
        boolean printInfo = false;
        while (i < argument.length()) {
            if (!UCharacter.isWhitespace(argument.charAt(i))) {
                // eat whitespace
                break;
            }
            i ++;
        }
        while (i < argument.length()) {
            // skip opening '['
            if (argument.charAt(i) == '[') {
                i ++;
            }
            else {
                if(!isModularBuild()){
                    errln("Error in collation arguments, missing ["); // no opening '['
                }
                // !!! following line has no effect
                printInfo=true;
                return;
            }

            int value = argument.indexOf(' ', i);
            String option = argument.substring(i, value);
            i = argument.indexOf(']', value);
            String optionvalue = argument.substring(value + 1, i);
            i ++;
            // some options are not added because they have no public apis yet
            // TODO add the rest of the options
            if (option.equalsIgnoreCase("alternate")) {
                if (optionvalue.equalsIgnoreCase("non-ignorable")) {
                    col.setAlternateHandlingShifted(false);
                }
                else {
                    col.setAlternateHandlingShifted(true);
                }
            }
            else if (option.equals("strength")) {
                if (optionvalue.equalsIgnoreCase("1")) {
                    col.setStrength(Collator.PRIMARY);
                }
                else if (optionvalue.equalsIgnoreCase("2")) {
                    col.setStrength(Collator.SECONDARY);
                }
                else if (optionvalue.equalsIgnoreCase("3")) {
                    col.setStrength(Collator.TERTIARY);
                }
                else if (optionvalue.equalsIgnoreCase("4")) {
                    col.setStrength(Collator.QUATERNARY);
                }
            }
        }
        if (printInfo) {
            warnln("Could not load the locale data. Skipping...");
        }
        // !!! effect is odd, if no modular build, this emits no
        // message at all.  How come?  Hmmm.  printInfo is never
        // true if we get here, so this code is never executed.
        /*
        if(printInfo == true && isModularBuild()){
            infoln("Could not load the locale data. Skipping...");
        }
        */
    }

    private void processTestCases(RuleBasedCollator col) {
        for (Iterator iter = t.getDataIterator(); iter.hasNext();) {
            DataMap e1 =  (DataMap) iter.next();
            processSequence(col, e1.getString("sequence"));
     }
    }

    private void processSequence(RuleBasedCollator col, String sequence) {
        // TODO: have a smarter tester that remembers the sequence and ensures
        // that the complete sequence is in order. That is why I have made a
        // constraint in the sequence format.
        m_sequence_ = sequence;
        m_sequenceIndex_ = 0;
        m_nextRelation_ = -1;
        m_target_.delete(0, m_target_.length());
        Vector vector = new Vector();
        int lastsmallerthanindex = -1;
        getNextInSequence();
        while (getNextInSequence()) {
            String target = m_target_.toString();
            doTest(this, col, m_source_, target, m_relation_);
            int vsize = vector.size();
            for (int i = vsize - 1; i >= 0; i --) {
                String source = (String)vector.elementAt(i);
                if (i > lastsmallerthanindex) {
                    doTest(this, col, source, target, m_relation_);
                }
                else {
                    doTest(this, col, source, target, -1);
                }
            }
            vector.addElement(target);
            if (m_relation_ < 0) {
                lastsmallerthanindex = vsize - 1;
            }
        }
    }

    /**
     * Parses the sequence to be tested
     */
    private boolean getNextInSequence() {
        if (m_sequenceIndex_ >= m_sequence_.length()) {
            return false;
        }

        boolean quoted = false;
        boolean quotedsingle = false;
        boolean done = false;
        int i = m_sequenceIndex_;
        int offset = 0;
        m_source_ = m_target_.toString();
        m_relation_ = m_nextRelation_;
        m_target_.delete(0, m_target_.length());
        while (i < m_sequence_.length() && !done) {
            int ch = UTF16.charAt(m_sequence_, i);
            if (UCharacter.isSupplementary(ch)) {
                i += 2;
            }
            else {
                i ++;
            }
            if (!quoted) {
                if (UCharacter.isWhitespace(ch)) {
                    continue;
                }
                switch (ch) {
                    case 0x003C : // <
                        m_nextRelation_ = -1;
                        done = true;
                        break;
                    case 0x003D : // =
                        m_nextRelation_ = 0;
                        done = true;
                        break;
                    case 0x003E : // >
                        m_nextRelation_ = 1;
                        done = true;
                        break;
                    case 0x0027 : // ' very basic quoting
                        quoted = true;
                        quotedsingle = false;
                        break;
                    case 0x005c : // \ single quote
                        quoted = true;
                        quotedsingle = true;
                        break;
                    default:
                        UTF16.insert(m_target_, offset, ch);
                        if (UCharacter.isSupplementary(ch)) {
                            offset += 2;
                        }
                        else {
                            offset ++;
                        }
                    }
                }
                else {
                      if (ch == 0x0027) {
                          quoted = false;
                      }
                      else {
                          UTF16.insert(m_target_, offset, ch);
                          if (UCharacter.isSupplementary(ch)) {
                              offset += 2;
                          }
                          else {
                              offset ++;
                          }
                      }
                      if (quotedsingle) {
                          quoted = false;
                      }
                }
          }
          if (quoted == true) {
              errln("Quote in sequence not closed!");
              return false;
          }


          m_sequenceIndex_ = i;
          return true;
    }

    private static void doTestVariant(TestFmwk test, 
                                      RuleBasedCollator myCollation,
                                      String source, String target, int result)
    {
        boolean printInfo = false;
        int compareResult  = myCollation.compare(source, target);
        if (compareResult != result) {
            
            // !!! if not mod build, error, else nothing.
            // warnln if not build, error, else always print warning.
            // do we need a 'quiet warning?' (err or log).  Hmmm,
            // would it work to have the 'verbose' flag let you 
            // suppress warnings?  Are there ever some warnings you
            // want to suppress, and others you don't?
            if(!test.isModularBuild()){
                test.errln("Comparing \"" + Utility.hex(source) + "\" with \""
                           + Utility.hex(target) + "\" expected " + result
                           + " but got " + compareResult);
            }else{
                printInfo = true;
            }
        }
        CollationKey ssk = myCollation.getCollationKey(source);
        CollationKey tsk = myCollation.getCollationKey(target);
        compareResult = ssk.compareTo(tsk);
        if (compareResult != result) {
            
            if(!test.isModularBuild()){
                test.errln("Comparing CollationKeys of \"" + Utility.hex(source) 
                           + "\" with \"" + Utility.hex(target) 
                           + "\" expected " + result + " but got " 
                           + compareResult);
           }else{
               printInfo = true;
           }
        }
        RawCollationKey srsk = new RawCollationKey();
        myCollation.getRawCollationKey(source, srsk);
        RawCollationKey trsk = new RawCollationKey();
        myCollation.getRawCollationKey(target, trsk);
        compareResult = ssk.compareTo(tsk);
        if (compareResult != result) {
            
            if(!test.isModularBuild()){
                test.errln("Comparing RawCollationKeys of \"" 
                           + Utility.hex(source) 
                           + "\" with \"" + Utility.hex(target) 
                           + "\" expected " + result + " but got " 
                           + compareResult);
           }else{
               printInfo = true;
           }
        }
        // hmmm, but here we issue a warning
        // only difference is, one warning or two, and detailed info or not?
        // hmmm, does seem preferable to omit detail if we know it is due to missing resource data.
        // well, if we label the errors as warnings, we can let people know the details, but
        // also know they may be due to missing resource data.  basically this code is asserting
        // that the errors are due to missing resource data, which may or may not be true.
        if (printInfo) {
            test.warnln("Could not load locale data skipping.");
        }
    }
}
