package org.unicode.cldr.unittest;

import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Matcher;

import org.unicode.cldr.tool.ConvertLanguageData.InverseComparator;
import org.unicode.cldr.util.CLDRConfig;
import org.unicode.cldr.util.CLDRFile;
import org.unicode.cldr.util.CLDRLocale;
import org.unicode.cldr.util.CLDRURLS;
import org.unicode.cldr.util.CldrUtility;
import org.unicode.cldr.util.Counter;
import org.unicode.cldr.util.DelegatingIterator;
import org.unicode.cldr.util.EscapingUtilities;
import org.unicode.cldr.util.Factory;
import org.unicode.cldr.util.Organization;
import org.unicode.cldr.util.PathHeader;
import org.unicode.cldr.util.PathHeader.PageId;
import org.unicode.cldr.util.PatternCache;
import org.unicode.cldr.util.PluralSamples;
import org.unicode.cldr.util.SpecialLocales;
import org.unicode.cldr.util.StringId;
import org.unicode.cldr.util.SupplementalDataInfo;
import org.unicode.cldr.util.SupplementalDataInfo.PluralInfo.Count;
import org.unicode.cldr.util.VettingViewer.Choice;
import org.unicode.cldr.util.VettingViewer.VoteStatus;
import org.unicode.cldr.util.VoteResolver;
import org.unicode.cldr.util.VoteResolver.CandidateInfo;
import org.unicode.cldr.util.VoteResolver.Level;
import org.unicode.cldr.util.VoteResolver.Status;
import org.unicode.cldr.util.VoteResolver.VoterInfo;
import org.unicode.cldr.util.props.ICUPropertyFactory;

import com.google.common.collect.ImmutableMap;
import com.ibm.icu.dev.util.UnicodeMap;
import com.ibm.icu.impl.Utility;
import com.ibm.icu.lang.UCharacter;
import com.ibm.icu.lang.UProperty;
import com.ibm.icu.text.Collator;
import com.ibm.icu.text.UnicodeSet;
import com.ibm.icu.util.ULocale;

public class TestUtilities extends TestFmwkPlus {
    private static final UnicodeSet DIGITS = new UnicodeSet("[0-9]");
    static CLDRConfig testInfo = CLDRConfig.getInstance();
    private static final SupplementalDataInfo SUPPLEMENTAL_DATA_INFO = testInfo
        .getSupplementalDataInfo();
    private static final int STRING_ID_TEST_COUNT = 1024 * 16;

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

    public void TestPluralSamples() {
        checkPluralSamples("en");
        checkPluralSamples("cs");
        checkPluralSamples("ar");
    }

    private void checkPluralSamples(String locale) {
        PluralSamples pluralSamples = PluralSamples.getInstance(locale);
        Set<Count> counts = SUPPLEMENTAL_DATA_INFO.getPlurals(locale)
            .getCounts();
        for (int i = 1; i < 5; ++i) {
            Map<Count, Double> samplesForDigits = pluralSamples.getSamples(i);
            if (!counts.containsAll(samplesForDigits.keySet())) {
                errln(locale + ": mismatch in samples, expected " + counts
                    + ", got: " + samplesForDigits);
            } else if (samplesForDigits.size() == 0) {
                errln(locale + ": no sample for digit " + i);
            } else {
                logln(locale + " plural samples: " + samplesForDigits);
            }
        }
    }

    public static class StringIdException extends RuntimeException {
        private static final long serialVersionUID = 1L;
    }

    public class StringIdThread extends Thread {
        final private Random r = new Random();
        final private int id;

        StringIdThread(int i) {
            super("Demo Thread");
            id = i;
        }

        public void run() {
            logln("Starting thread: " + this);
            for (int i = 0; i < STRING_ID_TEST_COUNT; ++i) {
                String s = String.valueOf(r.nextInt());
                long l = StringId.getId(s);
                String s2 = StringId.getStringFromId(l);
                if (!s.equals(s2)) {
                    throw new StringIdException();
                }
            }
            logln("Ending thread: " + this);
        }

        public String toString() {
            return "StringIdThread " + id;
        }
    }

    public void TestStringId() {
        ArrayList<StringIdThread> threads = new ArrayList<StringIdThread>();

        for (int i = 0; i < 8; i++) {
            StringIdThread thread = new StringIdThread(i);
            threads.add(thread);
            thread.start();
        }
        for (StringIdThread thread : threads) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                errln(e.toString());
            }
        }
    }

    public void TestUrlEscape() {
        Matcher byte1 = PatternCache.get("%[A-Za-z0-9]{2}").matcher("");
        Matcher byte2 = PatternCache.get("%[A-Za-z0-9]{2}%[A-Za-z0-9]{2}")
            .matcher("");
        Matcher byte3 = PatternCache.get(
            "%[A-Za-z0-9]{2}%[A-Za-z0-9]{2}%[A-Za-z0-9]{2}").matcher("");
        Matcher byte4 = PatternCache.get(
            "%[A-Za-z0-9]{2}%[A-Za-z0-9]{2}%[A-Za-z0-9]{2}%[A-Za-z0-9]{2}")
            .matcher("");
        for (int i = 1; i <= 0x10FFFF; i = i * 3 / 2 + 1) {
            String escaped = EscapingUtilities.urlEscape(new StringBuilder()
            .appendCodePoint(i).toString());
            logln(Integer.toHexString(i) + " => " + escaped);
            if (EscapingUtilities.OK_TO_NOT_QUOTE.contains(i)) {
                assertTrue("Should be unquoted", escaped.length() == 1);
            } else if (i < 0x80) {
                assertTrue("Should be %xx", byte1.reset(escaped).matches());
            } else if (i < 0x800) {
                assertTrue("Should be %xx%xx", byte2.reset(escaped).matches());
            } else if (i < 0x10000) {
                assertTrue("Should be %xx%xx%xx", byte3.reset(escaped)
                    .matches());
            } else {
                assertTrue("Should be %xx%xx%xx%xx", byte4.reset(escaped)
                    .matches());
            }
        }
    }

    public void TestDelegatingIterator() {
        Set<String> s = new TreeSet<String>(Arrays.asList(new String[] { "a",
            "b", "c" }));
        Set<String> t = new LinkedHashSet<String>(Arrays.asList(new String[] {
            "f", "d", "e" }));
        StringBuilder result = new StringBuilder();

        for (String u : DelegatingIterator.iterable(s, t)) {
            result.append(u);
        }
        assertEquals("Iterator", "abcfde", result.toString());

        result.setLength(0);
        for (String u : DelegatingIterator.array("s", "t", "u")) {
            result.append(u);
        }
        assertEquals("Iterator", "stu", result.toString());

        int count = 0;
        result.setLength(0);
        for (int u : DelegatingIterator.array(1, 3, 5)) {
            count += u;
        }
        assertEquals("Iterator", 9, count);

        result.setLength(0);
        for (Object u : DelegatingIterator.array(1, "t", "u", new UnicodeSet(
            "[a-z]"))) {
            result.append(u);
        }
        assertEquals("Iterator", "1tu[a-z]", result.toString());
    }

    public void TestUntimedCounter() {
        // simulates how Counter is used in VettingViewer
        Counter<Choice> problemCounter = new Counter<Choice>();
        problemCounter.increment(Choice.error);
        problemCounter.increment(Choice.error);
        problemCounter.increment(Choice.warning);

        assertEquals("problemCounter error", 2, problemCounter.get(Choice.error));
        assertEquals("problemCounter warning", 1, problemCounter.get(Choice.warning));
        assertEquals("problemCounter weLost", 0, problemCounter.get(Choice.weLost));


        Counter<Choice> otherCounter = new Counter<Choice>();
        otherCounter.addAll(problemCounter);
        otherCounter.increment(Choice.error);

        assertEquals("otherCounter error", 3, otherCounter.get(Choice.error));
        assertEquals("otherCounter warning", 1, otherCounter.get(Choice.warning));
        assertEquals("otherCounter weLost", 0, otherCounter.get(Choice.weLost));
    }

    public void TestCounter() {
        Counter<String> counter = new Counter<String>(true);
        Comparator<String> uca = new Comparator<String>() {
            Collator col = Collator.getInstance(ULocale.ENGLISH);

            public int compare(String o1, String o2) {
                return col.compare(o1, o2);
            }
        };
        InverseComparator ucaDown = new InverseComparator(uca);

        counter.add("c", 95);
        counter.add("b", 50);
        counter.add("b", 101);
        counter.add("a", 100);
        counter.add("a", -5);
        counter.add("d", -3);
        assertEquals("getCount(b)", counter.getCount("b"), 151);
        assertEquals("getCount(a)", counter.getCount("a"), 95);
        assertEquals("getCount(a)", counter.getTotal(), 338);
        assertEquals("getItemCount", counter.getItemCount(), 4);

        assertEquals("getMap", "{a=95, b=151, c=95, d=-3}", counter.toString());

        assertEquals("getKeysetSortedByKey", Arrays.asList("a", "b", "c", "d"),
            new ArrayList<String>(counter.getKeysetSortedByKey()));

        assertEquals(
            "getKeysetSortedByCount(true, ucaDown)",
            Arrays.asList("d", "c", "a", "b"),
            new ArrayList<String>(counter.getKeysetSortedByCount(true,
                ucaDown)));

        assertEquals("getKeysetSortedByCount(true, null), value",
            Arrays.asList("d", "a", "c", "b"), new ArrayList<String>(
                counter.getKeysetSortedByCount(true, uca)));

        assertEquals("getKeysetSortedByCount(false, ucaDown), descending",
            Arrays.asList("b", "c", "a", "d"), new ArrayList<String>(
                counter.getKeysetSortedByCount(false, ucaDown)));

        assertEquals("getKeysetSortedByCount(false, null), descending, value",
            Arrays.asList("b", "a", "c", "d"), new ArrayList<String>(
                counter.getKeysetSortedByCount(false, uca)));
    }

    public void TestOrganizationOrder() {
        Map<String, Organization> stringToOrg = new TreeMap<String, Organization>();
        for (Organization org : Organization.values()) {
            stringToOrg.put(org.toString(), org);
        }
        List<Organization> reordered = new ArrayList<Organization>(
            stringToOrg.values());
        List<Organization> plain = Arrays.asList(Organization.values());
        for (int i = 0; i < reordered.size(); ++i) {
            assertEquals("Items not in alphabetical order", reordered.get(i),
                plain.get(i));
        }
    }

    public void TestOrganizationNames() {
        UnicodeSet uppercase = new UnicodeSet("[:uppercase:]");
        for (Organization org : Organization.values()) {
            if (!uppercase.contains(org.getDisplayName().codePointAt(0))) {
                errln("Organization name isn't titlecased: " + org + ", "
                    + org.getDisplayName());
            }
            assertEquals("Organization from enum name", org,
                Organization.fromString(org.toString()));
            assertEquals("Organization from display name", org,
                Organization.fromString(org.getDisplayName()));
        }
    }

    // public void TestVoteResolverData() {
    // final PrintWriter errorLogPrintWriter = this.getErrorLogPrintWriter();
    // final PrintWriter logPrintWriter = this.getLogPrintWriter();
    // String userFile = CldrUtility.getProperty("usersxml",
    // CldrUtility.TMP_DIRECTORY +
    // "/incoming/vetted/usersa/usersa.xml");
    // String votesDirectory = CldrUtility.getProperty("votesxml",
    // CldrUtility.TMP_DIRECTORY +
    // "/incoming/vetted/votes/");
    // String vettedDirectory = CldrUtility.getProperty("vetted",
    // CldrUtility.TMP_DIRECTORY + "/incoming/vetted/main/");
    //
    // PathValueInfo.voteInfo = VoteResolver.getIdToPath(votesDirectory +
    // "xpathTable.xml");
    // Factory factory = Factory.make(vettedDirectory, ".*");
    //
    // VoteResolver.setVoterToInfo(userFile);
    // Map<String, Map<Organization, Relation<Level, Integer>>> map =
    // VoteResolver
    // .getLocaleToVetters();
    // for (String locale : map.keySet()) {
    // Map<Organization, Relation<Level, Integer>> orgToLevelToVoter =
    // map.get(locale);
    // String localeName = null;
    // try {
    // localeName = testInfo.getEnglish().getName(locale);
    // } catch (RuntimeException e) {
    // errln("Invalid locale:\t" + locale);
    // localeName = "UNVALID(" + locale + ")";
    // }
    // if (DEBUG) {
    // for (Organization org : orgToLevelToVoter.keySet()) {
    // log(locale + "\t" + localeName + "\t" + org + ":");
    // final Relation<Level, Integer> levelToVoter = orgToLevelToVoter.get(org);
    // for (Level level : levelToVoter.keySet()) {
    // log("\t" + level + "=" + levelToVoter.getAll(level).size());
    // }
    // logln("");
    // }
    // }
    // }
    //
    // File votesDir = new File(votesDirectory);
    // for (String file : votesDir.list()) {
    // if (file.startsWith("xpathTable")) {
    // continue;
    // }
    // if (file.endsWith(".xml")) {
    // final String locale = file.substring(0,file.length()-4);
    // try {
    // checkLocaleVotes(factory, locale, votesDirectory, errorLogPrintWriter,
    // logPrintWriter);
    // } catch (RuntimeException e) {
    // errln("Can't process " + locale + ": " + e.getMessage() + " " +
    // Arrays.asList(e.getStackTrace()));
    // //throw (RuntimeException) new IllegalArgumentException("Can't process "
    // + locale).initCause(e);
    // }
    // }
    // }
    // }

    static final boolean SHOW_DETAILS = CldrUtility.getProperty("showdetails",
        false);
    private static final CharSequence DEBUG_COMMENT = "set up a case of conflict within organization";

    // private void checkLocaleVotes(Factory factory, final String locale,
    // String votesDirectory, PrintWriter errorLog,
    // PrintWriter warningLog) {
    // // logln("*** Locale " + locale + ": \t***");
    // PathValueInfo pathValueInfo = new PathValueInfo(factory, locale);

    // Map<Organization, Level> orgToMaxVote =
    // VoteResolver.getOrganizationToMaxVote(locale);
    // if (orgToMaxVote.size() == 0) {
    // logln("");
    // logln(locale + ": \tNo organizations with translators");
    // } else if (!locale.contains("_")) {
    // logln("");
    // logln(locale + ": \tOrganizations with translators:\t" + orgToMaxVote);
    // }
    //
    // Map<Integer, Map<Integer, CandidateInfo>> info =
    // VoteResolver.getBaseToAlternateToInfo(votesDirectory + locale
    // + ".xml");
    // Set<Organization> missingOrganizations =
    // EnumSet.noneOf(Organization.class);
    // Counter<Organization> missingOrganizationCounter = new
    // Counter<Organization>(true);
    // Counter<Organization> goodOrganizationCounter = new
    // Counter<Organization>(true);
    // Counter<Status> winningStatusCounter = new Counter<Status>(true);
    // EnumSet<Organization> conflictedOrganizations =
    // EnumSet.noneOf(Organization.class);
    // Set<Integer> missingOptimals = new TreeSet<Integer>();
    //
    // Set<Integer> surveyVsVoteResolverDifferences = new TreeSet<Integer>();
    //
    // Set<Integer> unknownVotersSoFar = new HashSet<Integer>();
    //
    // Counter<Status> oldStatusCounter = new Counter<Status>(true);
    // Counter<Status> surveyStatusCounter = new Counter<Status>(true);
    // Counter<Type> surveyTypeCounter = new Counter<Type>(true);
    // VoteResolver<String> voteResolver = new VoteResolver<String>();
    // Map<String, Integer> valueToItem = new HashMap<String, Integer>();
    //
    // for (int basePath : info.keySet()) {
    // final Map<Integer, CandidateInfo> itemInfo = info.get(basePath);
    // // if there is any approved value, then continue;
    // Status surveyWinningStatus = null;
    // String surveyWinningValue = null;
    //
    // // find the last release status and value
    // voteResolver.clear();
    // boolean haveOldStatus = false;
    //
    // valueToItem.clear();
    //
    // for (int item : itemInfo.keySet()) {
    // String itemValue = getValue(item);
    // String realPath = pathValueInfo.getRealPath(item);
    // if (realPath == null) {
    // logln(locale + ": \t!!! missing path for " + item);
    // continue;
    // }
    // String realValue = pathValueInfo.getRealValue(item);
    // if (realValue == null) {
    // logln(locale + ": \t!!! missing value for " + item);
    // continue;
    // }
    //
    // if (valueToItem.containsKey(itemValue)) {
    // errln(locale + ": \tTwo alternatives with same value:\t" + item + ", " +
    // itemValue);
    // } else {
    // valueToItem.put(itemValue, item);
    // }
    //
    // CandidateInfo candidateInfo = itemInfo.get(item);
    // oldStatusCounter.add(candidateInfo.oldStatus, 1);
    // surveyStatusCounter.add(candidateInfo.surveyStatus, 1);
    // surveyTypeCounter.add(candidateInfo.surveyType, 1);
    // if (candidateInfo.surveyType == Type.optimal) {
    // if (surveyWinningValue != null) {
    // errln(locale + ": \tDuplicate optimal item:\t" + item);
    // }
    // surveyWinningStatus = candidateInfo.surveyStatus;
    // surveyWinningValue = String.valueOf(item);
    // }
    // if (candidateInfo.oldStatus != null) {
    // if (haveOldStatus) {
    // errln(locale + ": \tDuplicate optimal item:\t" + item);
    // }
    // haveOldStatus = true;
    // voteResolver.setLastRelease(itemValue, candidateInfo.oldStatus);
    // }
    // voteResolver.add(itemValue);
    // for (int voter : candidateInfo.voters) {
    // try {
    // voteResolver.add(itemValue, voter);
    // } catch (UnknownVoterException e) {
    // if (!unknownVotersSoFar.contains(e.getVoter())) {
    // errln(locale + ":\t" + e);
    // unknownVotersSoFar.add(e.getVoter());
    // }
    // }
    // }
    // }
    //
    // if (voteResolver.size() == 0) {
    // logln(locale + ": \t!!! no values for " + basePath);
    // continue;
    // }
    // if (surveyWinningValue == null) {
    // missingOptimals.add(basePath);
    // surveyWinningValue = BAD_VALUE;
    // }
    //
    // EnumSet<Organization> basePathConflictedOrganizations =
    // voteResolver.getConflictedOrganizations();
    // conflictedOrganizations.addAll(basePathConflictedOrganizations);
    //
    // Status winningStatus = voteResolver.getWinningStatus();
    // String winningValue = voteResolver.getWinningValue();
    //
    // winningStatusCounter.add(winningStatus, 1);
    //
    // // we'll say the status is "good enough" if they have the same votes

    // final boolean sameResults = surveyWinningStatus == winningStatus
    // && voteResolver.getValuesWithSameVotes().contains(surveyWinningValue);
    // if (surveyWinningStatus == Status.approved && sameResults) {
    // continue;
    // }
    // if (!sameResults) {
    // surveyVsVoteResolverDifferences.add(basePath);
    // if (SHOW_DETAILS) {
    // showPaths(pathValueInfo, locale, basePath, itemInfo);
    // log("\t***Different results for:\t" + basePath);
    // if (surveyWinningStatus != winningStatus) {
    // log(", status ST:\t" + surveyWinningStatus);
    // log(", VR:\t" + winningStatus);
    // }
    // if (!voteResolver.getValuesWithSameVotes().contains(surveyWinningValue))
    // {
    // log(", value ST:\t" + surveyWinningValue);
    // log(", VR:\t" + winningValue);
    // }
    // logln("");
    // logln("\t\tVR:" + voteResolver);
    // }
    // }
    //
    // CandidateInfo candidateInfo =
    // itemInfo.get(valueToItem.get(winningValue));
    // Map<Organization, Level> orgToMaxVoteHere =
    // VoteResolver.getOrganizationToMaxVote(candidateInfo.voters);
    //
    // // if the winning item is less than contributed, record the organizations
    // that haven't given their maximum
    // // vote to the winning item.
    // if (winningStatus.compareTo(Status.contributed) < 0) {
    // // showPaths(basePath, itemInfo);
    // missingOrganizations.clear();
    // for (Organization org : orgToMaxVote.keySet()) {
    // Level maxVote = orgToMaxVote.get(org);
    // Level maxVoteHere = orgToMaxVoteHere.get(org);
    // if (maxVoteHere == null || maxVoteHere.compareTo(maxVote) < 0) {
    // missingOrganizations.add(org);
    // missingOrganizationCounter.add(org, 1);
    // }
    // }
    // // logln("&Missing organizations:\t" + missingOrganizations);
    // } else {
    // for (Organization org : orgToMaxVote.keySet()) {
    // Level maxVote = orgToMaxVote.get(org);
    // Level maxVoteHere = orgToMaxVoteHere.get(org);
    // if (maxVoteHere == null || maxVoteHere.compareTo(maxVote) < 0) {
    // } else {
    // goodOrganizationCounter.add(org, 1);
    // }
    // }
    // }
    // }
    // if (missingOptimals.size() != 0) {
    // errln(locale + ": \tSurvey Tool missing optimal item for basePaths:\t" +
    // missingOptimals);
    // }
    // if (surveyVsVoteResolverDifferences.size() > 0) {
    // errln(locale + ": \tSurvey Tool vs VoteResolver differences (approx):\t"
    // + surveyVsVoteResolverDifferences.size());
    // }
    // if (missingOrganizationCounter.size() > 0) {
    // if (SHOW_DETAILS) {
    // logln(locale + ": \toldStatus values:\t" + oldStatusCounter +
    // ", TOTAL:\t"
    // + oldStatusCounter.getTotal());
    // logln(locale + ": \tsurveyType values:\t" + surveyTypeCounter +
    // ", TOTAL:\t"
    // + surveyTypeCounter.getTotal());
    // logln(locale + ": \tsurveyStatus values:\t" + surveyStatusCounter +
    // ", TOTAL:\t"
    // + surveyStatusCounter.getTotal());
    // }
    // logln(locale + ": \tMIA organizations:\t" + missingOrganizationCounter);
    // logln(locale + ": \tConflicted organizations:\t" +
    // conflictedOrganizations);
    // logln(locale + ": \tCool organizations!:\t" + goodOrganizationCounter);
    // }
    // logln(locale + ": \tOptimal Status:\t" + winningStatusCounter);
    // }

    static class PathValueInfo {
        private static Map<Integer, String> voteInfo;
        private CLDRFile file;

        public PathValueInfo(Factory factory, String locale) {
            this.file = factory.make(locale, false);
        }

        public String getRealValue(int id) {
            return file.getStringValue(getRealPath(id));
        }

        public String getRealPath(int id) {
            return voteInfo.get(id);
        }
    }

    private void showPaths(PathValueInfo pathValueInfo, String locale,
        int basePath, final Map<Integer, CandidateInfo> itemInfo) {
        logln(locale + " basePath:\t" + basePath + "\t"
            + pathValueInfo.getRealPath(basePath));
        for (int item : itemInfo.keySet()) {
            CandidateInfo candidateInfo = itemInfo.get(item);
            logln("\tpath:\t" + item + ", " + candidateInfo);
            logln("\tvalue:\t" + pathValueInfo.getRealValue(item)
                + "\tpath:\t<" + pathValueInfo.getRealPath(item) + ">");
        }
    }
    
    /** Test user data. Restructured to be easier to read, more typesafe */
    enum TestUser {
        guestS(801, Organization.guest, Level.street),
        gnomeS(701, Organization.gnome, Level.street),
        googleV(404, Organization.google, Level.vetter),
        googleS(411, Organization.google, Level.street),
        googleV2(424, Organization.google, Level.vetter),
        appleV(304, Organization.apple, Level.vetter),
        adobeE(208, Organization.adobe, Level.expert),
        ibmS(101, Organization.ibm, Level.street),
        ibmV(134, Organization.ibm, Level.vetter),
        ibmE(118, Organization.ibm, Level.expert),
        ibmT(129, Organization.ibm, Level.tc),
        guestS2(802,Organization.guest, Level.street),
        ;
        public static final Map<Integer, VoterInfo> TEST_USERS;
        public final Integer voterId;
        public final VoterInfo voterInfo;
        
        TestUser(int intVoterId, Organization organization, Level level) {
            voterId = intVoterId;
            voterInfo = new VoterInfo(organization, level, name());
        }
        
        static {
            ImmutableMap.Builder<Integer, VoterInfo> temp = ImmutableMap.builder();
            for (TestUser testUser : values()) {
                temp.put(testUser.voterId, testUser.voterInfo);
            }
            TEST_USERS = temp.build();
//            for (Entry<Integer, VoterInfo> entry : TEST_USERS.entrySet()) {
//                int key = entry.getKey();
//                VoterInfo value = entry.getValue();
//                VoterInfo oldValue = testdata.get(key);
//                if (!Objects.equal(value, oldValue)) {
//                    System.out.println(key + "\t" + value + "\t" + oldValue);
//                }
//            }
//            for (Entry<Integer, VoterInfo> entry : testdata.entrySet()) {
//                int key = entry.getKey();
//                VoterInfo value = entry.getValue();
//                VoterInfo oldValue = TEST_USERS.get(key);
//                if (!Objects.equal(value, oldValue)) {
//                    System.out.println(key + "\t" + value + "\t" + oldValue);
//                }
//            }
//            if (TEST_USERS.size() != testdata.size()) {
//                throw new IllegalArgumentException();
//            }
        }
    }
    
    public static final Map<Integer, VoterInfo> testdata = TestUser.TEST_USERS;

    private int toVoterId(String s) {
        return TestUser.valueOf(s).voterId;
    }

    public void TestTrunkStatus() {
        VoteResolver.setVoterToInfo(testdata);
        VoteResolver<String> resolver = new VoteResolver<String>();
        resolver.setLocale("de");

        resolver.setLastRelease("old-item", Status.approved);
        resolver.setTrunk("new-item", Status.approved);
        assertEquals("", "new-item", resolver.getWinningValue());

        resolver.clear();
        resolver.setBaileyValue("bailey");

        resolver.setLastRelease("old-item", Status.approved);
        resolver.setTrunk("new-item", Status.provisional);
        assertEquals("", "old-item", resolver.getWinningValue());
    }

    public void TestVoteResolverNgombaTrunkStatus() {
        VoteResolver.setVoterToInfo(testdata);
        VoteResolver<String> resolver = new VoteResolver<String>();
        resolver.setLocale("jgo");
        final String jgo21 = "\uA78B"; // "[ɑ ɑ́ ɑ̂ ɑ̌ b c d ɛ {ɛ́} {ɛ̂} {ɛ̌} {ɛ̀} {ɛ̄} f ɡ h i í î ǐ j k l m ḿ {m̀} {m̄} n ń ǹ {n̄} ŋ {ŋ́} {ŋ̀} {ŋ̄} ɔ {ɔ́} {ɔ̂} {ɔ̌} p pf s sh t ts u ú û ǔ ʉ {ʉ́} {ʉ̂} {ʉ̌} {ʉ̈} v w ẅ y z Ꞌ]";
        final String jgo22trunk = "\uA78C"; // "[a á â ǎ b c d ɛ {ɛ́} {ɛ̂} {ɛ̌} {ɛ̀} {ɛ̄} f ɡ h i í î ǐ j k l m ḿ {m̀} {m̄} n ń ǹ {n̄} ŋ {ŋ́} {ŋ̀} {ŋ̄} ɔ {ɔ́} {ɔ̂} {ɔ̌} p {pf} s {sh} t {ts} u ú û ǔ ʉ {ʉ́} {ʉ̂} {ʉ̌} {ʉ̈} v w ẅ y z ꞌ]";
        Status oldStatus = Status.approved;
        resolver.setLastRelease(jgo21, oldStatus); // seed/jgo.xml from 21
        logln("Last release: " + jgo21 + ", " + oldStatus);
        resolver.setTrunk(jgo22trunk, Status.approved); // seed/jgo.xml from 22
        // trunk
        logln("SVN: " + jgo22trunk);
        logln(resolver.toString());
        assertEquals("Winning Value", jgo22trunk, resolver.getWinningValue());
    }

    public void TestVoteStatus() {
        VoteResolver.setVoterToInfo(testdata);
        VoteResolver<String> resolver = new VoteResolver<String>();

        resolver.setLocale("de");
        resolver.setBaileyValue("bailey");
        resolver.setLastRelease("foo", Status.approved);
        resolver.add("fii", toVoterId("adobeE"));
        resolver.add("fii", toVoterId("appleV"));
        VoteStatus voteStatus;
        voteStatus = resolver.getStatusForOrganization(Organization.google);
        assertEquals("", VoteStatus.ok, voteStatus);
        voteStatus = resolver.getStatusForOrganization(Organization.apple);
        assertEquals("", VoteStatus.ok, voteStatus);

        // make non-equal foo
        String s1 = "foo";
        String s2 = new StringBuilder("fo").append("o").toString();
        if (s1 == s2) {
            errln("Test problem");
        }
        resolver.clear();
        resolver.setBaileyValue("bailey");
        resolver.setLastRelease(s1, Status.approved);
        resolver.add(s2, toVoterId("appleV"));
        voteStatus = resolver.getStatusForOrganization(Organization.apple);
        assertEquals("", VoteStatus.ok, voteStatus);
    }

    public void TestLosingStatus() {
        // af
        // losing? {lastRelease: {BQ, missing}, trunk: {null, null},
        // {orgToVotes: , totals: {}, conflicted: []},
        // sameVotes: [BQ], O: null, N: null, totals: {}, winning: {BQ,
        // missing}}
        // XPath: //ldml/localeDisplayNames/territories/territory[@type="BQ"]
        // gcvs.openoffice_org.example.com
        VoteResolver.setVoterToInfo(testdata);
        VoteResolver<String> resolver = new VoteResolver<String>();

        resolver.setLocale("af");
        resolver.setLastRelease("BQ", Status.missing);
        VoteStatus status = resolver
            .getStatusForOrganization(Organization.openoffice_org);
        assertEquals("", VoteStatus.provisionalOrWorse, status);

        // {lastRelease: {{0}: {1}, missing}, trunk: {null, null}, {orgToVotes:
        // pakistan={{0}: {1}=8}, totals: {{0}:
        // {1}=8}, conflicted: []}, sameVotes: [{0}: {1}], O: {0}: {1}, N: null,
        // totals: {{0}: {1}=8}, winning: {{0}:
        // {1}, approved}}
        resolver.clear();
        resolver.setBaileyValue("bailey");
        resolver.setLastRelease("{0}: {1}", Status.missing);
        resolver.add("{0}: {1}", toVoterId("adobeE"));
        status = resolver.getStatusForOrganization(Organization.openoffice_org);
        assertEquals("", VoteStatus.ok, status);

        // {lastRelease: {Arabisch, approved}, trunk: {Arabisch, approved},
        // {orgToVotes: , totals: {}, conflicted: []},
        // sameVotes: [Arabisch], O: null, N: null, totals: {}, winning:
        // {Arabisch, approved}}
        resolver.clear();
        resolver.setBaileyValue("bailey");
        resolver.setLastRelease("Arabisch", Status.approved);
        resolver.setTrunk("Arabisch", Status.approved);
        status = resolver.getStatusForOrganization(Organization.openoffice_org);
        assertEquals("", VoteStatus.ok_novotes, status);
    }

    public void TestTotalVotesStatus() {
        VoteResolver.setVoterToInfo(testdata);
        VoteResolver<String> resolver = new VoteResolver<String>();

        Status oldStatus = Status.unconfirmed;

        resolver.setBaileyValue("bailey");
        resolver.setLocale("de");
        resolver.setLastRelease("foo", oldStatus);
        resolver.add("zebra", toVoterId("googleV"));
        resolver.add("apple", toVoterId("appleV"));

        // check that alphabetical wins when votes are equal
        String winner = resolver.getWinningValue();
        Status winningStatus = resolver.getWinningStatus();
        assertEquals("", "apple", winner);
        assertEquals("", Status.provisional, winningStatus);

        resolver.clear();
        resolver.setBaileyValue("bailey");
        resolver.setLocale("de");
        resolver.setLastRelease("foo", oldStatus);
        resolver.add("zebra", toVoterId("googleV"));
        resolver.add("zebra", toVoterId("googleS"));
        resolver.add("apple", toVoterId("appleV"));

        // check that total votes over alphabetical
        winner = resolver.getWinningValue();
        winningStatus = resolver.getWinningStatus();
        assertEquals("", "zebra", winner);
        assertEquals("", Status.provisional, winningStatus);
    }

    public void TestVoteDowngrade() {
        VoteResolver.setVoterToInfo(testdata);
        VoteResolver<String> resolver = new VoteResolver<String>();

        Status oldStatus = Status.unconfirmed;

        resolver.setBaileyValue("bailey");
        resolver.setLocale("mt");
        resolver.setLastRelease("foo", oldStatus);
        resolver.add("aardvark", toVoterId("adobeE"));
        resolver.add("zebra", toVoterId("ibmT"));
        assertEquals("", "zebra", resolver.getWinningValue()); // TC vote of 20
        // beats
        // expert's 8
        assertEquals("", Status.approved, resolver.getWinningStatus());

        resolver.clear();
        resolver.setBaileyValue("bailey");
        resolver.setLocale("mt");
        resolver.setLastRelease("foo", oldStatus);
        resolver.add("aardvark", toVoterId("adobeE"));
        resolver.add("zebra", toVoterId("ibmT"));
        resolver.add("aardvark", toVoterId("ibmE"));
        assertEquals("", "zebra", resolver.getWinningValue()); // TC vote of 20
        // beats
        // expert's 8
        // and its own
        // expert's 8
        assertEquals("", Status.approved, resolver.getWinningStatus());

        resolver.clear();
        resolver.setBaileyValue("bailey");
        resolver.setLocale("mt");
        resolver.setLastRelease("foo", oldStatus);
        resolver.add("aardvark", toVoterId("adobeE"));
        resolver.add("zebra", toVoterId("ibmT"), Level.vetter.getVotes()); // NOTE:
        // reduced
        // votes:
        // as
        // vetter.
        resolver.add("aardvark", toVoterId("ibmE"));
        assertEquals("", "aardvark", resolver.getWinningValue()); // Now
        // aardvark
        // wins -
        // experts
        // win out.
        assertEquals("", Status.approved, resolver.getWinningStatus());

        resolver.clear();
        resolver.setBaileyValue("bailey");
        resolver.setLocale("mt");
        resolver.setLastRelease("foo", oldStatus);
        resolver.add("aardvark", toVoterId("adobeE"));
        resolver.add("zebra", toVoterId("ibmT"), Level.vetter.getVotes()); // NOTE:
        // reduced
        // votes:
        // as
        // vetter.
        assertEquals("", "aardvark", resolver.getWinningValue()); // Now
        // aardvark
        // wins -
        // experts
        // win out.
        assertEquals("", Status.approved, resolver.getWinningStatus());
    }

    public void TestResolvedVoteCounts() {
        VoteResolver.setVoterToInfo(testdata);
        VoteResolver<String> resolver = new VoteResolver<String>();

        Status oldStatus = Status.unconfirmed;

        resolver.setBaileyValue("bailey");
        resolver.setLocale("de");
        resolver.setLastRelease("foo", oldStatus);
        resolver.setTrunk("foo", oldStatus);
        resolver.add("zebra", toVoterId("googleV"));
        resolver.add("apple", toVoterId("appleV"));

        // check that alphabetical wins when votes are equal
        Map<String, Long> counts = resolver.getResolvedVoteCounts();
        logln(counts.toString());
        assertEquals("", "foo", new ArrayList<String>(counts.keySet()).get(2));

        resolver.clear();
        resolver.setBaileyValue("bailey");
        resolver.setLocale("de");
        resolver.setLastRelease("foo", Status.approved);
        resolver.setTrunk("foo", Status.approved);
        resolver.add("zebra", toVoterId("googleV"));
        resolver.add("apple", toVoterId("appleV"));
        counts = resolver.getResolvedVoteCounts();
        logln(counts.toString());
        assertEquals("", "foo", new ArrayList<String>(counts.keySet()).get(0));

        resolver.clear();
        resolver.setBaileyValue("bailey");
        resolver.setLocale("de");
        resolver.setLastRelease("foo", Status.approved);
        resolver.setTrunk("foo", Status.approved);
        resolver.add("zebra", toVoterId("googleS"));
        counts = resolver.getResolvedVoteCounts();
        logln(counts.toString());
        assertEquals("", "foo", new ArrayList<String>(counts.keySet()).get(0));
    }

    private void verifyRequiredVotes(VoteResolver resolver, String locale,
        String xpath, int required) {
        StringBuilder sb = new StringBuilder();
        sb.append("Locale: " + locale);
        resolver.clear();
        resolver.setBaileyValue("bailey");
        PathHeader ph = null;
        if (xpath != null) {
            sb.append(" XPath: " + xpath);
            ph = PathHeader.getFactory(testInfo.getEnglish())
                .fromPath(xpath);
        }
        resolver.setLocale(CLDRLocale.getInstance(locale), ph);
        assertEquals(ph.toString(), required, resolver.getRequiredVotes());
    }

    public void TestRequiredVotes() {
        VoteResolver.setVoterToInfo(testdata);
        VoteResolver<String> resolver = new VoteResolver<String>();

        verifyRequiredVotes(
            resolver,
            "mt",
            "//ldml/localeDisplayNames/languages/language[@type=\"fr_CA\"]",
            4);
        verifyRequiredVotes(
            resolver,
            "fr",
            "//ldml/localeDisplayNames/languages/language[@type=\"fr_CA\"]",
            8);

        assertEquals("VoteResolver.HIGH_BAR", VoteResolver.HIGH_BAR, 20);
        verifyRequiredVotes(resolver, "es",
            "//ldml/numbers/symbols[@numberSystem=\"latn\"]/group",
            VoteResolver.HIGH_BAR); // == 20
        verifyRequiredVotes(resolver, "es",
            "//ldml/numbers/symbols[@numberSystem=\"latn\"]/decimal",
            VoteResolver.HIGH_BAR);
        verifyRequiredVotes(resolver, "hi",
            "//ldml/numbers/symbols[@numberSystem=\"deva\"]/decimal",
            VoteResolver.HIGH_BAR);
        verifyRequiredVotes(resolver, "hi",
            "//ldml/numbers/symbols[@numberSystem=\"deva\"]/group",
            VoteResolver.HIGH_BAR);
        verifyRequiredVotes(resolver, "ast",
            "//ldml/numbers/symbols[@numberSystem=\"latn\"]/decimal", 4);
        verifyRequiredVotes(resolver, "mt",
            "//ldml/characters/exemplarCharacters", VoteResolver.HIGH_BAR);
        verifyRequiredVotes(resolver, "mt",
            "//ldml/characters/exemplarCharacters[@type=\"auxiliary\"]",
            VoteResolver.HIGH_BAR);
        verifyRequiredVotes(resolver, "mt",
            "//ldml/characters/exemplarCharacters[@type=\"index\"]",
            VoteResolver.HIGH_BAR);
        verifyRequiredVotes(resolver, "mt",
            "//ldml/characters/exemplarCharacters[@type=\"punctuation\"]",
            4);
        verifyRequiredVotes(
            resolver,
            "es",
            "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/days/dayContext[@type=\"format\"]/dayWidth[@type=\"wide\"]/day[@type=\"sun\"]",
            VoteResolver.HIGH_BAR);
        verifyRequiredVotes(
            resolver,
            "ast",
            "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/days/dayContext[@type=\"format\"]/dayWidth[@type=\"wide\"]/day[@type=\"sun\"]",
            4);
        verifyRequiredVotes(
            resolver,
            "es",
            "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/months/monthContext[@type=\"format\"]/monthWidth[@type=\"wide\"]/month[@type=\"1\"]",
            VoteResolver.HIGH_BAR);
        verifyRequiredVotes(
            resolver,
            "ast",
            "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/months/monthContext[@type=\"format\"]/monthWidth[@type=\"wide\"]/month[@type=\"1\"]",
            4);
    }

    public void TestVoteResolver() {
        // to make it easier to debug failures, the first digit is an org,
        // second is the individual in that org, and
        // third is the voting weight.
        VoteResolver.setVoterToInfo(testdata);
        VoteResolver<String> resolver = new VoteResolver<String>();
        String[] tests = {
            "bailey=BAILEY",
            "comment=regression case from John Emmons",
            "locale=wae",
            "oldValue=2802",
            "oldStatus=approved",
            "304=208027", // Apple vetter
            // expected values
            "value=208027",
            "status=approved",
            "sameVotes=208027",
            "conflicts=[]",
            "check",
            // first test
            "oldValue=old-value",
            "oldStatus=provisional",
            "comment=Check that identical values get the top overall vote, and that org is maxed (eg vetter + street = vetter)",
            "404=next",
            "411=next",
            "304=best",
            // expected values
            "value=next",
            "sameVotes=next,best",
            "conflicts=[]",
            "status=provisional",
            "check",

            "comment=now give next a slight edge (5 to 4) with a different organization",
            "404=next",
            "304=best",
            "801=next",
            // expected values
            "value=next",
            "sameVotes=next",
            "status=approved",
            "check",

            "comment=set up a case of conflict within organization",
            "404=next",
            "424=best",
            // expected values
            "value=best", // alphabetical
            "sameVotes=best",
            "conflicts=[google]",
            "status=approved",
            "check",

            "comment=now cross-organizational conflict, also check for max value in same organization (4, 1) => 4 not 5",
            "404=next",
            "424=best",
            "411=best",
            "304=best",
            // expected values
            "conflicts=[google]",
            "value=best",
            "sameVotes=best",
            "status=approved",
            "check",

            "comment=now clear winner 8 over 4",
            "404=next",
            // "424=best",
            "411=best",
            "304=best",
            "208=primo",
            // expected values
            "conflicts=[]",
            "value=primo",
            "sameVotes=primo",
            "status=approved",
            "check",

            "comment=now not so clear, throw in a street value. So it is 8 to 5. (used to be provisional)",
            "404=next",
            // "424=best",
            "411=best",
            "304=best",
            "208=primo",
            "101=best",
            // expected values
            "conflicts=[]",
            "value=primo",
            "status=approved",
            "check",

            "comment=set up vote of 4 in established locale, with old provisional value",
            "locale=fr",
            "404=best",
            "oldStatus=provisional",
            // expected values
            "value=best",
            "sameVotes=best",
            "status=contributed",
            "conflicts=[]",
            "check",

            "comment=now set up vote of 4 in established locale, but with old contributed value",
            "oldStatus=contributed",
            // expected values
            "value=old-value",
            "sameVotes=old-value",
            "status=contributed",
            "conflicts=[]",
            "check",

            "comment=now set up vote of 1 + 1 in established locale, and with old contributed value",
            "411=best", "101=best", "oldStatus=contributed",
            // expected values
            "value=best", "sameVotes=best", "status=contributed",
            "conflicts=[]", "check", };
        String expectedValue = null;
        String expectedConflicts = null;
        Status expectedStatus = null;
        String oldValue = null;
        Status oldStatus = null;
        String baileyValue = null;
        List<String> sameVotes = null;
        String locale = null;
        Map<Integer, String> values = new TreeMap<Integer, String>();
        int counter = -1;

        for (String test : tests) {
            String[] item = test.split("=");
            String name = item[0];
            String value = item.length < 2 ? null : item[1];
            if (name.equalsIgnoreCase("comment")) {
                logln("#\t" + value);
                //System.out.println("#\t" + value);
                if (DEBUG_COMMENT != null && value.contains(DEBUG_COMMENT)) {
                    int x = 0;
                }
            } else if (name.equalsIgnoreCase("locale")) {
                locale = value;
            } else if (name.equalsIgnoreCase("bailey")) {
                baileyValue = value;
            } else if (name.equalsIgnoreCase("oldValue")) {
                oldValue = value;
            } else if (name.equalsIgnoreCase("oldStatus")) {
                oldStatus = Status.valueOf(value);
            } else if (name.equalsIgnoreCase("value")) {
                expectedValue = value;
            } else if (name.equalsIgnoreCase("sameVotes")) {
                sameVotes = value == null ? new ArrayList<String>(0) : Arrays
                    .asList(value.split(",\\s*"));
            } else if (name.equalsIgnoreCase("status")) {
                expectedStatus = Status.valueOf(value);
            } else if (name.equalsIgnoreCase("conflicts")) {
                expectedConflicts = value;
            } else if (DIGITS.containsAll(name)) {
                final int voter = Integer.parseInt(name);
                if (value == null || value.equals("null")) {
                    values.remove(voter);
                } else {
                    values.put(voter, value);
                }
            } else if (name.equalsIgnoreCase("check")) {
                counter++;
                // load the resolver
                resolver.setBaileyValue(baileyValue);
                resolver.setLocale(locale);
                resolver.setLastRelease(oldValue, oldStatus);
                for (int voter : values.keySet()) {
                    resolver.add(values.get(voter), voter);
                }
                // print the contents
                logln(counter + "\t" + values);
                logln(resolver.toString());
                // now print the values
                assertEquals(counter + " value", expectedValue,
                    resolver.getWinningValue());
                assertEquals(counter + " sameVotes", sameVotes.toString(),
                    resolver.getValuesWithSameVotes().toString());
                assertEquals(counter + " status", expectedStatus,
                    resolver.getWinningStatus());
                assertEquals(counter + " conflicts", expectedConflicts,
                    resolver.getConflictedOrganizations().toString());
                resolver.clear();
                resolver.setBaileyValue("bailey");
                values.clear();
            } else {
                errln("unknown command:\t" + test);
            }
        }
    }

    void assertSpecialLocale(String loc, SpecialLocales.Type type) {
        assertEquals("SpecialLocales.txt for " + loc, type,
            SpecialLocales.getType(CLDRLocale.getInstance(loc)));
    }

    public void TestSpecialLocales() {
        assertSpecialLocale("sr", null);
        assertSpecialLocale("sr_Latn", SpecialLocales.Type.readonly);
        assertSpecialLocale("sr_Latn_BA", SpecialLocales.Type.readonly);
        assertSpecialLocale("en", SpecialLocales.Type.readonly);
        assertSpecialLocale("en_ZZ", SpecialLocales.Type.readonly);
        assertSpecialLocale("en_ZZ_PROGRAMMERESE", null); // not defined
        assertSpecialLocale("und", SpecialLocales.Type.scratch);
        assertSpecialLocale("und_001", null); // not defined

        CLDRLocale sr_Latn = CLDRLocale.getInstance("sr_Latn");
        CLDRLocale sr_Latn_BA = CLDRLocale.getInstance("sr_Latn_BA");
        logln("sr_Latn raw comment = " + SpecialLocales.getCommentRaw(sr_Latn));
        assertTrue("sr_Latn raw contains @ sign",
            SpecialLocales.getCommentRaw(sr_Latn).contains("@"));

        logln("sr_Latn comment = " + SpecialLocales.getComment(sr_Latn));
        assertTrue("sr_Latn comment does NOT contain @ sign", !SpecialLocales
            .getComment(sr_Latn).contains("@"));
        logln("sr_Latn_BA raw comment = "
            + SpecialLocales.getCommentRaw(sr_Latn_BA));
        assertTrue("sr_Latn_BA raw contains '@sr_Latn_BA'", SpecialLocales
            .getCommentRaw(sr_Latn_BA).contains("@sr_Latn_BA"));

    }

    public void TestCLDRURLS() {
        final String KOREAN_LANGUAGE = "//ldml/localeDisplayNames/languages/language[@type=\"ko\"]";
        final String KOREAN_LANGUAGE_STRID = "821c2a2fc5c206d";
        final CLDRLocale maltese = CLDRLocale.getInstance("mt");
        assertEquals("base", "http://st.unicode.org/cldr-apps", CLDRConfig
            .getInstance().urls().base());
        assertEquals(
            "locales list",
            "http://st.unicode.org/cldr-apps/v#locales///",
            CLDRConfig.getInstance().urls()
            .forSpecial(CLDRURLS.Special.Locales));
        assertEquals("maltese", "http://st.unicode.org/cldr-apps/v#/mt//",
            CLDRConfig.getInstance().urls().forLocale(maltese));
        assertEquals("korean in maltese",
            "http://st.unicode.org/cldr-apps/v#/mt//"
                + KOREAN_LANGUAGE_STRID, CLDRConfig.getInstance()
                .urls().forXpath(maltese, KOREAN_LANGUAGE));
        assertEquals("korean in maltese via stringid",
            "http://st.unicode.org/cldr-apps/v#/mt//"
                + KOREAN_LANGUAGE_STRID, CLDRConfig.getInstance()
                .urls().forXpathHexId(maltese, KOREAN_LANGUAGE_STRID));
        assertEquals("south east asia in maltese",
            "http://st.unicode.org/cldr-apps/v#/mt/C_SEAsia/", CLDRConfig
            .getInstance().urls().forPage(maltese, PageId.C_SEAsia));
        try {
            String ret = CLDRConfig.getInstance().urls()
                .forXpathHexId(maltese, KOREAN_LANGUAGE);
            errln("Error- expected forXpathHexId to choke on  an xpath but got "
                + ret);
        } catch (IllegalArgumentException iae) {
            logln("GOOD: forXpathHexId Caught expected " + iae);
        }
        try {
            String ret = CLDRConfig.getInstance().urls()
                .forXpath(maltese, KOREAN_LANGUAGE_STRID);
            errln("Error- expected forXpath to choke on  a hexid but got "
                + ret);
        } catch (IllegalArgumentException iae) {
            logln("GOOD: forXpath Caught expected " + iae);
        }

        assertEquals("korean in maltese - absoluteUrl",
            "http://st.unicode.org/cldr-apps/v#/mt//"
                + KOREAN_LANGUAGE_STRID, CLDRConfig.getInstance()
                .absoluteUrls().forXpath(maltese, KOREAN_LANGUAGE));

    }


    static final UnicodeMap<String> SCRIPTS = ICUPropertyFactory.make().getProperty("script").getUnicodeMap_internal();
    static final UnicodeMap<String> GC = ICUPropertyFactory.make().getProperty("general_category").getUnicodeMap_internal();

    public void TestUnicodeMapCompose() {
        logln("Getting Scripts");

        UnicodeMap.Composer<String> composer = new UnicodeMap.Composer<String>() {
            @Override
            public String compose(int codepoint, String string, String a, String b) {
                return a.toString() + "_" + b.toString();
            }
        };

        logln("Trying Compose");

//        Map<Integer, String> map2 = new HashMap<Integer, String>();
//        Map<Integer, String> map3 = new TreeMap<Integer, String>();
        UnicodeMap<String> composed = ((UnicodeMap)SCRIPTS.cloneAsThawed()).composeWith(GC, composer);
        String last = "";
        for (int i = 0; i < 0x10FFFF; ++i) {
//            if (i == 888) {
//                int debug = 0;
//            }
            String comp = composed.getValue(i);
            String gc = GC.getValue(i);
            String sc = SCRIPTS.getValue(i);
            if (!comp.equals(composer.compose(i, null, sc, gc))) {
                errln("Failed compose at: " + i);
                break;
            }
            if (!last.equals(comp)) {
                logln(Utility.hex(i) + "\t" + comp);
                last = comp;
            }
        }
    }

    private static final int SET_LIMIT = 0x10FFFF;
    private static final int CHECK_LIMIT = 0xFFFF;
    private static final NumberFormat pf = NumberFormat.getPercentInstance();
    private static final NumberFormat nf = NumberFormat.getInstance();

    public void TestUnicodeMapTime() {
        boolean shortTest = getInclusion() < 10;
        double hashTime, umTime, icuTime, treeTime;
        int warmup = shortTest ? 1 : 20;
        umTime = checkUnicodeMapSetTime(warmup, 0);
        hashTime = checkUnicodeMapSetTime(warmup, 1);
        logln("Percentage: " + pf.format(hashTime/umTime));
        treeTime = checkUnicodeMapSetTime(warmup, 3);
        logln("Percentage: " + pf.format(treeTime/umTime));
        //logln(map1.toString());

        if (shortTest) {
            return;
        }

        umTime = checkUnicodeMapGetTime(1000, 0);
        hashTime = checkUnicodeMapGetTime(1000, 1);
        logln("Percentage: " + pf.format(hashTime/umTime));
        icuTime = checkUnicodeMapGetTime(1000, 2);
        logln("Percentage: " + pf.format(icuTime/umTime));
        treeTime = checkUnicodeMapGetTime(1000, 3);
        logln("Percentage: " + pf.format(treeTime/umTime));
    }

    private static final int propEnum = UProperty.GENERAL_CATEGORY;

    private double checkUnicodeMapSetTime(int iterations, int type) {
        _checkUnicodeMapSetTime(1,type);
        double result = _checkUnicodeMapSetTime(iterations, type);
        logln((type == 0 ? "UnicodeMap" : type == 1 ? "HashMap" : type == 2 ? "ICU" : "TreeMap") + "\t" + nf.format(result));
        return result;
    }

    private double _checkUnicodeMapSetTime(int iterations, int type) {
        UnicodeMap<String> map1 = SCRIPTS;
        Map<Integer,String> map2 = map1.putAllCodepointsInto(new HashMap<Integer,String>());
        Map<Integer, String> map3 = new TreeMap<Integer, String>(map2);
        System.gc();
        double start = System.currentTimeMillis();
        for (int j = 0; j < iterations; ++j)
            for (int cp = 0; cp <= SET_LIMIT; ++cp) {
                int enumValue = UCharacter.getIntPropertyValue(cp, propEnum);
                if (enumValue <= 0) continue; // for smaller set
                String value = UCharacter.getPropertyValueName(propEnum,enumValue, UProperty.NameChoice.LONG);
                switch(type) {
                case 0: map1.put(cp, value); break;
                case 1: map2.put(cp, value); break;
                case 3: map3.put(cp, value); break;
                }
            }
        double end = System.currentTimeMillis();
        return (end-start)/1000/iterations;
    }

    private double checkUnicodeMapGetTime(int iterations, int type) {
        UnicodeMap<String> map1 = new UnicodeMap<String>();
        Map<Integer,String> map2 = map1.putAllCodepointsInto(new HashMap<Integer,String>());
        Map<Integer, String> map3 = new TreeMap<Integer, String>();
        _checkUnicodeMapGetTime(map1, map2, map3, 1,type); // warmup
        double result = _checkUnicodeMapGetTime(map1, map2, map3, iterations, type);
        logln((type == 0 ? "UnicodeMap" : type == 1 ? "HashMap" : type == 2 ? "ICU" : "TreeMap") + "\t" + nf.format(result));
        return result;
    }

    private double _checkUnicodeMapGetTime(UnicodeMap<String> map1, Map<Integer,String> map2, Map<Integer,String> map3, int iterations, int type) {
        System.gc();
        double start = System.currentTimeMillis();
        for (int j = 0; j < iterations; ++j)
            for (int cp = 0; cp < CHECK_LIMIT; ++cp) {
                switch (type) {
                case 0: map1.getValue(cp); break;
                case 1: map2.get(cp); break;
                case 2:
                    int enumValue = UCharacter.getIntPropertyValue(cp, propEnum);
                    //if (enumValue <= 0) continue;
                    UCharacter.getPropertyValueName(propEnum,enumValue, UProperty.NameChoice.LONG);
                    break;                
                case 3: map3.get(cp); break;
                }
            }
        double end = System.currentTimeMillis();
        return (end-start)/1000/iterations;
    }


    public void TestStevenTest(){

        VoteResolver.setVoterToInfo(testdata);
        VoteResolver<String> resolver = new VoteResolver<String>();

        String tests[] = {
            "bailey=BAILEY",
            "comment=Steven Loomis test case tweaked by Parthinator",
            "locale=wae",
            "oldValue=_",
            "oldStatus=approved",
            "304=test", // Apple vetter
            // expected values
            "value=test",
            "status=approved",
            "sameVotes=test",
            "conflicts=[]",
            "check",


            //test1
            "comment=timestamp case1",
            "locale=de",
            "oldValue=old-value",
            "oldStatus=provisional",
            "404=Foo",
            "424=Bar",
            //expected
            "value=Bar",
            "status=provisional",
            "sameVotes=Bar, test",
            "conflicts=[google]",
            "check",

            //test2
            "comment=timestamp case2",
            "locale=de",
            "oldValue=Bar",
            "oldStatus=provisional",
            "424=Foo",
            "404=Bar",
            // expected values
            "value=Bar",
            "status=provisional",
            "sameVotes=Bar, test",
            "conflicts=[google]",
            "check",

            //test 3
            "comment=timestamp guest case",
            "locale=de",
            "oldValue=_",
            "oldStatus=unconfirmed",
            //# // G vetter A
            //timestamp=1
            "801=Foo",
            //timestamp=2
            "802=Bar",
            // expected values
            "value=Bar",
            "status=contributed",
            "sameVotes=Bar",
            "conflicts=[google, guest]",
            "check",
        };

        String expectedValue = null;
        String expectedConflicts = null;
        Status expectedStatus = null;
        String oldValue = null;
        Status oldStatus = null;
        String baileyValue = null;
        List<String> sameVotes = null;
        String locale = null;
        int voteEntries = 0;
        Map<Integer, String> values = new TreeMap<Integer, String>();
        Map<Integer, VoteEntries> valuesMap = new TreeMap<Integer, VoteEntries>();

        int counter = -1;

        for (String test : tests) {
            String[] item = test.split("=");
            String name = item[0];
            String value = item.length < 2 ? null : item[1];
            if (name.equalsIgnoreCase("comment")) {
                logln("#\t" + value);
                //System.out.println("#\t" + value);
                if (DEBUG_COMMENT != null && value.contains(DEBUG_COMMENT)) {
                    int x = 0;
                }
            } else if (name.equalsIgnoreCase("locale")) {
                locale = value;
            } else if (name.equalsIgnoreCase("oldValue")) {
                oldValue = value;
            } else if (name.equalsIgnoreCase("oldStatus")) {
                oldStatus = Status.valueOf(value);
            } else if (name.equalsIgnoreCase("value")) {
                expectedValue = value;
            } else if (name.equalsIgnoreCase("bailey")) {
                baileyValue = value;
            } else if (name.equalsIgnoreCase("sameVotes")) {
                sameVotes = value == null ? new ArrayList<String>(0) : Arrays
                    .asList(value.split(",\\s*"));
            } else if (name.equalsIgnoreCase("status")) {
                expectedStatus = Status.valueOf(value);
            } else if (name.equalsIgnoreCase("conflicts")) {
                expectedConflicts = value;
            } else if (DIGITS.containsAll(name)) {
                final int voter = Integer.parseInt(name);
                if (value == null || value.equals("null")) {
                    values.remove(voter);
                    for(Map.Entry<Integer, VoteEntries> entry : valuesMap.entrySet()){
                        if(entry.getValue().getVoter() == voter){
                            valuesMap.remove(entry.getKey());
                        }
                    }
                } else {
                    values.put(voter, value);
                    valuesMap.put(++voteEntries, new VoteEntries(voter, value));
                }
            } else if (name.equalsIgnoreCase("check")) {
                counter++;
                // load the resolver
                resolver.setBaileyValue(baileyValue);
                resolver.setLocale(locale);
                resolver.setLastRelease(oldValue, oldStatus);
                for (int voteEntry : valuesMap.keySet()) {

                    resolver.add(valuesMap.get(voteEntry).getValue(), valuesMap.get(voteEntry).getVoter());
                }
                // print the contents
                logln(counter + "\t" + values);
                logln(resolver.toString());
                // now print the values
                assertEquals(counter + " value", expectedValue,
                    resolver.getWinningValue());
                assertEquals(counter + " sameVotes", sameVotes.toString(),
                    resolver.getValuesWithSameVotes().toString());
                assertEquals(counter + " status", expectedStatus,
                    resolver.getWinningStatus());
                assertEquals(counter + " conflicts", expectedConflicts,
                    resolver.getConflictedOrganizations().toString());
                resolver.clear();
                values.clear();
            } else {
                errln("unknown command:\t" + test);
            }
        }
    }

    public void testBaileyVotes() {
        VoteResolver.setVoterToInfo(TestUser.TEST_USERS);
        VoteResolver<String> resolver = new VoteResolver<String>();
        VoteStatus voteStatus;

        // Simple case, all = bailey
        resolver.setLocale("de");
        resolver.setBaileyValue("bailey");
        resolver.setTrunk("foo", Status.approved);
        
        resolver.add("bailey", TestUser.appleV.voterId);
        resolver.add("bailey", TestUser.ibmV.voterId);
        resolver.add("bailey", TestUser.googleV.voterId);
        assertEquals("Simple case, all = bailey", "bailey", resolver.getWinningValue());
        
        resolver.clear();
        resolver.setLocale("de");
        resolver.setBaileyValue("bailey");
        resolver.setTrunk("foo", Status.approved);
        
        resolver.add("bailey", TestUser.appleV.voterId);
        resolver.add(CldrUtility.INHERITANCE_MARKER, TestUser.ibmV.voterId);
        resolver.add(CldrUtility.INHERITANCE_MARKER, TestUser.googleV.voterId);
        assertEquals("The bailey value and explicit value combine to win", "bailey", resolver.getWinningValue());
        
        // The bailey value and explicit value combine to win
        resolver.clear();
        resolver.setLocale("de");
        resolver.setBaileyValue("bailey");
        resolver.setTrunk("foo", Status.approved);
        
        resolver.add("bailey", TestUser.appleV.voterId);
        resolver.add(CldrUtility.INHERITANCE_MARKER, TestUser.ibmV.voterId);
        resolver.add("other-vote", TestUser.googleV.voterId);
        assertEquals("The bailey value and explicit value combine to win", "bailey", resolver.getWinningValue());
        
        // The bailey value and explicit value combine to win
        resolver.clear();
        resolver.setLocale("de");
        resolver.setBaileyValue("bailey");
        resolver.setTrunk("foo", Status.approved);
        
        resolver.add("bailey", TestUser.appleV.voterId);
        resolver.add("not-bailey", TestUser.ibmV.voterId);
        resolver.add("other-vote", TestUser.googleV.voterId);
        assertEquals("Split vote, no action", "foo", resolver.getWinningValue());
        
        // Currently the only case where CldrUtility.INHERITANCE_MARKER wins is where they all are.
        resolver.clear();
        resolver.setLocale("de");
        resolver.setBaileyValue("bailey");
        resolver.setTrunk("foo", Status.approved);
        
        resolver.add(CldrUtility.INHERITANCE_MARKER, TestUser.googleV.voterId);
        resolver.add(CldrUtility.INHERITANCE_MARKER, TestUser.appleV.voterId);
        resolver.add(CldrUtility.INHERITANCE_MARKER, TestUser.ibmV.voterId);
        assertEquals("Currently the only case where CldrUtility.INHERITANCE_MARKER wins is where they all are", CldrUtility.INHERITANCE_MARKER, resolver.getWinningValue());
    }
}


