/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.amnesica.kryptey.inputmethod.latin.common;

import android.content.res.Resources;
import android.os.Build;
import android.os.LocaleList;
import android.text.TextUtils;

import com.amnesica.kryptey.inputmethod.latin.utils.LocaleResourceUtils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;

/**
 * A class to help with handling Locales in string form.
 * <p>
 * This file has the same meaning and features (and shares all of its code) with the one with the
 * same name in Latin IME. They need to be kept synchronized; for any update/bugfix to
 * this file, consider also updating/fixing the version in Latin IME.
 */
public final class LocaleUtils {
  private LocaleUtils() {
    // Intentional empty constructor for utility class.
  }

  // Locale match level constants.
  // A higher level of match is guaranteed to have a higher numerical value.
  // Some room is left within constants to add match cases that may arise necessary
  // in the future, for example differentiating between the case where the countries
  // are both present and different, and the case where one of the locales does not
  // specify the countries. This difference is not needed now.

  private static final HashMap<String, Locale> sLocaleCache = new HashMap<>();

  /**
   * Creates a locale from a string specification.
   *
   * @param localeString a string specification of a locale, in a format of "ll_cc_variant" where
   *                     "ll" is a language code, "cc" is a country code.
   */
  public static Locale constructLocaleFromString(final String localeString) {
    synchronized (sLocaleCache) {
      if (sLocaleCache.containsKey(localeString)) {
        return sLocaleCache.get(localeString);
      }
      final String[] elements = localeString.split("_", 3);
      final Locale locale;
      if (elements.length == 1) {
        locale = new Locale(elements[0] /* language */);
      } else if (elements.length == 2) {
        locale = new Locale(elements[0] /* language */, elements[1] /* country */);
      } else { // localeParams.length == 3
        locale = new Locale(elements[0] /* language */, elements[1] /* country */,
            elements[2] /* variant */);
      }
      sLocaleCache.put(localeString, locale);
      return locale;
    }
  }

  /**
   * Creates a string specification for a locale.
   *
   * @param locale the locale.
   * @return a string specification of a locale, in a format of "ll_cc_variant" where "ll" is a
   * language code, "cc" is a country code.
   */
  public static String getLocaleString(final Locale locale) {
    if (!TextUtils.isEmpty(locale.getVariant())) {
      return locale.getLanguage() + "_" + locale.getCountry() + "_" + locale.getVariant();
    }
    if (!TextUtils.isEmpty(locale.getCountry())) {
      return locale.getLanguage() + "_" + locale.getCountry();
    }
    return locale.getLanguage();
  }

  /**
   * Get the closest matching locale. This searches by:
   * 1. {@link Locale#equals(Object)}
   * 2. Language, Country, and Variant match
   * 3. Language and Country match
   * 4. Language matches
   *
   * @param localeToMatch the locale to match.
   * @param options       a collection of locales to find the best match.
   * @return the locale from the collection that is the best match for the specified locale or
   * null if nothing matches.
   */
  public static Locale findBestLocale(final Locale localeToMatch,
                                      final Collection<Locale> options) {
    // Find the best subtype based on a straightforward matching algorithm.
    // TODO: Use LocaleList#getFirstMatch() instead.
    for (final Locale locale : options) {
      if (locale.equals(localeToMatch)) {
        return locale;
      }
    }
    for (final Locale locale : options) {
      if (locale.getLanguage().equals(localeToMatch.getLanguage()) &&
          locale.getCountry().equals(localeToMatch.getCountry()) &&
          locale.getVariant().equals(localeToMatch.getVariant())) {
        return locale;
      }
    }
    for (final Locale locale : options) {
      if (locale.getLanguage().equals(localeToMatch.getLanguage()) &&
          locale.getCountry().equals(localeToMatch.getCountry())) {
        return locale;
      }
    }
    for (final Locale locale : options) {
      if (locale.getLanguage().equals(localeToMatch.getLanguage())) {
        return locale;
      }
    }
    return null;
  }

  /**
   * Get the list of locales enabled in the system.
   *
   * @return the list of locales enabled in the system.
   */
  public static List<Locale> getSystemLocales() {
    ArrayList<Locale> locales = new ArrayList<>();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
      LocaleList localeList = Resources.getSystem().getConfiguration().getLocales();
      for (int i = 0; i < localeList.size(); i++) {
        locales.add(localeList.get(i));
      }
    } else {
      locales.add(Resources.getSystem().getConfiguration().locale);
    }
    return locales;
  }

  /**
   * Comparator for {@link Locale} to order them alphabetically
   * first.
   */
  public static class LocaleComparator implements Comparator<Locale> {
    @Override
    public int compare(Locale a, Locale b) {
      if (a.equals(b)) {
        // ensure that this is consistent with equals
        return 0;
      }
      final String aDisplay =
          LocaleResourceUtils.getLocaleDisplayNameInSystemLocale(getLocaleString(a));
      final String bDisplay =
          LocaleResourceUtils.getLocaleDisplayNameInSystemLocale(getLocaleString(b));
      final int result = aDisplay.compareToIgnoreCase(bDisplay);
      if (result != 0) {
        return result;
      }
      // ensure that non-equal objects are distinguished to be consistent with equals
      return a.hashCode() > b.hashCode() ? 1 : -1;
    }
  }
}
