/*
 * Copyright (C) 2014 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.cyanogenmod.eleven.locale;

import android.text.TextUtils;
import com.google.common.annotations.VisibleForTesting;
import java.util.Locale;

public class LocaleSet {
    private static final String CHINESE_LANGUAGE = Locale.CHINESE.getLanguage().toLowerCase();
    private static final String JAPANESE_LANGUAGE = Locale.JAPANESE.getLanguage().toLowerCase();
    private static final String KOREAN_LANGUAGE = Locale.KOREAN.getLanguage().toLowerCase();

    private static class LocaleWrapper {
        private final Locale mLocale;
        private final String mLanguage;
        private final boolean mLocaleIsCJK;

        private static boolean isLanguageCJK(String language) {
            return CHINESE_LANGUAGE.equals(language) ||
                    JAPANESE_LANGUAGE.equals(language) ||
                    KOREAN_LANGUAGE.equals(language);
        }

        public LocaleWrapper(Locale locale) {
            mLocale = locale;
            if (mLocale != null) {
                mLanguage = mLocale.getLanguage().toLowerCase();
                mLocaleIsCJK = isLanguageCJK(mLanguage);
            } else {
                mLanguage = null;
                mLocaleIsCJK = false;
            }
        }

        public boolean hasLocale() {
            return mLocale != null;
        }

        public Locale getLocale() {
            return mLocale;
        }

        public boolean isLocale(Locale locale) {
            return mLocale == null ? (locale == null) : mLocale.equals(locale);
        }

        public boolean isLocaleCJK() {
            return mLocaleIsCJK;
        }

        public boolean isLanguage(String language) {
            return mLanguage == null ? (language == null)
                    : mLanguage.equalsIgnoreCase(language);
        }

        public String toString() {
            return mLocale != null ? mLocale.toLanguageTag() : "(null)";
        }
    }

    public static LocaleSet getDefault() {
        return new LocaleSet(Locale.getDefault());
    }

    public LocaleSet(Locale locale) {
        this(locale, null);
    }

    /**
     * Returns locale set for a given set of IETF BCP-47 tags separated by ';'.
     * BCP-47 tags are what is used by ICU 52's toLanguageTag/forLanguageTag
     * methods to represent individual Locales: "en-US" for Locale.US,
     * "zh-CN" for Locale.CHINA, etc. So eg "en-US;zh-CN" specifies the locale
     * set LocaleSet(Locale.US, Locale.CHINA).
     *
     * @param localeString One or more BCP-47 tags separated by ';'.
     * @return LocaleSet for specified locale string, or default set if null
     * or unable to parse.
     */
    public static LocaleSet getLocaleSet(String localeString) {
        // Locale.toString() generates strings like "en_US" and "zh_CN_#Hans".
        // Locale.toLanguageTag() generates strings like "en-US" and "zh-Hans-CN".
        // We can only parse language tags.
        if (localeString != null && localeString.indexOf('_') == -1) {
            final String[] locales = localeString.split(";");
            final Locale primaryLocale = Locale.forLanguageTag(locales[0]);
            // ICU tags undefined/unparseable locales "und"
            if (primaryLocale != null &&
                    !TextUtils.equals(primaryLocale.toLanguageTag(), "und")) {
                if (locales.length > 1 && locales[1] != null) {
                    final Locale secondaryLocale = Locale.forLanguageTag(locales[1]);
                    if (secondaryLocale != null &&
                            !TextUtils.equals(secondaryLocale.toLanguageTag(), "und")) {
                        return new LocaleSet(primaryLocale, secondaryLocale);
                    }
                }
                return new LocaleSet(primaryLocale);
            }
        }
        return getDefault();
    }

    private final LocaleWrapper mPrimaryLocale;
    private final LocaleWrapper mSecondaryLocale;

    public LocaleSet(Locale primaryLocale, Locale secondaryLocale) {
        mPrimaryLocale = new LocaleWrapper(primaryLocale);
        mSecondaryLocale = new LocaleWrapper(
                mPrimaryLocale.equals(secondaryLocale) ? null : secondaryLocale);
    }

    public LocaleSet normalize() {
        final Locale primaryLocale = getPrimaryLocale();
        if (primaryLocale == null) {
            return getDefault();
        }
        Locale secondaryLocale = getSecondaryLocale();
        // disallow both locales with same language (redundant and/or conflicting)
        // disallow both locales CJK (conflicting rules)
        if (secondaryLocale == null ||
                isPrimaryLanguage(secondaryLocale.getLanguage()) ||
                (isPrimaryLocaleCJK() && isSecondaryLocaleCJK())) {
            return new LocaleSet(primaryLocale);
        }
        // unnecessary to specify English as secondary locale (redundant)
        if (isSecondaryLanguage(Locale.ENGLISH.getLanguage())) {
            return new LocaleSet(primaryLocale);
        }
        return this;
    }

    public boolean hasSecondaryLocale() {
        return mSecondaryLocale.hasLocale();
    }

    public Locale getPrimaryLocale() {
        return mPrimaryLocale.getLocale();
    }

    public Locale getSecondaryLocale() {
        return mSecondaryLocale.getLocale();
    }

    public boolean isPrimaryLocale(Locale locale) {
        return mPrimaryLocale.isLocale(locale);
    }

    public boolean isSecondaryLocale(Locale locale) {
        return mSecondaryLocale.isLocale(locale);
    }

    private static final String SCRIPT_SIMPLIFIED_CHINESE = "Hans";
    private static final String SCRIPT_TRADITIONAL_CHINESE = "Hant";

    @VisibleForTesting
    public static boolean isLocaleSimplifiedChinese(Locale locale) {
        // language must match
        if (locale == null || !TextUtils.equals(locale.getLanguage(), CHINESE_LANGUAGE)) {
            return false;
        }
        // script is optional but if present must match
        if (!TextUtils.isEmpty(locale.getScript())) {
            return locale.getScript().equals(SCRIPT_SIMPLIFIED_CHINESE);
        }
        // if no script, must match known country
        return locale.equals(Locale.SIMPLIFIED_CHINESE);
    }

    public boolean isPrimaryLocaleSimplifiedChinese() {
        return isLocaleSimplifiedChinese(getPrimaryLocale());
    }

    public boolean isSecondaryLocaleSimplifiedChinese() {
        return isLocaleSimplifiedChinese(getSecondaryLocale());
    }

    @VisibleForTesting
    public static boolean isLocaleTraditionalChinese(Locale locale) {
        // language must match
        if (locale == null || !TextUtils.equals(locale.getLanguage(), CHINESE_LANGUAGE)) {
            return false;
        }
        // script is optional but if present must match
        if (!TextUtils.isEmpty(locale.getScript())) {
            return locale.getScript().equals(SCRIPT_TRADITIONAL_CHINESE);
        }
        // if no script, must match known country
        return locale.equals(Locale.TRADITIONAL_CHINESE);
    }

    public boolean isPrimaryLocaleTraditionalChinese() {
        return isLocaleTraditionalChinese(getPrimaryLocale());
    }

    public boolean isSecondaryLocaleTraditionalChinese() {
        return isLocaleTraditionalChinese(getSecondaryLocale());
    }

    public boolean isPrimaryLocaleCJK() {
        return mPrimaryLocale.isLocaleCJK();
    }

    public boolean isSecondaryLocaleCJK() {
        return mSecondaryLocale.isLocaleCJK();
    }

    public boolean isPrimaryLanguage(String language) {
        return mPrimaryLocale.isLanguage(language);
    }

    public boolean isSecondaryLanguage(String language) {
        return mSecondaryLocale.isLanguage(language);
    }

    @Override
    public boolean equals(Object object) {
        if (object == this) {
            return true;
        }
        if (object instanceof LocaleSet) {
            final LocaleSet other = (LocaleSet) object;
            return other.isPrimaryLocale(mPrimaryLocale.getLocale())
                    && other.isSecondaryLocale(mSecondaryLocale.getLocale());
        }
        return false;
    }

    @Override
    public final String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append(mPrimaryLocale.toString());
        if (hasSecondaryLocale()) {
            builder.append(";");
            builder.append(mSecondaryLocale.toString());
        }
        return builder.toString();
    }
}
