/*
 * 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.property;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.trix.cuery.property.config.DefaultFontConfiguration;
import org.trix.cuery.property.config.FontConfiguration;
import org.trix.cuery.value.AttrFunction;
import org.trix.cuery.value.CSSAngle;
import org.trix.cuery.value.CSSColor;
import org.trix.cuery.value.CSSFrequency;
import org.trix.cuery.value.CSSLength;
import org.trix.cuery.value.CSSNumber;
import org.trix.cuery.value.CSSPercentage;
import org.trix.cuery.value.CSSString;
import org.trix.cuery.value.CSSTime;
import org.trix.cuery.value.CSSURI;
import org.trix.cuery.value.CSSValue;
import org.trix.cuery.value.CounterFunction;
import org.trix.cuery.value.CountersFunction;
import org.trix.cuery.value.Identifier;
import org.trix.cuery.value.RectFunction;

import org.w3c.css.sac.LexicalUnit;
import org.w3c.dom.DOMException;
import org.w3c.dom.css.Counter;
import org.w3c.dom.css.RGBColor;
import org.w3c.dom.css.Rect;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * DOCUMENT.
 * 
 * @author <a href="mailto:Teletha.T@gmail.com">Teletha Testarossa</a>
 * @version $ Id: PropertyRegistry.java,v 1.04 2005/09/27 20:05:21 Teletha Exp $
 */
public final class PropertyRegistry {

    /** The default font configuration. */
    private static FontConfiguration fontConfiguration = new DefaultFontConfiguration();

    /** The path to the default property definition for CSS2. */
    private static final String CSS2 = "org/trix/cuery/property/css2/definition.xml";

    /** The path to the default property definition for CSS3. */
    private static final String CSS3 = "org/trix/cuery/property/css3/definition.xml";

    /** The property definition mapping. */
    private static final Map DEFINITIONS = new HashMap();

    /** The xml reader. */
    private static SAXParser parser;

    // initialize
    static {
        SAXParserFactory factory = SAXParserFactory.newInstance();

        try {
            parser = factory.newSAXParser();

            InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(CSS2);
            readPropertyDefinition(new InputSource(stream));

            stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(CSS3);
            readPropertyDefinition(new InputSource(stream));
        } catch (Exception e) {
            System.out.println("PropertyRegistery initialization phase   " + e);
        }
    }

    /**
     * Avoid creating PropertyRegistry instance.
     */
    private PropertyRegistry() {
    }

    /**
     * Return a current font configuration.
     * 
     * @return A current font configuration.
     */
    public static FontConfiguration getFontConfiguration() {
        return fontConfiguration;
    }

    /**
     * Set a current font configuration.
     * 
     * @param fontConfiguration A font configuration to set.
     */
    public static void setFontConfiguration(FontConfiguration fontConfiguration) {
        if (fontConfiguration == null) {
            return;
        }
        PropertyRegistry.fontConfiguration = fontConfiguration;
    }

    /**
     * Retrive a specified property definition.
     * 
     * @param name A property name.
     * @return A definition.
     * @throws IllegalArgumentException If the suitable property definition is not found.
     */
    public static PropertyDefinition getDefinition(String name) throws IllegalArgumentException {
        PropertyDefinition definition = (PropertyDefinition) DEFINITIONS.get(name);

        if (definition == null) {
            throw new IllegalArgumentException("The property definition for '" + name + "' is not found.");
        }
        return definition;
    }

    /**
     * Input property difinition file.
     * 
     * @param source A property definition file.
     * @throws IOException If the file has I/O error.
     */
    public static void readPropertyDefinition(InputSource source) throws IOException {
        readPropertyDefinition(source, false);
    }

    /**
     * Input property difinition file.
     * 
     * @param source A property definition file.
     * @param override A override flag.
     * @throws IOException If the file has I/O error.
     */
    public static void readPropertyDefinition(InputSource source, boolean override) throws IOException {
        try {
            parser.parse(source, new DefinitionReader(override));
        } catch (SAXException e) {
            throw new IOException(e.getLocalizedMessage());
        } catch (IOException e) {
            throw new IOException(e.getLocalizedMessage());
        }
    }

    /**
     * DOCUMENT.
     * 
     * @author <a href="mailto:Teletha.T@gmail.com">Teletha Testarossa</a>
     * @version $ Id: DefinitionReader.java,v 1.01 2005/09/27 20:01:26 Teletha Exp $
     */
    private static final class DefinitionReader extends DefaultHandler {

        /** The class type mapping. */
        private static final Map CLASSTYPES = new HashMap();

        // initialize
        static {
            CLASSTYPES.put("angle", CSSAngle.class);
            CLASSTYPES.put("attr", AttrFunction.class);
            CLASSTYPES.put("color", CSSColor.class);
            CLASSTYPES.put("counter", CounterFunction.class);
            CLASSTYPES.put("counters", CountersFunction.class);
            CLASSTYPES.put("frequency", CSSFrequency.class);
            CLASSTYPES.put("ident", Identifier.class);
            CLASSTYPES.put("integer", CSSNumber.class);
            CLASSTYPES.put("length", CSSLength.class);
            CLASSTYPES.put("number", CSSNumber.class);
            CLASSTYPES.put("percentage", CSSPercentage.class);
            CLASSTYPES.put("rect", RectFunction.class);
            CLASSTYPES.put("string", CSSString.class);
            CLASSTYPES.put("time", CSSTime.class);
            CLASSTYPES.put("uri", CSSURI.class);
        }

        /** The current processing property definition. */
        private PropertyDefinition current;

        /** The override flag. */
        private boolean override = false;

        /**
         * Create DefinitionReader instance.
         * 
         * @param override A override flag.
         */
        private DefinitionReader(boolean override) {
            this.override = override;
        }

        /**
         * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String,
         *      java.lang.String, org.xml.sax.Attributes)
         */
        public void startElement(String namespaceURI, String localName, String qName, Attributes attributes)
                throws SAXException {
            if (qName.equals("property-def")) {
                String name = attributes.getValue("name");

                // check override
                if (!override && DEFINITIONS.containsKey(name)) {
                    return;
                }

                String inherited = attributes.getValue("inherited");
                String initialValue = attributes.getValue("initial");
                String className = attributes.getValue("class");
                PropertyDefinition definition;

                if (className == null) {
                    definition = new SimplePropertyDefinition();
                } else {
                    try {
                        definition = (PropertyDefinition) Class.forName(className).newInstance();
                    } catch (InstantiationException e) {
                        System.out.println(e);
                        return;
                    } catch (IllegalAccessException e) {
                        System.out.println(e);
                        return;
                    } catch (ClassNotFoundException e) {
                        System.out.println(e);
                        return;
                    }
                }

                CSSValue initial = null;

                if (initialValue == null) {
                    initial = UserAgentDependnecy.SINGLETON;
                } else if (initialValue.endsWith(CSSPercentage.UNIT)) {
                    initial = new CSSPercentage(initialValue, null);
                } else {
                    initial = new Identifier(initialValue, null);
                }

                definition.setup(name, inherited.equals("yes"), initial);

                if (!DEFINITIONS.containsKey(name)) {
                    DEFINITIONS.put(name, definition);
                }
                current = definition;
            } else if (qName.equals("keyword")) {
                current.addAcceptable(attributes.getValue("name"));
            } else if (qName.equals("datatype")) {
                Class clazz = (Class) CLASSTYPES.get(attributes.getValue("name"));

                if (clazz != null) {
                    current.addAcceptable(clazz);
                }
            } else if (qName.equals("function")) {
                Class clazz = (Class) CLASSTYPES.get(attributes.getValue("name"));

                if (clazz != null) {
                    current.addAcceptable(clazz);
                }
            } else if (qName.equals("property")) {
                PropertyDefinition definition = (PropertyDefinition) DEFINITIONS.get(attributes.getValue("name"));

                if (definition != null) {
                    current.addAcceptable(definition);
                }
            }
        }
    }

    /**
     * DOCUMENT.
     * 
     * @author <a href="mailto:Teletha.T@gmail.com">Teletha Testarossa</a>
     * @version $ Id: UserAgentDependnecy.java,v 1.01 2005/09/28 11:15:14 Teletha Exp $
     */
    private static final class UserAgentDependnecy implements CSSValue {

        /** The singleton. */
        private static final CSSValue SINGLETON = new UserAgentDependnecy();

        /**
         * @see org.w3c.dom.css.CSSValue#getCssText()
         */
        public String getCssText() {
            return null;
        }

        /**
         * @see org.w3c.dom.css.CSSValue#getCssValueType()
         */
        public short getCssValueType() {
            return 0;
        }

        /**
         * @see org.w3c.dom.css.CSSValue#setCssText(java.lang.String)
         */
        public void setCssText(String arg0) throws DOMException {
            throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "This method is not supported.");
        }

        /**
         * @see org.trix.cuery.value.CSSValue#get(int)
         */
        public CSSValue get(int index) {
            return null;
        }

        /**
         * @see org.trix.cuery.value.CSSValue#getPosition()
         */
        public int getPosition() {
            return 0;
        }

        /**
         * @see org.trix.cuery.value.CSSValue#getNextValue()
         */
        public CSSValue getNextValue() {
            return null;
        }

        /**
         * @see org.trix.cuery.value.CSSValue#getPreviousValue()
         */
        public CSSValue getPreviousValue() {
            return null;
        }

        /**
         * @see org.trix.cuery.value.CSSValue#getType()
         */
        public Class getType() {
            return getClass();
        }

        /**
         * @see org.w3c.dom.css.CSSValueList#getLength()
         */
        public int getLength() {
            return 0;
        }

        /**
         * @see org.w3c.dom.css.CSSValueList#item(int)
         */
        public org.w3c.dom.css.CSSValue item(int arg0) {
            return null;
        }

        /**
         * @see org.w3c.dom.css.CSSPrimitiveValue#getCounterValue()
         */
        public Counter getCounterValue() throws DOMException {
            throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "This method is not supported.");
        }

        /**
         * @see org.w3c.dom.css.CSSPrimitiveValue#getFloatValue(short)
         */
        public float getFloatValue(short arg0) throws DOMException {
            throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "This method is not supported.");
        }

        /**
         * @see org.w3c.dom.css.CSSPrimitiveValue#getPrimitiveType()
         */
        public short getPrimitiveType() {
            return CSSValue.CSS_UA_DEPENDENCY;
        }

        /**
         * @see org.w3c.dom.css.CSSPrimitiveValue#getRectValue()
         */
        public Rect getRectValue() throws DOMException {
            throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "This method is not supported.");
        }

        /**
         * @see org.w3c.dom.css.CSSPrimitiveValue#getRGBColorValue()
         */
        public RGBColor getRGBColorValue() throws DOMException {
            throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "This method is not supported.");
        }

        /**
         * @see org.w3c.dom.css.CSSPrimitiveValue#setFloatValue(short, float)
         */
        public void setFloatValue(short arg0, float arg1) throws DOMException {
            throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "This method is not supported.");
        }

        /**
         * @see org.w3c.dom.css.CSSPrimitiveValue#setStringValue(short, java.lang.String)
         */
        public void setStringValue(short arg0, String arg1) throws DOMException {
            throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "This method is not supported.");
        }

        /**
         * @see org.w3c.css.sac.LexicalUnit#getDimensionUnitText()
         */
        public String getDimensionUnitText() {
            return null;
        }

        /**
         * @see org.w3c.css.sac.LexicalUnit#getFloatValue()
         */
        public float getFloatValue() {
            return 0;
        }

        /**
         * @see org.w3c.css.sac.LexicalUnit#getFunctionName()
         */
        public String getFunctionName() {
            return null;
        }

        /**
         * @see org.w3c.css.sac.LexicalUnit#getIntegerValue()
         */
        public int getIntegerValue() {
            return 0;
        }

        /**
         * @see org.w3c.css.sac.LexicalUnit#getLexicalUnitType()
         */
        public short getLexicalUnitType() {
            return CSSValue.SAC_UA_DEPENDENCY;
        }

        /**
         * @see org.w3c.css.sac.LexicalUnit#getNextLexicalUnit()
         */
        public LexicalUnit getNextLexicalUnit() {
            return null;
        }

        /**
         * @see org.w3c.css.sac.LexicalUnit#getParameters()
         */
        public LexicalUnit getParameters() {
            return null;
        }

        /**
         * @see org.w3c.css.sac.LexicalUnit#getPreviousLexicalUnit()
         */
        public LexicalUnit getPreviousLexicalUnit() {
            return null;
        }

        /**
         * @see org.w3c.css.sac.LexicalUnit#getStringValue()
         */
        public String getStringValue() {
            return null;
        }

        /**
         * @see org.w3c.css.sac.LexicalUnit#getSubValues()
         */
        public LexicalUnit getSubValues() {
            return null;
        }

        /**
         * @see java.lang.Object#toString()
         */
        public String toString() {
            return "This value depends on user agent.";
        }

    }
}
