package com.ibm.icu.text;

import com.ibm.icu.util.ULocale;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;

/**
 * Provide information about gender in locales based on data in CLDR. Currently just gender of
 * lists.
 *
 * @author markdavis
 */
public class GenderInfo {

    private final ListGenderStyle style; // set based on locale

    /**
     * Gender: OTHER means either the information is unavailable, or the person has declined to
     * state MALE or FEMALE.
     */
    public enum Gender {
        MALE,
        FEMALE,
        OTHER
    }

    /**
     * Create GenderInfo from a ULocale.
     *
     * @param uLocale
     */
    public GenderInfo(ULocale uLocale) {
        ULocale language =
                new ULocale(uLocale.getLanguage()); // in the hard coded data, the language is
        // sufficient.
        // Will change with RB.
        ListGenderStyle tempStyle = localeToListGender.get(language);
        style = tempStyle == null ? ListGenderStyle.NEUTRAL : tempStyle;
    }

    /**
     * Create GenderInfo from a Locale.
     *
     * @param uLocale
     */
    public GenderInfo(Locale locale) {
        this(ULocale.forLocale(locale));
    }

    /** Enum only meant for use in CLDR and in testing. Indicates the category for the locale. */
    public enum ListGenderStyle {
        /** Always OTHER (if more than one) */
        NEUTRAL,
        /** gender(all male) = male, gender(all female) = female, otherwise gender(list) = other */
        MIXED_NEUTRAL,
        /** gender(all female) = female, otherwise gender(list) = male */
        MALE_TAINTS
    }

    /**
     * Reset the data used for mapping locales to styles. Only for use in CLDR and in testing.
     *
     * @param uLocale
     */
    public static void setLocaleMapping(Map<ULocale, ListGenderStyle> newULocaleToListGender) {
        localeToListGender.clear();
        for (Entry<ULocale, ListGenderStyle> entry : newULocaleToListGender.entrySet()) {
            localeToListGender.put(entry.getKey(), entry.getValue());
        }
    }

    /**
     * Get the gender of a list, based on locale usage.
     *
     * @param genders a list of genders.
     * @return the gender of the list.
     */
    public Gender getListGender(Gender... genders) {
        return getListGender(Arrays.asList(genders));
    }

    /**
     * Get the gender of a list, based on locale usage.
     *
     * @param genders a list of genders.
     * @return the gender of the list.
     */
    public Gender getListGender(List<Gender> genders) {
        if (genders.size() == 0 || style == ListGenderStyle.NEUTRAL) {
            return Gender.OTHER; // degenerate case
        }
        if (genders.size() == 1) {
            return genders.get(0); // degenerate case
        }
        switch (style) {
            case MIXED_NEUTRAL: // gender(all male) = male, gender(all female) = female, otherwise
                // gender(list) = other
                boolean hasFemale = false;
                boolean hasMale = false;
                for (Gender gender : genders) {
                    switch (gender) {
                        case FEMALE:
                            if (hasMale) {
                                return Gender.OTHER;
                            }
                            hasFemale = true;
                            break;
                        case MALE:
                            if (hasFemale) {
                                return Gender.OTHER;
                            }
                            hasMale = true;
                            break;
                        case OTHER:
                            return Gender.OTHER;
                    }
                }
                return hasMale ? Gender.MALE : hasFemale ? Gender.FEMALE : Gender.OTHER;
            case MALE_TAINTS: // gender(all female) = female, otherwise gender(list) = male
                for (Gender gender : genders) {
                    if (gender != Gender.FEMALE) {
                        return Gender.MALE;
                    }
                }
                return Gender.FEMALE;
            default:
                return Gender.OTHER;
        }
    }

    // TODO Get this data from a resource bundle generated from CLDR.
    // For now, hard coded.

    private static Map<ULocale, ListGenderStyle> localeToListGender = new HashMap<>();

    static {
        for (String locale :
                Arrays.asList(
                        "ar", "ca", "cs", "hr", "es", "fr", "he", "hi", "it", "lt", "lv", "mr",
                        "nl", "pl", "pt", "ro", "ru", "sk", "sl", "sr", "uk", "ur", "zh")) {
            localeToListGender.put(new ULocale(locale), ListGenderStyle.MALE_TAINTS);
        }
        for (String locale : Arrays.asList("el", "is")) {
            localeToListGender.put(new ULocale(locale), ListGenderStyle.MIXED_NEUTRAL);
        }
    }
}
