/*
 * Copyright 2004-2005 The Trix Development Team.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.trix.cuery;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.trix.cuery.css.AbstractCSSConsumer;
import org.trix.cuery.parser.CueryParser;
import org.trix.cuery.property.PropertyDefinition;
import org.trix.cuery.property.PropertyRegistry;
import org.trix.cuery.util.CSSUtil;
import org.trix.cuery.util.I18nUtil;
import org.trix.cuery.value.CSSValue;
import org.trix.cuery.value.ValueUtil;

import org.w3c.css.sac.CSSException;
import org.w3c.css.sac.CSSParseException;
import org.w3c.css.sac.InputSource;
import org.w3c.css.sac.LexicalUnit;
import org.w3c.css.sac.SACMediaList;

/**
 * This class validates the syntax of CSS and the validity verification according to property
 * definitions.
 * 
 * @author <a href="mailto:Teletha.T@gmail.com">Teletha Testarossa</a>
 * @version $ Id: CSSValidator.java,v 1.01 2005/09/10 12:59:15 Teletha Exp $
 */
public class CSSValidator {

    /** The single parser. */
    private static final CueryParser PARSER = new CueryParser();

    /** The validation options */
    private Option option = new Option();

    /**
     * Allow validation with underscore hack.
     * 
     * @param permission True or false.
     */
    public void allowUnderscoreHack(boolean permission) {
        this.option.underscoreHack = permission;
    }

    /**
     * Validate css.
     * 
     * @param source A target css.
     * @return A list of invalid points.
     * @throws IOException If this css has I/O error.
     */
    public CSSParseException[] validate(InputSource source) throws IOException {
        ValidatableConsumer consumer = new ValidatableConsumer();
        consumer.option = option;
        PARSER.setDocumentHandler(consumer);
        PARSER.parseStyleSheet(source);

        return (CSSParseException[]) consumer.container.toArray(new CSSParseException[consumer.container.size()]);
    }

    /**
     * DOCUMENT.
     * 
     * @author <a href="mailto:Teletha.T@gmail.com">Teletha Testarossa</a>
     * @version $ Id: ValidatableConsumer.java,v 1.01 2005/11/15 5:12:14 Teletha Exp $
     */
    private static class ValidatableConsumer extends AbstractCSSConsumer {

        /** The error container. */
        private List container = new ArrayList();

        /** The validation options */
        private Option option;

        /** The list of imported stylesheets. */
        private LinkedList imports = new LinkedList();

        /**
         * @see org.trix.cuery.css.AbstractCSSConsumer#endDocument(org.w3c.css.sac.InputSource)
         */
        public void endDocument(InputSource source) throws CSSException {
            if (imports.size() == 0) {
                return;
            }

            try {
                URL url = (URL) imports.removeFirst();
                PARSER.parseStyleSheet(url.toExternalForm());
            } catch (CSSException e) {
                System.out.println(e);
            } catch (IOException e) {
                System.out.println(e);
            }
        }

        /**
         * @see org.trix.cuery.css.AbstractCSSPipe#importStyle(java.lang.String,
         *      org.w3c.css.sac.SACMediaList, java.lang.String)
         */
        public void importStyle(String uri, SACMediaList mediaList, String defaultNamespaceURI) throws CSSException {
            // chek null
            if (uri == null || uri.length() == 0) {
                return;
            }

            try {
                URL url = new URL(new URL(PARSER.getLocator().getURI()), uri);

                System.out.println(url);
                imports.add(url);
            } catch (MalformedURLException e) {
                System.out.println(e);
            }
        }

        /**
         * @see org.w3c.css.sac.DocumentHandler#property(java.lang.String,
         *      org.w3c.css.sac.LexicalUnit, boolean)
         */
        public void property(String name, LexicalUnit value, boolean important) throws CSSException {
            // underscore hack
            if (option.underscoreHack) {
                if (name.charAt(0) == '_') {
                    name = name.substring(1);
                }
            }

            try {
                PropertyDefinition definition = PropertyRegistry.getDefinition(name);

                CSSValue cssValue = CSSUtil.convert(value);
                Map result = definition.parse(cssValue);

                if (result == null) {
                    addError(I18nUtil.getText("validator.invalidValue", name, ValueUtil.toString(cssValue)));
                }
            } catch (IllegalArgumentException e) {
                addError(I18nUtil.getText("validator.propertyNotDefined", name));
            }
        }

        /**
         * @see org.trix.cuery.css.AbstractCSSConsumer#error(org.w3c.css.sac.CSSParseException)
         */
        public void error(CSSParseException exception) throws CSSException {
            addError(exception);
        }

        /**
         * @see org.trix.cuery.css.AbstractCSSConsumer#fatalError(org.w3c.css.sac.CSSParseException)
         */
        public void fatalError(CSSParseException exception) throws CSSException {
            addError(exception);
        }

        /**
         * @see org.trix.cuery.css.AbstractCSSConsumer#warning(org.w3c.css.sac.CSSParseException)
         */
        public void warning(CSSParseException exception) throws CSSException {
            addError(exception);
        }

        /**
         * Add an error.
         * 
         * @param message An error message
         */
        private void addError(String message) {
            CSSParseException exception = new CSSParseException(message, PARSER.getLocator());
            container.add(exception);
        }

        /**
         * Add an error.
         * 
         * @param ex An error
         */
        private void addError(CSSParseException ex) {
            String message;

            if (ex.getException() == null) {
                message = ex.getLocalizedMessage();
            } else {
                message = ex.getException().getMessage();
            }
            container.add(new CSSParseException(message, ex.getURI(), ex.getLineNumber(), ex.getColumnNumber()));
        }
    }

    /**
     * DOCUMENT.
     * 
     * @author <a href="mailto:Teletha.T@gmail.com">Teletha Testarossa</a>
     * @version $ Id: Option.java,v 1.0 2005/09/10 12:55:29 Teletha Exp $
     */
    private static class Option {

        /** The flag for underscore hack. */
        private boolean underscoreHack = false;
    }
}
