/*
 * This file is part of the RELAX NG schemas far (X)HTML5. 
 * Please see the file named LICENSE in the relaxng directory for 
 * license details.
 */

package org.whattf.syntax;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.MalformedURLException;

import nu.validator.gnu.xml.aelfred2.SAXDriver;
import nu.validator.htmlparser.sax.HtmlParser;
import nu.validator.xml.IdFilter;
import nu.validator.xml.SystemErrErrorHandler;

import org.whattf.checker.NormalizationChecker;
import org.whattf.checker.TextContentChecker;
import org.whattf.checker.jing.CheckerSchema;
import org.whattf.checker.jing.CheckerValidator;
import org.whattf.checker.table.TableChecker;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.XMLReader;

import com.thaiopensource.relaxng.impl.CombineValidator;
import com.thaiopensource.util.PropertyMap;
import com.thaiopensource.util.PropertyMapBuilder;
import com.thaiopensource.validate.Schema;
import com.thaiopensource.validate.SchemaReader;
import com.thaiopensource.validate.ValidateProperty;
import com.thaiopensource.validate.Validator;
import com.thaiopensource.validate.auto.AutoSchemaReader;
import com.thaiopensource.validate.prop.rng.RngProperty;
import com.thaiopensource.validate.rng.CompactSchemaReader;
import com.thaiopensource.xml.sax.CountingErrorHandler;
import com.thaiopensource.xml.sax.Jaxp11XMLReaderCreator;


/**
 * 
 * @version $Id: Driver.java 404 2009-04-05 11:08:21Z sideshowbarker $
 * @author hsivonen
 */
public class Driver {

    private static final String PATH = "syntax/relaxng/tests/";
    
    private PrintWriter err;

    private PrintWriter out;

    private Schema mainSchema;

    private Schema assertionSchema;

    private Validator validator;

    private SystemErrErrorHandler errorHandler = new SystemErrErrorHandler();

    private CountingErrorHandler countingErrorHandler = new CountingErrorHandler();

    private HtmlParser htmlParser = new HtmlParser();

    private XMLReader xmlParser;

    private boolean failed = false;

    private boolean verbose;

    /**
     * @param basePath
     */
    public Driver(boolean verbose) {
        this.verbose = verbose;
        try {
            this.err = new PrintWriter(new OutputStreamWriter(System.err,
                    "UTF-8"));
            this.out = new PrintWriter(new OutputStreamWriter(System.out,
                    "UTF-8"));
            /*
             * SAXParserFactory factory = SAXParserFactory.newInstance();
             * factory.setNamespaceAware(true); factory.setValidating(false);
             * XMLReader parser = factory.newSAXParser().getXMLReader();
             */
            this.xmlParser = new IdFilter(new SAXDriver());
        } catch (Exception e) {
            // If this happens, the JDK is too broken anyway
            throw new RuntimeException(e);
        }
    }

    private Schema schemaByFilename(File name) throws Exception {
        PropertyMapBuilder pmb = new PropertyMapBuilder();
        pmb.put(ValidateProperty.ERROR_HANDLER, errorHandler);
        pmb.put(ValidateProperty.XML_READER_CREATOR,
                new Jaxp11XMLReaderCreator());
        RngProperty.CHECK_ID_IDREF.add(pmb);
        PropertyMap jingPropertyMap = pmb.toPropertyMap();

        InputSource schemaInput;
        try {
            schemaInput = new InputSource(name.toURL().toString());
        } catch (MalformedURLException e1) {
            System.err.println();
            e1.printStackTrace(err);
            failed = true;
            return null;
        }
        SchemaReader sr;
        if (name.getName().endsWith(".rnc")) {
            sr = CompactSchemaReader.getInstance();
        } else {
            sr = new AutoSchemaReader();
        }
        return sr.createSchema(schemaInput, jingPropertyMap);
    }

    private void checkFile(File file) throws IOException, SAXException {
        validator.reset();
        InputSource is = new InputSource(new FileInputStream(file));
        is.setSystemId(file.toURL().toString());
        String name = file.getName();
        if (name.endsWith(".html") || name.endsWith(".htm")) {
            is.setEncoding("UTF-8");
            if (verbose) {
                out.println(file);
                out.flush();
            }
            htmlParser.parse(is);
        } else if (name.endsWith(".xhtml") || name.endsWith(".xht")) {
            if (verbose) {
                out.println(file);
                out.flush();
            }
            xmlParser.parse(is);
        }
    }

    private boolean isCheckableFile(File file) {
        String name = file.getName();
        return file.isFile()
                && (name.endsWith(".html") || name.endsWith(".htm")
                        || name.endsWith(".xhtml") || name.endsWith(".xht"));
    }

    private void checkValidFiles(File directory) {
        File[] files = directory.listFiles();
        for (int i = 0; i < files.length; i++) {
            File file = files[i];
            if (isCheckableFile(file)) {
                errorHandler.reset();
                try {
                    checkFile(file);
                } catch (IOException e) {
                } catch (SAXException e) {
                }
                if (errorHandler.isInError()) {
                    failed = true;
                }
            } else if (file.isDirectory()) {
                checkValidFiles(file);
            }
        }
    }

    private void checkInvalidFiles(File directory) {
        File[] files = directory.listFiles();
        for (int i = 0; i < files.length; i++) {
            File file = files[i];
            if (isCheckableFile(file)) {
                countingErrorHandler.reset();
                try {
                    checkFile(file);
                } catch (IOException e) {
                } catch (SAXException e) {
                }
                if (!countingErrorHandler.getHadErrorOrFatalError()) {
                    failed = true;
                    try {
                        err.println(file.toURL().toString()
                                + " was supposed to be invalid but was not.");
                        err.flush();
                    } catch (MalformedURLException e) {
                        throw new RuntimeException(e);
                    }
                }
            } else if (file.isDirectory()) {
                checkInvalidFiles(file);
            }
        }
    }

    private void checkDirectory(File directory, File schema) throws SAXException {
        try {
            mainSchema = schemaByFilename(schema);
        } catch (Exception e) {
            err.println("Reading schema failed. Skipping test directory.");
            e.printStackTrace();
            err.flush();
            return;
        }
        File[] files = directory.listFiles();
        for (int i = 0; i < files.length; i++) {
            File file = files[i];
            if ("valid".equals(file.getName())) {
                setup(errorHandler);
                checkValidFiles(file);
            } else if ("invalid".equals(file.getName())) {
                setup(countingErrorHandler);
                checkInvalidFiles(file);
            }
        }
    }

    /**
     * @throws SAXNotSupportedException 
     * @throws SAXdException 
     * 
     */
    private void setup(ErrorHandler eh) throws SAXException {
        PropertyMapBuilder pmb = new PropertyMapBuilder();
        pmb.put(ValidateProperty.ERROR_HANDLER, eh);
        pmb.put(ValidateProperty.XML_READER_CREATOR,
                new Jaxp11XMLReaderCreator());
        RngProperty.CHECK_ID_IDREF.add(pmb);
        PropertyMap jingPropertyMap = pmb.toPropertyMap();

        validator = mainSchema.createValidator(jingPropertyMap);
        Validator assertionValidator = assertionSchema.createValidator(jingPropertyMap);
        validator = new CombineValidator(validator, assertionValidator);
        validator = new CombineValidator(validator, new CheckerValidator(
                new TableChecker(), jingPropertyMap));
        validator = new CombineValidator(validator, new CheckerValidator(
                new NormalizationChecker(), jingPropertyMap));
        validator = new CombineValidator(validator, new CheckerValidator(
                new TextContentChecker(), jingPropertyMap));

        htmlParser.setContentHandler(validator.getContentHandler());
        htmlParser.setErrorHandler(eh);
        htmlParser.setFeature("http://xml.org/sax/features/unicode-normalization-checking", true);
        xmlParser.setContentHandler(validator.getContentHandler());
        xmlParser.setErrorHandler(eh);
        xmlParser.setFeature("http://xml.org/sax/features/unicode-normalization-checking", true);
        htmlParser.setMappingLangToXmlLang(true);
    }

    public boolean check() throws SAXException {
        try {
            assertionSchema = CheckerSchema.ASSERTION_SCH;
        } catch (Exception e) {
            err.println("Reading schema failed. Terminating.");
            e.printStackTrace();
            err.flush();
            return false;
        }

        checkDirectory(new File(PATH + "html5core/"), new File(PATH + "../xhtml5core.rnc"));
        checkDirectory(new File(PATH + "html5core-plus-web-forms2/"), new File(
                PATH + "../xhtml5core-plus-web-forms2.rnc"));
        checkDirectory(new File(PATH + "html5full-html/"), new File(
                PATH + "../html5full.rnc"));
        checkDirectory(new File(PATH + "html5full-xhtml/"), new File(
                PATH + "../xhtml5full-xhtml.rnc"));
        checkDirectory(new File(PATH + "assertions/"), new File(
                PATH + "../xhtml5full-xhtml.rnc"));

        checkDirectory(new File(PATH + "tables/"), new File(PATH + "../xhtml5full-xhtml.rnc"));
        checkDirectory(new File(PATH + "media-queries/"), new File(PATH + "../html5full.rnc"));
        checkDirectory(new File(PATH + "mime-types/"), new File(PATH + "../html5full.rnc"));
        if (verbose) {
            if (failed) {
                out.println("Failure!");
            } else {
                out.println("Success!");
            }
        }
        err.flush();
        out.flush();
        return !failed;
    }

    /**
     * @param args
     * @throws SAXException 
     */
    public static void main(String[] args) throws SAXException {
        boolean verbose = ((args.length == 1) && "-v".equals(args[0]));
        Driver d = new Driver(verbose);
        if (d.check()) {
            System.exit(0);
        } else {
            System.exit(-1);
        }
    }
}
