package org.unicode.cldr.util;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.unicode.cldr.util.CldrUtility.Output;
import org.unicode.cldr.util.PatternPlaceholders.PlaceholderInfo;

import com.ibm.icu.text.MessageFormat;

public class PathDescription {

    public enum ErrorHandling {SKIP, CONTINUE}
    // BE sure to sync with the list in xmbSkip!
    public static final Set<String> EXTRA_LANGUAGES = new TreeSet<String>(Arrays.asList(
            "ach|af|ak|ak|am|ar|az|be|bem|bg|bh|bn|br|bs|ca|chr|ckb|co|crs|cs|cy|da|de|de_AT|de_CH|ee|el|en|en_AU|en_CA|en_GB|en_US|eo|es|es_419|es_ES|et|eu|fa|fi|fil|fo|fr|fr_CA|fr_CH|fy|ga|gaa|gd|gl|gn|gsw|gu|ha|haw|he|hi|hr|ht|hu|hy|ia|id|ig|is|it|ja|jv|ka|kg|kk|km|kn|ko|kri|ku|ky|la|lg|ln|lo|loz|lt|lua|lv|mfe|mg|mi|mk|ml|mn|mr|ms|mt|my|nb|ne|nl|nl_BE|nn|nso|ny|nyn|oc|om|or|pa|pcm|pl|ps|pt|pt_BR|pt_PT|qu|rm|rn|ro|ro|ro_MD|ru|rw|sd|si|sk|sl|sn|so|sq|sr|sr_Latn|sr_ME|st|su|sv|sw|ta|te|tg|th|ti|tk|tlh|tn|to|tr|tt|tum|ug|uk|und|ur|uz|vi|wo|xh|yi|yo|zh|zh_Hans|zh_Hant|zh_HK|zu|zxx"
            .split("|")));

    private static final Pattern METAZONE_PATTERN = Pattern.compile("//ldml/dates/timeZoneNames/metazone\\[@type=\"([^\"]*)\"]/(.*)/(.*)");
    private static final Pattern STAR_ATTRIBUTE_PATTERN = Pattern.compile("=\"([^\"]*)\"");

    private static final StandardCodes STANDARD_CODES = StandardCodes.make();
    private static Map<String, String> ZONE2COUNTRY = STANDARD_CODES.getZoneToCounty();
    private static RegexLookup<String> pathHandling = new RegexLookup<String>().loadFromFile(PathDescription.class, "data/PathDescription.txt");

    // set in construction

    private final CLDRFile english;
    private final Map<String, String> extras;
    private final ErrorHandling errorHandling;
    private final Map<String, List<Set<String>>> starredPaths;
    private final Set<String> allMetazones;

    // used on instance

    private Matcher metazoneMatcher = METAZONE_PATTERN.matcher("");
    private XPathParts parts = new XPathParts();
    private String starredPathOutput;
    private Output<String[]> pathArguments = new Output<String[]>();
    private EnumSet<Status> status = EnumSet.noneOf(Status.class);

    public static final String MISSING_DESCRIPTION = "Before translating, please see http://cldr.org/translation.";

    public PathDescription(SupplementalDataInfo supplementalDataInfo, 
            CLDRFile english, 
            Map<String, String> extras, 
            Map<String, List<Set<String>>> starredPaths, 
            ErrorHandling errorHandling) {
        this.english = english;
        this.extras = extras == null ? new HashMap<String, String>() : extras;
        this.starredPaths = starredPaths == null ? new HashMap<String, List<Set<String>>>() : starredPaths;
        allMetazones = supplementalDataInfo.getAllMetazones();
        this.errorHandling = errorHandling;
    }

    public String getStarredPathOutput() {
        return starredPathOutput;
    }

    public EnumSet<Status> getStatus() {
        return status;
    }

    public enum Status {SKIP, NULL_VALUE, EMPTY_CONTENT, NOT_REQUIRED}

    public String getDescription(String path, String value, Level level, Object context) {
        status.clear();

        String description = pathHandling.get(path, context, pathArguments);
        if (description == null) {
            description = MISSING_DESCRIPTION;
        } else if ("SKIP".equals(description)) {
            status.add(Status.SKIP);
            if (errorHandling == ErrorHandling.SKIP) {
                return null;
            }
        }

        //                String localeWhereFound = english.getSourceLocaleID(path, status);
        //                if (!status.pathWhereFound.equals(path)) {
        //                    reasonsToPaths.put("alias", path + "  " + value);
        //                    continue;
        //                }
        if (value == null) { // a count item?
            String xpath = extras.get(path);
            if (xpath != null) {
                value = english.getStringValue(xpath);
            } else if (path.contains("/metazone")) {
                if (metazoneMatcher.reset(path).matches()) {
                    String name = metazoneMatcher.group(1);
                    String type = metazoneMatcher.group(3);
                    value = name.replace('_', ' ') + (type.equals("generic") ? "" : type.equals("daylight") ? " Summer" : " Winter") + " Time";
                    // System.out.println("Missing:    " + path + " :    " + value);
                }
            }
            if (value == null) {
                status.add(Status.NULL_VALUE);
                if (errorHandling == ErrorHandling.SKIP) {
                    return null;
                }
            }
        }
        if (value != null && value.length() == 0) {
            status.add(Status.EMPTY_CONTENT);
            if (errorHandling == ErrorHandling.SKIP) {
                return null;
            }
        }
        //        if (GenerateXMB.contentMatcher != null && !GenerateXMB.contentMatcher.reset(value).find()) {
        //            PathDescription.addSkipReasons(reasonsToPaths, "content-parameter", level, path, value);
        //            return null;
        //        }

        List<String> attributes = addStarredInfo(starredPaths, path);

        // In special cases, only use if there is a root value (languageNames, ...
        if (description.startsWith("ROOT")) {
            int typeEnd = description.indexOf(';');
            String type = description.substring(4,typeEnd).trim();
            description = description.substring(typeEnd+1).trim();

            boolean isMetazone = type.equals("metazone");
            String code = attributes.get(0);
            boolean isRootCode = isRootCode(code, allMetazones, type, isMetazone);
            if (!isRootCode) {
                status.add(Status.NOT_REQUIRED);
                if (errorHandling == ErrorHandling.SKIP) {
                    return null;
                }
            }
            if (isMetazone) {
                parts.set(path);
                String daylightType = parts.getElement(-1);
                daylightType =  daylightType.equals("daylight") ? "summer" : daylightType.equals("standard") ? "winter" : daylightType;
                String length = parts.getElement(-2);
                length = length.equals("long") ? "" : "abbreviated ";
                code = code + ", " + length + daylightType + " form";
            } else if (type.equals("timezone")) {
                String country = (String) ZONE2COUNTRY.get(code);
                int lastSlash = code.lastIndexOf('/');
                String codeName = lastSlash< 0 ? code : code.substring(lastSlash+1).replace('_', ' ');

                boolean found = false;
                if ("001".equals(country)) {
                    code = "the timezone “" + codeName + "”";
                    found = true;
                } else if (country != null) {
                    String countryName = english.getName("territory", country);
                    if (countryName != null) {
                        if (!codeName.equals(countryName)) {
                            code = "the city “" + codeName + "” (in " + countryName + ")";
                        } else {
                            code = "the country “" + codeName + "”";
                        }
                        found = true;
                    }
                }
                if (!found) {
                    System.out.println("Missing country for timezone " + code);
                }
            }
            description = MessageFormat.format(MessageFormat.autoQuoteApostrophe(description), new Object[]{code});
        } else if (path.contains("exemplarCity")) {
            String regionCode = ZONE2COUNTRY.get(attributes.get(0));
            String englishRegionName = english.getName(CLDRFile.TERRITORY_NAME, regionCode);
            description = MessageFormat.format(MessageFormat.autoQuoteApostrophe(description), new Object[]{englishRegionName});
        } else if (description != MISSING_DESCRIPTION){
            description = MessageFormat.format(MessageFormat.autoQuoteApostrophe(description), (Object [])pathArguments.value);
        }

        return description;
    }

    /**
     * Creates an escaped HTML string of placeholder information.
     * @param path the xpath to specify placeholder information for
     * @return a HTML string, or an empty string if there was no placeholder information
     */
    public String getPlaceholderDescription(String path) {
        Map<String, PlaceholderInfo> placeholders = PatternPlaceholders.getInstance().get(path);
        if (placeholders != null && placeholders.size() > 0) {
            StringBuffer buffer = new StringBuffer();
            buffer.append("<table>");
            buffer.append("<tr><th>Placeholder</th><th>Meaning</th><th>Example</th></tr>");
            for (Entry<String, PlaceholderInfo> entry : placeholders.entrySet()) {
                PlaceholderInfo info = entry.getValue();
                buffer.append("<tr>");
                buffer.append("<td>").append(entry.getKey()).append("</td>");
                buffer.append("<td>").append(info.name).append("</td>");
                buffer.append("<td>").append(info.example).append("</td>");
                buffer.append("</tr>");
            }
            buffer.append("</table>");
            return buffer.toString();
        }
        return "";
    }

    private static boolean isRootCode(String code, Set<String> allMetazones, String type, boolean isMetazone) {
        Set<String> codes = isMetazone ? allMetazones  
                : type.equals("timezone") ? STANDARD_CODES.getCanonicalTimeZones() 
                        : STANDARD_CODES.getSurveyToolDisplayCodes(type); 
                // end
                boolean isRootCode = codes.contains(code) || code.contains("_");
                if (!isRootCode && type.equals("language") 
                        && EXTRA_LANGUAGES.contains(code)) {
                    isRootCode = true;
                }
                return isRootCode;
    }

    private List<String> addStarredInfo(Map<String, List<Set<String>>> starredPaths, String path) {
        Matcher starAttributeMatcher = STAR_ATTRIBUTE_PATTERN.matcher(path);
        StringBuilder starredPath = new StringBuilder();
        List<String> attributes = new ArrayList<String>();
        int lastEnd = 0;
        while (starAttributeMatcher.find()) {
            int start = starAttributeMatcher.start(1);
            int end = starAttributeMatcher.end(1);
            starredPath.append(path.substring(lastEnd, start));
            starredPath.append(".*");

            attributes.add(path.substring(start, end));
            lastEnd = end;
        }
        starredPath.append(path.substring(lastEnd));
        String starredPathString = starredPath.toString().intern();
        starredPathOutput = starredPathString;

        List<Set<String>> attributeList = starredPaths.get(starredPathString);
        if (attributeList == null) {
            starredPaths.put(starredPathString, attributeList = new ArrayList<Set<String>>());
        }
        int i = 0;
        for (String attribute : attributes) {
            if (attributeList.size() <= i) {
                TreeSet<String> subset = new TreeSet<String>();
                subset.add(attribute);
                attributeList.add(subset);
            } else {
                Set<String> subset = attributeList.get(i);
                subset.add(attribute);
            }
            ++i;
        }
        return attributes;
    }
}
