package org.unicode.cldr.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

/**
 * List of locale IDs which are somehow 'special'. Parses SpecialLocales.txt
 * 
 * @author srl
 * 
 */
public class SpecialLocales {
    private static final String INCLUDE_SUBLOCALES = "*";

    public enum Type {
        /**
         * Locale may not be modified by user.
         */
        readonly,
        /**
         * Locale may be modified by user. Contents aren't part of CLDR release and may change.
         */
        scratch
    };

    /**
     * Get the type of this locale
     * 
     * @param l
     * @return a Type or null
     */
    public static Type getType(CLDRLocale l) {
        return getInstance().getTypeInternal(l);
    }

    /**
     * Get all CLDRLocales matching this type. Does not include wildcard (*) sublocales.
     * 
     * @param t
     * @return a set, or null if none found
     */
    public static Set<CLDRLocale> getByType(Type t) {
        return getInstance().getByTypeInternal(t);
    }

    /**
     * Get the comment on this locale. Strip out @ text.
     * 
     * @param l
     * @return string or null
     */
    public static String getComment(CLDRLocale l) {
        return getCommentRaw(l).replaceAll("@", "");
    }

    /**
     * Get the comment on this locale. Include "@locale" markers.
     * 
     * @param l
     * @return string or null
     */
    public static String getCommentRaw(CLDRLocale l) {
        return getInstance().getCommentInternal(l).replaceAll("@@", "@" + l.getBaseName());
    }

    /**
     * Singleton object
     */
    private static SpecialLocales singleton = null;

    /**
     * Internal accessor. All access is via the static functions.
     * @return
     */
    private static synchronized SpecialLocales getInstance() {
        if (singleton == null) {
            singleton = new SpecialLocales();
        }
        return singleton;
    }

    private Map<CLDRLocale, Type> specials = new HashMap<CLDRLocale, Type>();
    private Map<Type, Set<CLDRLocale>> types = new HashMap<Type, Set<CLDRLocale>>();
    private Map<CLDRLocale, String> comments = new HashMap<CLDRLocale, String>();
    private Set<CLDRLocale> specialsWildcards = new HashSet<CLDRLocale>();

    public Set<CLDRLocale> getByTypeInternal(Type t) {
        return types.get(t);
    }

    public Type getTypeInternal(CLDRLocale l) {
        l = findLocale(l, l);
        return specials.get(l);
    }

    public String getCommentInternal(CLDRLocale l) {
        l = findLocale(l, l);
        return comments.get(l);
    }

    public CLDRLocale findLocale(CLDRLocale fromLocale, CLDRLocale origLocale) {
        if (origLocale == fromLocale && specials.containsKey(origLocale)) {
            return origLocale; // explicit locale - no search.
        }
        if (fromLocale == null) {
            return origLocale;
        }
        if (specialsWildcards.contains(fromLocale)) {
            return fromLocale;
        }
        return findLocale(fromLocale.getParent(), origLocale);
    }

    /**
     * Internal constructor
     */
    private SpecialLocales() {
        // from StandardCodes.java
        String line;
        int ln = 0;
        try {
            BufferedReader lstreg = CldrUtility.getUTF8Data("SpecialLocales.txt");
            while (true) {
                line = lstreg.readLine();
                ln++;
                if (line == null)
                    break;
                int commentPos = line.indexOf('#');
                if (commentPos >= 0) {
                    line = line.substring(0, commentPos);
                }
                line = line.trim();
                if (line.length() == 0)
                    continue;
                List<String> stuff = CldrUtility.splitList(line, ';', true);
                String id = (String) stuff.get(0);
                boolean includeSublocs = (id.endsWith(INCLUDE_SUBLOCALES));
                if (includeSublocs) {
                    id = id.substring(0, id.length() - INCLUDE_SUBLOCALES.length());
                }
                String type = (String) stuff.get(1);
                String comment = (String) stuff.get(2);
                Type t = null;

                // verify that the locale is valid
                CLDRLocale l = null;
                try {
                    l = CLDRLocale.getInstance(id);
                } catch (Exception e) {
                    throw new IllegalArgumentException("Invalid CLDRLocale in SpecialLocales.txt:" + ln + ": " + line);
                }

                // verify that the type is valid
                try {
                    t = Type.valueOf(type.toLowerCase(Locale.ENGLISH));
                } catch (Exception e) {
                    throw new IllegalArgumentException("Invalid SpecialLocales.Type in SpecialLocales.txt:" + ln + ": "
                        + line);
                }

                Set<CLDRLocale> s = types.get(t);
                if (s == null) {
                    s = new TreeSet<CLDRLocale>();
                    types.put(t, s);
                }
                s.add(l);
                specials.put(l, t);
                if (includeSublocs) {
                    specialsWildcards.add(l);
                }
                if (!comment.isEmpty()) {
                    comments.put(l, comment);
                }
                if (false) {
                    System.out.println(SpecialLocales.class.getSimpleName() + ": locale " + l + ", includejSublocs=" + includeSublocs + ", type=" + t
                        + ", comment: " + comment);
                }
            }
        } catch (IOException e) {
            throw (IllegalArgumentException) new IllegalArgumentException("Internal Error").initCause(e);
        }
        specials = Collections.unmodifiableMap(specials);
        specialsWildcards = Collections.unmodifiableSet(specialsWildcards);
        comments = Collections.unmodifiableMap(comments);
        types = Collections.unmodifiableMap(types);
    }

}
