package org.unicode.cldr.test;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.unicode.cldr.util.CLDRFile;
import org.unicode.cldr.util.CLDRFile.DtdType;
import org.unicode.cldr.util.CldrUtility;
import org.unicode.cldr.util.Factory;
import org.unicode.cldr.util.PrettyPath;
import org.unicode.cldr.util.XMLFileReader;
import org.unicode.cldr.util.XPathParts;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;

import com.ibm.icu.dev.util.Relation;

/**
 * Simple test that loads each file in the cldr directory, thus verifying that
 * the DTD works, and also checks that the PrettyPaths work.
 * 
 * @author markdavis
 */
public class QuickCheck {
    private static final Set skipAttributes = new HashSet(Arrays.asList(new String[] {
        "alt", "draft", "references" }));

    private static String localeRegex;

    private static boolean showInfo = false;

    private static String commonDirectory;
    private static String mainDirectory;

    private static boolean showForceZoom;

    private static boolean resolved;

    private static Exception[] internalException = new Exception[1];

    private static boolean verbose;

    public static void main(String[] args) throws IOException {
        verbose = CldrUtility.getProperty("verbose", "false", "true").matches("(?i)T|TRUE");
        localeRegex = CldrUtility.getProperty("locale", ".*");

        showInfo = CldrUtility.getProperty("showinfo", "false", "true").matches("(?i)T|TRUE");

        commonDirectory = CldrUtility.COMMON_DIRECTORY; // Utility.getProperty("common", Utility.COMMON_DIRECTORY);
        // if (commonDirectory == null) commonDirectory = Utility.COMMON_DIRECTORY
        // System.out.println("Main Source Directory: " + commonDirectory +
        // "\t\t(to change, use -DSOURCE=xxx, eg -DSOURCE=C:/cvsdata/unicode/cldr/incoming/proposed/main)");

        mainDirectory = CldrUtility.getProperty("main", CldrUtility.COMMON_DIRECTORY + "/main");
        // System.out.println("Main Source Directory: " + commonDirectory +
        // "\t\t(to change, use -DSOURCE=xxx, eg -DSOURCE=C:/cvsdata/unicode/cldr/incoming/proposed/main)");

        showForceZoom = CldrUtility.getProperty("forcezoom", "false", "true").matches("(?i)T|TRUE");

        resolved = CldrUtility.getProperty("resolved", "false", "true").matches("(?i)T|TRUE");

        boolean paths = CldrUtility.getProperty("paths", "true").matches("(?i)T|TRUE");

        pretty = CldrUtility.getProperty("pretty", "true").matches("(?i)T|TRUE");

        double startTime = System.currentTimeMillis();
        checkDtds();
        double deltaTime = System.currentTimeMillis() - startTime;
        System.out.println("Elapsed: " + deltaTime / 1000.0 + " seconds");

        if (paths) {
            System.out.println("Checking paths");
            checkPaths();
            deltaTime = System.currentTimeMillis() - startTime;
            System.out.println("Elapsed: " + deltaTime / 1000.0 + " seconds");
            System.out.println("Basic Test Passes");
        }
    }

    private static void checkDtds() throws IOException {
        checkDtds(commonDirectory + "supplemental");
        checkDtds(commonDirectory + "collation");
        checkDtds(commonDirectory + "main");
        checkDtds(commonDirectory + "rbnf");
        checkDtds(commonDirectory + "segments");
        checkDtds(commonDirectory + "../test");
        checkDtds(commonDirectory + "transforms");
    }

    private static void checkDtds(String directory) throws IOException {
        File directoryFile = new File(directory);
        File[] listFiles = directoryFile.listFiles();
        String canonicalPath = directoryFile.getCanonicalPath();
        if (listFiles == null) {
            throw new IllegalArgumentException("Empty directory: " + canonicalPath);
        }
        System.out.println("Checking files for DTD errors in: " + canonicalPath);
        for (File fileName : listFiles) {
            if (!fileName.toString().endsWith(".xml")) {
                continue;
            }
            check(fileName);
        }
    }

    static class MyErrorHandler implements ErrorHandler {
        public void error(SAXParseException exception) throws SAXException {
            System.out.println("\r\nerror: " + XMLFileReader.showSAX(exception));
            throw exception;
        }

        public void fatalError(SAXParseException exception) throws SAXException {
            System.out.println("\r\nfatalError: " + XMLFileReader.showSAX(exception));
            throw exception;
        }

        public void warning(SAXParseException exception) throws SAXException {
            System.out.println("\r\nwarning: " + XMLFileReader.showSAX(exception));
            throw exception;
        }
    }

    public static void check(File systemID) {
        try {
            FileInputStream fis = new FileInputStream(systemID);
            XMLReader xmlReader = XMLFileReader.createXMLReader(true);
            xmlReader.setErrorHandler(new MyErrorHandler());
            InputSource is = new InputSource(fis);
            is.setSystemId(systemID.toString());
            xmlReader.parse(is);
            fis.close();
        } catch (SAXParseException e) {
            System.out.println("\t" + "Can't read " + systemID);
            System.out.println("\t" + e.getClass() + "\t" + e.getMessage());
        } catch (SAXException e) {
            System.out.println("\t" + "Can't read " + systemID);
            System.out.println("\t" + e.getClass() + "\t" + e.getMessage());
        } catch (IOException e) {
            System.out.println("\t" + "Can't read " + systemID);
            System.out.println("\t" + e.getClass() + "\t" + e.getMessage());
        }
    }

    static Matcher skipPaths = Pattern.compile("/identity" + "|/alias" + "|\\[@alt=\"proposed").matcher("");

    private static boolean pretty;

    private static void checkPaths() {
        Relation<String, String> distinguishing = new Relation(new TreeMap(), TreeSet.class, null);
        Relation<String, String> nonDistinguishing = new Relation(new TreeMap(), TreeSet.class, null);
        XPathParts parts = new XPathParts();
        Factory cldrFactory = Factory.make(mainDirectory, localeRegex);
        CLDRFile english = cldrFactory.make("en", true);

        Relation<String, String> pathToLocale = new Relation(new TreeMap(CLDRFile.ldmlComparator), TreeSet.class, null);
        for (String locale : cldrFactory.getAvailable()) {
            // if (locale.equals("root") && !localeRegex.equals("root"))
            // continue;
            CLDRFile file;
            try {
                file = cldrFactory.make(locale, resolved);
            } catch (Exception e) {
                System.out.println("\r\nfatalError: " + e.getMessage());
                continue;
            }
            if (file.isNonInheriting())
                continue;
            DisplayAndInputProcessor displayAndInputProcessor = new DisplayAndInputProcessor(file);

            System.out.println(locale + "\t-\t" + english.getName(locale));
            DtdType dtdType = null;

            for (Iterator<String> it = file.iterator(); it.hasNext();) {
                String path = it.next();
                if (path.endsWith("/alias")) {
                    continue;
                }
                String value = file.getStringValue(path);
                if (value == null) {
                    throw new IllegalArgumentException(locale + "\tError: in null value at " + path);
                }
                String displayValue = displayAndInputProcessor.processForDisplay(path, value);
                if (!displayValue.equals(value)) {
                    System.out.println("\t" + locale + "\tdisplayAndInputProcessor changes display value <" + value
                        + ">\t=>\t<" + displayValue + ">\t\t" + path);
                }
                String inputValue = displayAndInputProcessor.processInput(path, value, internalException);
                if (internalException[0] != null) {
                    System.out.println("\t" + locale + "\tdisplayAndInputProcessor internal error <" + value
                        + ">\t=>\t<" + inputValue + ">\t\t" + path);
                    internalException[0].printStackTrace(System.out);
                }
                if (verbose && !inputValue.equals(value)) {
                    displayAndInputProcessor.processInput(path, value, internalException); // for debugging
                    System.out.println("\t" + locale + "\tdisplayAndInputProcessor changes input value <" + value
                        + ">\t=>\t<" + inputValue + ">\t\t" + path);
                }

                pathToLocale.put(path, locale);

                // also check for non-distinguishing attributes
                if (path.contains("/identity")) continue;

                // make sure we don't have problem alts
                if (path.contains("proposed")) {
                    String sourceLocale = file.getSourceLocaleID(path, null);
                    if (locale.equals(sourceLocale)) {
                        String nonAltPath = file.getNondraftNonaltXPath(path);
                        if (!path.equals(nonAltPath)) {
                            String nonAltLocale = file.getSourceLocaleID(nonAltPath, null);
                            String nonAltValue = file.getStringValue(nonAltPath);
                            if (nonAltValue == null || !locale.equals(nonAltLocale)) {
                                System.out.println("\t" + locale + "\tProblem alt=proposed <" + value + ">\t\t" + path);
                            }
                        }
                    }
                }

                String fullPath = file.getFullXPath(path);
                parts.set(fullPath);
                if (dtdType == null) {
                    dtdType = DtdType.valueOf(parts.getElement(0));
                }
                for (int i = 0; i < parts.size(); ++i) {
                    if (parts.getAttributeCount(i) == 0) continue;
                    String element = parts.getElement(i);
                    for (String attribute : parts.getAttributeKeys(i)) {
                        if (skipAttributes.contains(attribute)) continue;
                        if (CLDRFile.isDistinguishing(dtdType, element, attribute)) {
                            distinguishing.put(element, attribute);
                        } else {
                            nonDistinguishing.put(element, attribute);
                        }
                    }
                }
            }
        }
        System.out.println();

        System.out.format("Distinguishing Elements: %s" + CldrUtility.LINE_SEPARATOR, distinguishing);
        System.out.format("Nondistinguishing Elements: %s" + CldrUtility.LINE_SEPARATOR, nonDistinguishing);
        System.out.format("Skipped %s" + CldrUtility.LINE_SEPARATOR, skipAttributes);

        if (verbose) {
            System.out.println(CldrUtility.LINE_SEPARATOR + "Paths to skip in Survey Tool");
            for (String path : pathToLocale.keySet()) {
                if (CheckCLDR.skipShowingInSurvey.matcher(path).matches()) {
                    System.out.println("Skipping: " + path);
                }
            }

            System.out.println(CldrUtility.LINE_SEPARATOR + "Paths to force zoom in Survey Tool");
            for (String path : pathToLocale.keySet()) {
                if (CheckCLDR.FORCE_ZOOMED_EDIT.matcher(path).matches()) {
                    System.out.println("Forced Zoom Edit: " + path);
                }
            }
        }

        if (pretty) {
            if (showInfo) {
                System.out.println(CldrUtility.LINE_SEPARATOR + "Showing Path to PrettyPath mapping"
                    + CldrUtility.LINE_SEPARATOR);
            }
            PrettyPath prettyPath = new PrettyPath().setShowErrors(true);
            Set<String> badPaths = new TreeSet();
            for (String path : pathToLocale.keySet()) {
                String prettied = prettyPath.getPrettyPath(path, false);
                if (showInfo) System.out.println(prettied + "\t\t" + path);
                if (prettied.contains("%%") && !path.contains("/alias")) {
                    badPaths.add(path);
                }
            }
            // now remove root

            if (showInfo) {
                System.out.println(CldrUtility.LINE_SEPARATOR + "Showing Paths not in root"
                    + CldrUtility.LINE_SEPARATOR);
            }

            CLDRFile root = cldrFactory.make("root", true);
            for (Iterator<String> it = root.iterator(); it.hasNext();) {
                pathToLocale.removeAll(it.next());
            }
            if (showInfo) for (String path : pathToLocale.keySet()) {
                if (skipPaths.reset(path).find()) {
                    continue;
                }
                System.out.println(path + "\t" + pathToLocale.getAll(path));
            }

            if (badPaths.size() != 0) {
                System.out.println("Error: " + badPaths.size()
                    + " Paths were not prettied: use -DSHOW and look for ones with %% in them.");
            }
        }
    }

}