// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
 **********************************************************************
 * Copyright (c) 2015, International Business Machines
 * Corporation and others.  All Rights Reserved.
 **********************************************************************
 * Author: Alan Liu
 * Created: January 14 2004
 * Since: ICU 2.8
 **********************************************************************
 */
package com.ibm.icu.dev.test.util;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import com.ibm.icu.dev.test.TestFmwk;
import com.ibm.icu.text.Collator;
import com.ibm.icu.text.DisplayContext;
import com.ibm.icu.text.DisplayContext.Type;
import com.ibm.icu.text.LocaleDisplayNames;
import com.ibm.icu.text.LocaleDisplayNames.UiListItem;
import com.ibm.icu.util.IllformedLocaleException;
import com.ibm.icu.util.ULocale;

@RunWith(JUnit4.class)
public class ULocaleCollationTest extends TestFmwk {
    @Test
    public void TestCollator() {
        checkService("ja_JP_YOKOHAMA", new ServiceFacade() {
            @Override
            public Object create(ULocale req) {
                return Collator.getInstance(req);
            }
        }, null, new Registrar() {
            @Override
            public Object register(ULocale loc, Object prototype) {
                return Collator.registerInstance((Collator) prototype, loc);
            }
            @Override
            public boolean unregister(Object key) {
                return Collator.unregister(key);
            }
        });
    }


    /**
     * Interface used by checkService defining a protocol to create an
     * object, given a requested locale.
     */
    interface ServiceFacade {
        Object create(ULocale requestedLocale);
    }

    /**
     * Interface used by checkService defining a protocol to get a
     * contained subobject, given its parent object.
     */
    interface Subobject {
        Object get(Object parent);
    }

    /**
     * Interface used by checkService defining a protocol to register
     * and unregister a service object prototype.
     */
    interface Registrar {
        Object register(ULocale loc, Object prototype);
        boolean unregister(Object key);
    }



    /**
     * Compare two locale IDs.  If they are equal, return 0.  If {@code string}
     * starts with {@code prefix} plus an additional element, that is, string ==
     * prefix + '_' + x, then return 1.  Otherwise return a value < 0.
     */
    static int loccmp(String string, String prefix) {
        int slen = string.length(),
                plen = prefix.length();
        /* 'root' is "less than" everything */
        if (prefix.equals("root")) {
            return string.equals("root") ? 0 : 1;
        }
        // ON JAVA (only -- not on C -- someone correct me if I'm wrong)
        // consider "" to be an alternate name for "root".
        if (plen == 0) {
            return slen == 0 ? 0 : 1;
        }
        if (!string.startsWith(prefix)) return -1; /* mismatch */
        if (slen == plen) return 0;
        if (string.charAt(plen) == '_') return 1;
        return -2; /* false match, e.g. "en_USX" cmp "en_US" */
    }

    /**
     * Check the relationship between requested locales, and report problems.
     * The caller specifies the expected relationships between requested
     * and valid (expReqValid) and between valid and actual (expValidActual).
     * Possible values are:
     * "gt" strictly greater than, e.g., en_US > en
     * "ge" greater or equal,      e.g., en >= en
     * "eq" equal,                 e.g., en == en
     */
    void checklocs(String label,
            String req,
            Locale validLoc,
            Locale actualLoc,
            String expReqValid,
            String expValidActual) {
        String valid = validLoc.toString();
        String actual = actualLoc.toString();
        int reqValid = loccmp(req, valid);
        int validActual = loccmp(valid, actual);
        boolean reqOK = (expReqValid.equals("gt") && reqValid > 0) ||
                (expReqValid.equals("ge") && reqValid >= 0) ||
                (expReqValid.equals("eq") && reqValid == 0);
        boolean valOK = (expValidActual.equals("gt") && validActual > 0) ||
                (expValidActual.equals("ge") && validActual >= 0) ||
                (expValidActual.equals("eq") && validActual == 0);
        if (reqOK && valOK) {
            logln("Ok: " + label + "; req=" + req + ", valid=" + valid +
                    ", actual=" + actual);
        } else {
            errln("FAIL: " + label + "; req=" + req + ", valid=" + valid +
                    ", actual=" + actual +
                    (reqOK ? "" : "\n  req !" + expReqValid + " valid") +
                    (valOK ? "" : "\n  val !" + expValidActual + " actual"));
        }
    }

    /**
     * Use reflection to call getLocale() on the given object to
     * determine both the valid and the actual locale.  Verify these
     * for correctness.
     */
    void checkObject(String requestedLocale, Object obj,
            String expReqValid, String expValidActual) {
        Class<?>[] getLocaleParams = new Class[] { ULocale.Type.class };
        try {
            Class<?> cls = obj.getClass();
            Method getLocale = cls.getMethod("getLocale", getLocaleParams);
            ULocale valid = (ULocale) getLocale.invoke(obj, new Object[] {
                    ULocale.VALID_LOCALE });
            ULocale actual = (ULocale) getLocale.invoke(obj, new Object[] {
                    ULocale.ACTUAL_LOCALE });
            checklocs(cls.getName(), requestedLocale,
                    valid.toLocale(), actual.toLocale(),
                    expReqValid, expValidActual);
        }

        // Make the following exceptions _specific_ -- do not
        // catch(Exception), since that will catch the exception
        // that errln throws.
        catch(NoSuchMethodException e1) {
            // no longer an error, Currency has no getLocale
            // errln("FAIL: reflection failed: " + e1);
        } catch(SecurityException e2) {
            errln("FAIL: reflection failed: " + e2);
        } catch(IllegalAccessException e3) {
            errln("FAIL: reflection failed: " + e3);
        } catch(IllegalArgumentException e4) {
            errln("FAIL: reflection failed: " + e4);
        } catch(InvocationTargetException e5) {
            // no longer an error, Currency has no getLocale
            // errln("FAIL: reflection failed: " + e5);
        }
    }

    /**
     * Verify the correct getLocale() behavior for the given service.
     * @param requestedLocale the locale to request.  This MUST BE
     * FAKE.  In other words, it should be something like
     * en_US_FAKEVARIANT so this method can verify correct fallback
     * behavior.
     * @param svc a factory object that can create the object to be
     * tested.  This isn't necessary here (one could just pass in the
     * object) but is required for the overload of this method that
     * takes a Registrar.
     */
    void checkService(String requestedLocale, ServiceFacade svc) {
        checkService(requestedLocale, svc, null, null);
    }

    /**
     * Verify the correct getLocale() behavior for the given service.
     * @param requestedLocale the locale to request.  This MUST BE
     * FAKE.  In other words, it should be something like
     * en_US_FAKEVARIANT so this method can verify correct fallback
     * behavior.
     * @param svc a factory object that can create the object to be
     * tested.
     * @param sub an object that can be used to retrieve a subobject
     * which should also be tested.  May be null.
     * @param reg an object that supplies the registration and
     * unregistration functionality to be tested.  May be null.
     */
    void checkService(String requestedLocale, ServiceFacade svc,
            Subobject sub, Registrar reg) {
        ULocale req = new ULocale(requestedLocale);
        Object obj = svc.create(req);
        checkObject(requestedLocale, obj, "gt", "ge");
        if (sub != null) {
            Object subobj = sub.get(obj);
            checkObject(requestedLocale, subobj, "gt", "ge");
        }
        if (reg != null) {
            logln("Info: Registering service");
            Object key = reg.register(req, obj);
            Object objReg = svc.create(req);
            checkObject(requestedLocale, objReg, "eq", "eq");
            if (sub != null) {
                Object subobj = sub.get(obj);
                // Assume subobjects don't come from services, so
                // their metadata should be structured normally.
                checkObject(requestedLocale, subobj, "gt", "ge");
            }
            logln("Info: Unregistering service");
            if (!reg.unregister(key)) {
                errln("FAIL: unregister failed");
            }
            Object objUnreg = svc.create(req);
            checkObject(requestedLocale, objUnreg, "gt", "ge");
        }
    }

    @Test
    public void TestNameList() {
        String[][][] tests = {
                /* name in French, name in self, minimized, modified */
                {{"fr-Cyrl-BE", "fr-Cyrl-CA"},
                    {"Français (cyrillique, Belgique)", "French (Cyrillic, Belgium)", "fr_Cyrl_BE", "fr_Cyrl_BE"},
                    {"Français (cyrillique, Canada)", "French (Cyrillic, Canada)", "fr_Cyrl_CA", "fr_Cyrl_CA"},
                },
                {{"en", "de", "fr", "zh"},
                    {"Allemand", "Deutsch", "de", "de"},
                    {"Anglais", "English", "en", "en"},
                    {"Chinois", "中文", "zh", "zh"},
                    {"Français", "Français", "fr", "fr"},
                },
                // some non-canonical names
                {{"iw", "iw-US", "no", "no-Cyrl", "in", "in-YU"},
                    {"Hébreu (États-Unis)", "עברית (ארצות הברית)", "he_US", "he_US"},
                    {"Hébreu (Israël)", "עברית (ישראל)", "he", "he_IL"},
                    {"Indonésien (Indonésie)", "Indonesia (Indonesia)", "id", "id_ID"},
                    {"Indonésien (Serbie)", "Indonesia (Serbia)", "id_RS", "id_RS"},
                    {"Norvégien (cyrillique)", "Norwegian (Cyrillic)", "no_Cyrl", "no_Cyrl"},
                    {"Norvégien (latin)", "Norsk (latinsk)", "no", "no_Latn"},
                },
                {{"zh-Hant-TW", "en", "en-gb", "fr", "zh-Hant", "de", "de-CH", "zh-TW"},
                    {"Allemand (Allemagne)", "Deutsch (Deutschland)", "de", "de_DE"},
                    {"Allemand (Suisse)", "Deutsch (Schweiz)", "de_CH", "de_CH"},
                    {"Anglais (États-Unis)", "English (United States)", "en", "en_US"},
                    {"Anglais (Royaume-Uni)", "English (United Kingdom)", "en_GB", "en_GB"},
                    {"Chinois (traditionnel)", "中文（繁體）", "zh_Hant", "zh_Hant"},
                    {"Français", "Français", "fr", "fr"},
                },
                {{"zh", "en-gb", "en-CA", "fr-Latn-FR"},
                    {"Anglais (Canada)", "English (Canada)", "en_CA", "en_CA"},
                    {"Anglais (Royaume-Uni)", "English (United Kingdom)", "en_GB", "en_GB"},
                    {"Chinois", "中文", "zh", "zh"},
                    {"Français", "Français", "fr", "fr"},
                },
                {{"en-gb", "fr", "zh-Hant", "zh-SG", "sr", "sr-Latn"},
                    {"Anglais (Royaume-Uni)", "English (United Kingdom)", "en_GB", "en_GB"},
                    {"Chinois (simplifié, Singapour)", "中文（简体，新加坡）", "zh_SG", "zh_Hans_SG"},
                    {"Chinois (traditionnel, Taïwan)", "中文（繁體，台灣）", "zh_Hant", "zh_Hant_TW"},
                    {"Français", "Français", "fr", "fr"},
                    {"Serbe (cyrillique)", "Српски (ћирилица)", "sr", "sr_Cyrl"},
                    {"Serbe (latin)", "Srpski (latinica)", "sr_Latn", "sr_Latn"},
                },
                {{"fr-Cyrl", "fr-Arab"},
                    {"Français (arabe)", "French (Arabic)", "fr_Arab", "fr_Arab"},
                    {"Français (cyrillique)", "French (Cyrillic)", "fr_Cyrl", "fr_Cyrl"},
                },
                {{"fr-Cyrl-BE", "fr-Arab-CA"},
                    {"Français (arabe, Canada)", "French (Arabic, Canada)", "fr_Arab_CA", "fr_Arab_CA"},
                    {"Français (cyrillique, Belgique)", "French (Cyrillic, Belgium)", "fr_Cyrl_BE", "fr_Cyrl_BE"},
                }
        };
        ULocale originalDefaultLocale = ULocale.getDefault();
        ULocale.setDefault(ULocale.US);
        try {
            ULocale french = ULocale.FRENCH;
            LocaleDisplayNames names = LocaleDisplayNames.getInstance(french,
                    DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU);
            for (Type type : DisplayContext.Type.values()) {
                logln("Contexts: " + names.getContext(type).toString());
            }
            Collator collator = Collator.getInstance(french);

            for (String[][] test : tests) {
                Set<ULocale> list = new LinkedHashSet<ULocale>();
                List<UiListItem> expected = new ArrayList<UiListItem>();
                for (String item : test[0]) {
                    list.add(new ULocale(item));
                }
                for (int i = 1; i < test.length; ++i) {
                    String[] rawRow = test[i];
                    expected.add(new UiListItem(new ULocale(rawRow[2]), new ULocale(rawRow[3]), rawRow[0], rawRow[1]));
                }
                List<UiListItem> newList = names.getUiList(list, false, collator);
                if (!expected.equals(newList)) {
                    if (expected.size() != newList.size()) {
                        errln(list.toString() + ": wrong size" + expected + ", " + newList);
                    } else {
//                        errln(list.toString());
                        for (int i = 0; i < expected.size(); ++i) {
                            assertEquals(i + "", expected.get(i), newList.get(i));
                        }
                    }
                } else {
                    assertEquals(list.toString(), expected, newList);
                }
            }
        } finally {
            ULocale.setDefault(originalDefaultLocale);
        }
    }

    @Test
    public void TestIllformedLocale() {
        ULocale french = ULocale.FRENCH;
        Collator collator = Collator.getInstance(french);
        LocaleDisplayNames names = LocaleDisplayNames.getInstance(french,
                DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU);
        for (String malformed : Arrays.asList("en-a", "$", "ü--a", "en--US")) {
            try {
                Set<ULocale> supported = Collections.singleton(new ULocale(malformed));
                names.getUiList(supported, false, collator);
                assertNull("Failed to detect bogus locale «" + malformed + "»", supported);
            } catch (IllformedLocaleException e) {
                logln("Successfully detected ill-formed locale «" + malformed + "»:" + e.getMessage());
            }
        }
    }
}
