package org.unicode.cldr.test;

import java.util.EnumMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.unicode.cldr.test.CheckCLDR.CheckStatus.Subtype;
import org.unicode.cldr.util.CLDRFile.DraftStatus;
import org.unicode.cldr.util.Factory;
import org.unicode.cldr.util.LogicalGrouping;
import org.unicode.cldr.util.PathHeader;
import org.unicode.cldr.util.XMLSource;
import org.unicode.cldr.util.XPathParts;

public class CheckLogicalGroupings extends FactoryCheckCLDR {

    public CheckLogicalGroupings(Factory factory) {
        super(factory);
    }
    
    // Change MINIMUM_DRAFT_STATUS to DraftStatus.contributed if you only care about
    // contributed or higher. This can help to reduce the error count when you have a lot of new data.

    static final DraftStatus MIMIMUM_DRAFT_STATUS = DraftStatus.approved;

    // remember to add this class to the list in CheckCLDR.getCheckAll
    // to run just this test, on just locales starting with 'nl', use CheckCLDR with -fnl.* -t.*LogicalGroupings.*

    //private XPathParts parts = new XPathParts(); // used to parse out a path
    
    /**
     * We are not as strict with sublocales (where the parent is neither root nor code_fallback). 
     * @param path
     * @return
     */
    public boolean isHereOrNonRoot(String path) {
        if (getCldrFileToCheck().isHere(path)) {
            return true;
        }
        if (!getCldrFileToCheck().getLocaleID().contains("_")) { // quick check for top level
            return false;
        }
        String value = getResolvedCldrFileToCheck().getStringValue(path);
        if (value == null) {
            return false;
        }
        // the above items are just for fast checking.
        // check the origin of the value, and make sure it is not ≥ root
        String source = getResolvedCldrFileToCheck().getSourceLocaleID(path, null);
        return !source.equals(XMLSource.ROOT_ID) && !source.equals(XMLSource.CODE_FALLBACK_ID);
    }
    
    public CheckCLDR handleCheck(String path, String fullPath, String value, Options options,
        List<CheckStatus> result) {
        // if (fullPath == null) return this; // skip paths that we don't have
        if (LogicalGrouping.isOptional(getCldrFileToCheck(), path)) return this;
        Set<String> paths = LogicalGrouping.getPaths(getCldrFileToCheck(), path);
        Set<String> paths2 = new HashSet<String>(paths);
        for (String p : paths2) {
            if (LogicalGrouping.isOptional(getCldrFileToCheck(), p)) {
                paths.remove(p);
            }
        }
        if (paths.size() < 2) return this; // skip if not part of a logical grouping
        int logicalGroupingCount = 0;
        for (String apath : paths) {
            if (isHereOrNonRoot(apath)) {
                logicalGroupingCount++;
            }
        }
        if (logicalGroupingCount == 0) return this; // skip if the logical grouping is empty
        if (!isHereOrNonRoot(path) || 
            (this.getPhase().equals(Phase.FINAL_TESTING) && logicalGroupingCount != paths.size())) {
            CheckStatus.Type showError = CheckStatus.errorType;
            if (this.getPhase().equals(Phase.BUILD)) {
                showError = CheckStatus.warningType;
            }
            result.add(new CheckStatus().setCause(this).setMainType(showError)
                .setSubtype(Subtype.incompleteLogicalGroup)
                .setMessage("Incomplete logical group - must enter a value for all fields in the group"));
        }

        // Special test during vetting phase to allow changes in a logical group when another item in the group
        // contains an error or warning.  See http://unicode.org/cldr/trac/ticket/4943.
        // I added the option lgWarningCheck so that we don't loop back on ourselves forever.
        // JCE: 2015-04-13: I don't think we need this any more, since we implemented
        // http://unicode.org/cldr/trac/ticket/6480, and it really slows things down.
        // I'll just comment it out until we get through a whole release without it.
        // TODO: Remove it completely if we really don't need it.
        //if (Phase.VETTING.equals(this.getPhase()) && options.get(Options.Option.lgWarningCheck) != "true") {
        //    Options checkOptions = options.clone();
        //    checkOptions.set("lgWarningCheck", "true");
        //    List<CheckStatus> statuses = new ArrayList<CheckStatus>();
        //    CompoundCheckCLDR secondaryChecker = CheckCLDR.getCheckAll(CLDRConfig.getInstance().getFullCldrFactory(), ".*");
        //    secondaryChecker.setCldrFileToCheck(getCldrFileToCheck(), checkOptions, statuses);

        //    for (String apath : paths) {
        //        if (apath == path) {
        //            continue;
        //        }
        //        String fPath = getCldrFileToCheck().getFullXPath(apath);
        //        if (fPath == null) {
        //            continue;
        //        }
        //        secondaryChecker.check(apath, fPath, getCldrFileToCheck().getWinningValue(apath), checkOptions, statuses);
        //        if (CheckStatus.hasType(statuses, Type.Error) || CheckStatus.hasType(statuses, Type.Warning)) {
        //            result.add(new CheckStatus().setCause(this).setMainType(CheckStatus.warningType)
        //                .setSubtype(Subtype.errorOrWarningInLogicalGroup)
        //                .setMessage("Error or warning within this logical group.  May require a change on this item to make the group correct."));
        //            break;
        //        }
        //    }
        //}

        //if (Phase.FINAL_TESTING.equals(this.getPhase())) {
        DraftStatus myStatus = null;
        EnumMap<DraftStatus, PathHeader> draftStatuses = new EnumMap<DraftStatus, PathHeader>(DraftStatus.class);
        for (String apath : paths) {
            String fPath = getCldrFileToCheck().getFullXPath(apath);
            if (fPath == null) {
                continue;
            }
            if (apath.startsWith("//ldml/dates/calendars/calendar[@type=\"gregorian\"]/days/dayContext[@type=\"format\"]/dayWidth[@type=\"wide\"]/day")) {
                int debug = 0;
            }
            XPathParts parts = XPathParts.getFrozenInstance(fPath);
            DraftStatus draftStatus = DraftStatus.forString(parts.findFirstAttributeValue("draft"));

            // anything at or above the minimum is ok.

            if (draftStatus.compareTo(MIMIMUM_DRAFT_STATUS) >= 0) {
                draftStatus = DraftStatus.approved;
            }
            if (apath.equals(path)) { // record what this path has, for later.
                myStatus = draftStatus;
            }
            PathHeader old = draftStatuses.get(draftStatus);
            if (old == null) { // take first or path itself
                draftStatuses.put(draftStatus, getPathHeaderFactory().fromPath(apath));
            }
        }
        if (draftStatuses.size() > 1 && myStatus != DraftStatus.approved) { // only show errors for the items that
            // have insufficient status
            if (myStatus != null) { // remove my status from the list
                draftStatuses.remove(myStatus);
            }
            CheckStatus.Type showError = CheckStatus.warningType;           
            if (this.getPhase().equals(Phase.FINAL_TESTING)) {
                showError = CheckStatus.errorType;
            }
            result.add(new CheckStatus().setCause(this).setMainType(showError)
                .setSubtype(Subtype.inconsistentDraftStatus) // typically warningType or errorType
                .setMessage("Logical group problem.  All members of a logical group need to be confirmed together. "
                    + "For how to do this, see <a target='CLDR-ST-DOCS' href='http://cldr.org/translation/logical-groups'>Logical Groups</a>"
                    + "​ in the CLDR translation guidelines.")); // the
            // message;
            // can
            // be
            // MessageFormat
            // with
            // arguments
        }
        // }
        return this;
    }
}
