/*
 * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0,
 * and the EPL 1.0 (https://h2database.com/html/license.html).
 * Initial Developer: H2 Group
 */
package org.h2.value;

import java.lang.reflect.Method;
import java.text.Collator;
import java.util.Comparator;
import java.util.Locale;

import org.h2.message.DbException;
import org.h2.util.JdbcUtils;
import org.h2.util.StringUtils;

/**
 * An implementation of CompareMode that uses the ICU4J Collator.
 */
public class CompareModeIcu4J extends CompareMode {

    private final Comparator<String> collator;

    private volatile CompareModeIcu4J caseInsensitive;

    protected CompareModeIcu4J(String name, int strength) {
        super(name, strength);
        collator = getIcu4jCollator(name, strength);
    }

    @Override
    public int compareString(String a, String b, boolean ignoreCase) {
        if (ignoreCase && getStrength() > Collator.SECONDARY) {
            CompareModeIcu4J i = caseInsensitive;
            if (i == null) {
                caseInsensitive = i = new CompareModeIcu4J(getName(), Collator.SECONDARY);
            }
            return i.compareString(a, b, false);
        }
        return collator.compare(a, b);
    }

    @Override
    public boolean equalsChars(String a, int ai, String b, int bi,
            boolean ignoreCase) {
        return compareString(a.substring(ai, ai + 1), b.substring(bi, bi + 1),
                ignoreCase) == 0;
    }

    @SuppressWarnings("unchecked")
    private static Comparator<String> getIcu4jCollator(String name, int strength) {
        try {
            Comparator<String> result = null;
            Class<?> collatorClass = JdbcUtils.loadUserClass(
                    "com.ibm.icu.text.Collator");
            Method getInstanceMethod = collatorClass.getMethod(
                    "getInstance", Locale.class);
            int length = name.length();
            if (length == 2) {
                Locale locale = new Locale(StringUtils.toLowerEnglish(name), "");
                if (compareLocaleNames(locale, name)) {
                    result = (Comparator<String>) getInstanceMethod.invoke(null, locale);
                }
            } else if (length == 5) {
                // LL_CC (language_country)
                int idx = name.indexOf('_');
                if (idx >= 0) {
                    String language = StringUtils.toLowerEnglish(name.substring(0, idx));
                    String country = name.substring(idx + 1);
                    Locale locale = new Locale(language, country);
                    if (compareLocaleNames(locale, name)) {
                        result = (Comparator<String>) getInstanceMethod.invoke(null, locale);
                    }
                }
            }
            if (result == null) {
                for (Locale locale : (Locale[]) collatorClass.getMethod(
                        "getAvailableLocales").invoke(null)) {
                    if (compareLocaleNames(locale, name)) {
                        result = (Comparator<String>) getInstanceMethod.invoke(null, locale);
                        break;
                    }
                }
            }
            if (result == null) {
                throw DbException.getInvalidValueException("collator", name);
            }
            collatorClass.getMethod("setStrength", int.class).invoke(result, strength);
            return result;
        } catch (Exception e) {
            throw DbException.convert(e);
        }
    }

}
