/*
 * $Id: CSSOMParser.java,v 1.2 2008/03/26 02:08:55 sdanig Exp $
 *
 * CSS Parser Project
 *
 * Copyright (C) 1999-2008 David Schweinsberg.  All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * To contact the authors of the library:
 *
 * http://cssparser.sourceforge.net/
 * mailto:davidsch@users.sourceforge.net
 */

package com.steadystate.css.parser;

import java.io.IOException;

import java.util.Properties;
import java.util.Stack;

import org.w3c.css.sac.CSSException;
import org.w3c.css.sac.ErrorHandler;
import org.w3c.css.sac.InputSource;
import org.w3c.css.sac.LexicalUnit;
import org.w3c.css.sac.Parser;
import org.w3c.css.sac.SACMediaList;
import org.w3c.css.sac.SelectorList;

import org.w3c.dom.DOMException;
import org.w3c.dom.Node;
import org.w3c.dom.css.CSSRule;
import org.w3c.dom.css.CSSStyleDeclaration;
import org.w3c.dom.css.CSSStyleSheet;
import org.w3c.dom.css.CSSValue;

import org.w3c.css.sac.helpers.ParserFactory;

import com.steadystate.css.dom.CSSCharsetRuleImpl;
import com.steadystate.css.dom.CSSFontFaceRuleImpl;
import com.steadystate.css.dom.CSSImportRuleImpl;
import com.steadystate.css.dom.CSSMediaRuleImpl;
import com.steadystate.css.dom.CSSOMObject;
import com.steadystate.css.dom.CSSPageRuleImpl;
import com.steadystate.css.dom.CSSRuleListImpl;
import com.steadystate.css.dom.CSSStyleDeclarationImpl;
import com.steadystate.css.dom.CSSStyleRuleImpl;
import com.steadystate.css.dom.CSSStyleSheetImpl;
import com.steadystate.css.dom.CSSUnknownRuleImpl;
import com.steadystate.css.dom.CSSValueImpl;
import com.steadystate.css.dom.MediaListImpl;
import com.steadystate.css.dom.Property;
import com.steadystate.css.sac.DocumentHandlerExt;
import com.steadystate.css.userdata.UserDataConstants;

/** 
 *
 * @author <a href="mailto:davidsch@users.sourceforge.net">David Schweinsberg</a>
 * @version $Id: CSSOMParser.java,v 1.2 2008/03/26 02:08:55 sdanig Exp $
 */
public class CSSOMParser {
    
    private static final String PARSER = "com.steadystate.css.parser.SACParserCSS2";
    
    private static boolean use_internal = false;

    private Parser _parser = null;
    private CSSStyleSheetImpl _parentStyleSheet = null;
    // TODO what is this _parentRule for? It is not read locally.
    //private CSSRule _parentRule = null;

    /** Creates new CSSOMParser */
    public CSSOMParser() {
        try {
            // use the direct method if we already failed once before
            if(use_internal) {
                this._parser = new SACParserCSS2();
            } else {
                setProperty("org.w3c.css.sac.parser", PARSER);
                ParserFactory factory = new ParserFactory();
                this._parser = factory.makeParser();
            }
        } catch (Exception e) {
            use_internal = true;
            System.err.println(e.getMessage());
            e.printStackTrace();
            System.err.println("using the default parser instead");
            this._parser = new SACParserCSS2();
        }
    }

    /**
     * Creates new CSSOMParser
     *
     * @param parser the SAC Parser
     */
    public CSSOMParser(Parser parser)
    {
        this._parser = parser;
    }
    

    public void setErrorHandler(ErrorHandler eh)
    {
        this._parser.setErrorHandler(eh);
    }

    /**
     * Parses a SAC input source into a CSSOM style sheet.
     * 
     * @param source the SAC input source
     * @param ownerNode the owner node (see the definition of
     *   <code>ownerNode</code> in org.w3c.dom.css.StyleSheet)
     * @param href the href (see the definition of <code>href</code> in
     *   org.w3c.dom.css.StyleSheet)
     * @return the CSSOM style sheet
     * @throws IOException if the underlying SAC parser throws an IOException
     */
    public CSSStyleSheet parseStyleSheet(InputSource source, Node ownerNode,
        String href) throws IOException {
        CSSOMHandler handler = new CSSOMHandler();
        handler.setOwnerNode(ownerNode);
        handler.setHref(href);
        this._parser.setDocumentHandler(handler);
        this._parser.parseStyleSheet(source);
        Object o = handler.getRoot();
        if (o instanceof CSSStyleSheet)
        {
            return (CSSStyleSheet) handler.getRoot();
        }
        return null;
    }
    
    /**
     * Parses a SAC input source into a CSSOM style declaration.
     * 
     * @param source the SAC input source
     * @return the CSSOM style declaration
     * @throws IOException if the underlying SAC parser throws an IOException
     */
    public CSSStyleDeclaration parseStyleDeclaration(InputSource source)
            throws IOException {
        CSSStyleDeclarationImpl sd = new CSSStyleDeclarationImpl(null);
        parseStyleDeclaration(sd, source);
        return sd;
    }
    
    public void parseStyleDeclaration(CSSStyleDeclaration sd, InputSource source)
            throws IOException {
        Stack nodeStack = new Stack();
        nodeStack.push(sd);
        CSSOMHandler handler = new CSSOMHandler(nodeStack);
        this._parser.setDocumentHandler(handler);
        this._parser.parseStyleDeclaration(source);
    }
    
    public CSSValue parsePropertyValue(InputSource source) throws IOException {
        CSSOMHandler handler = new CSSOMHandler();
        this._parser.setDocumentHandler(handler);
        return new CSSValueImpl(this._parser.parsePropertyValue(source));
    }
    
    public CSSRule parseRule(InputSource source) throws IOException {
        CSSOMHandler handler = new CSSOMHandler();
        this._parser.setDocumentHandler(handler);
        this._parser.parseRule(source);
        return (CSSRule) handler.getRoot();
    }
    
    public SelectorList parseSelectors(InputSource source) throws IOException {
        HandlerBase handler = new HandlerBase();
        this._parser.setDocumentHandler(handler);
        return this._parser.parseSelectors(source);
    }

    public void setParentStyleSheet(CSSStyleSheetImpl parentStyleSheet) {
        this._parentStyleSheet = parentStyleSheet;
    }
    
    protected CSSStyleSheetImpl getParentStyleSheet()
    {
        return this._parentStyleSheet;
    }
    // See _parentRule
    /*
    public void setParentRule(CSSRule parentRule) {
        _parentRule = parentRule;
    }
    */
    
    class CSSOMHandler implements DocumentHandlerExt {
        
        private Stack _nodeStack;
        private Object _root = null;
        private Node ownerNode;
        private String href;

        private Node getOwnerNode()
        {
            return this.ownerNode;
        }

        private void setOwnerNode(Node ownerNode)
        {
            this.ownerNode = ownerNode;
        }

        private String getHref()
        {
            return this.href;
        }

        private void setHref(String href)
        {
            this.href = href;
        }

        public CSSOMHandler(Stack nodeStack) {
            this._nodeStack = nodeStack;
        }
        
        public CSSOMHandler() {
            this._nodeStack = new Stack();
        }
        
        public Object getRoot() {
            return this._root;
        }
        
        public void startDocument(InputSource source) throws CSSException {
            if (this._nodeStack.empty()) {
                CSSStyleSheetImpl ss = new CSSStyleSheetImpl();
                CSSOMParser.this.setParentStyleSheet(ss);
                ss.setOwnerNode(this.getOwnerNode());
                ss.setBaseUri(source.getURI());
                ss.setHref(this.getHref());
                ss.setMediaText(source.getMedia());
                ss.setTitle(source.getTitle());
                // Create the rule list
                CSSRuleListImpl rules = new CSSRuleListImpl();
                ss.setCssRules(rules);
                this._nodeStack.push(ss);
                this._nodeStack.push(rules);
            } else {
                // Error
            }
        }

        public void endDocument(InputSource source) throws CSSException {

            // Pop the rule list and style sheet nodes
            this._nodeStack.pop();
            this._root = this._nodeStack.pop();
        }

        public void comment(String text) throws CSSException {
        }

        public void ignorableAtRule(String atRule) throws CSSException {

            // Create the unknown rule and add it to the rule list
            CSSUnknownRuleImpl ir = new CSSUnknownRuleImpl(
                CSSOMParser.this.getParentStyleSheet(),
                this.getParentRule(),
                atRule);
            this.addLocator(ir);
            if (!this._nodeStack.empty()) {
                ((CSSRuleListImpl)this._nodeStack.peek()).add(ir);
            } else {
//                _nodeStack.push(ir);
                this._root = ir;
            }
        }

        public void namespaceDeclaration(String prefix, String uri)
                throws CSSException {
        }

        public void charset(String characterEncoding) throws CSSException
        {
            CSSCharsetRuleImpl cr = new CSSCharsetRuleImpl(CSSOMParser.this.getParentStyleSheet(),
                this.getParentRule(),
                characterEncoding);
            this.addLocator(cr);
            if (!this._nodeStack.empty())
            {
                ((CSSRuleListImpl)this._nodeStack.peek()).add(cr);
            }
            else
            {
                this._root = cr;
            }
        }

        public void importStyle(
                String uri,
                SACMediaList media, 
                String defaultNamespaceURI) throws CSSException {
            // Create the import rule and add it to the rule list
            CSSImportRuleImpl ir = new CSSImportRuleImpl(
                CSSOMParser.this.getParentStyleSheet(),
                this.getParentRule(),
                uri,
                new MediaListImpl(media));
            this.addLocator(ir);
            if (!this._nodeStack.empty()) {
                ((CSSRuleListImpl)this._nodeStack.peek()).add(ir);
            } else {
//                _nodeStack.push(ir);
                this._root = ir;
            }
        }

        public void startMedia(SACMediaList media) throws CSSException {

            MediaListImpl ml = new MediaListImpl(media);
            this.addLocator(ml);
            // Create the media rule and add it to the rule list
            CSSMediaRuleImpl mr = new CSSMediaRuleImpl(
                CSSOMParser.this.getParentStyleSheet(),
                this.getParentRule(),
                ml);
            this.addLocator(mr);
            if (!this._nodeStack.empty()) {
                ((CSSRuleListImpl)this._nodeStack.peek()).add(mr);
            }

            // Create the rule list
            CSSRuleListImpl rules = new CSSRuleListImpl();
            mr.setRuleList(rules);
            this._nodeStack.push(mr);
            this._nodeStack.push(rules);
        }

        public void endMedia(SACMediaList media) throws CSSException {

            // Pop the rule list and media rule nodes
            this._nodeStack.pop();
            this._root = this._nodeStack.pop();
        }

        public void startPage(String name, String pseudo_page) throws CSSException {

            // Create the page rule and add it to the rule list
            CSSPageRuleImpl pr = new CSSPageRuleImpl(
                CSSOMParser.this.getParentStyleSheet(),
                this.getParentRule(), name, pseudo_page);
            this.addLocator(pr);
            if (!this._nodeStack.empty()) {
                ((CSSRuleListImpl)this._nodeStack.peek()).add(pr);
            }

            // Create the style declaration
            CSSStyleDeclarationImpl decl = new CSSStyleDeclarationImpl(pr);
            pr.setStyle(decl);
            this._nodeStack.push(pr);
            this._nodeStack.push(decl);
        }

        public void endPage(String name, String pseudo_page) throws CSSException {

            // Pop both the style declaration and the page rule nodes
            this._nodeStack.pop();
            this._root = this._nodeStack.pop();
        }

        public void startFontFace() throws CSSException {

            // Create the font face rule and add it to the rule list
            CSSFontFaceRuleImpl ffr = new CSSFontFaceRuleImpl(
                CSSOMParser.this.getParentStyleSheet(),
                this.getParentRule());
            this.addLocator(ffr);
            if (!this._nodeStack.empty()) {
                ((CSSRuleListImpl)this._nodeStack.peek()).add(ffr);
            }

            // Create the style declaration
            CSSStyleDeclarationImpl decl = new CSSStyleDeclarationImpl(ffr);
            ffr.setStyle(decl);
            this._nodeStack.push(ffr);
            this._nodeStack.push(decl);
        }

        public void endFontFace() throws CSSException {

            // Pop both the style declaration and the font face rule nodes
            this._nodeStack.pop();
            this._root = this._nodeStack.pop();
        }

        public void startSelector(SelectorList selectors) throws CSSException {

            // Create the style rule and add it to the rule list
            CSSStyleRuleImpl sr = new CSSStyleRuleImpl(
                CSSOMParser.this.getParentStyleSheet(),
                this.getParentRule(), selectors);
            this.addLocator(sr);
            if (!this._nodeStack.empty()) {
                Object o = this._nodeStack.peek();
                ((CSSRuleListImpl)/*this._nodeStack.peek()*/o).add(sr);
            }
            
            // Create the style declaration
            CSSStyleDeclarationImpl decl = new CSSStyleDeclarationImpl(sr);
            sr.setStyle(decl);
            this._nodeStack.push(sr);
            this._nodeStack.push(decl);
        }

        public void endSelector(SelectorList selectors) throws CSSException {

            // Pop both the style declaration and the style rule nodes
            this._nodeStack.pop();
            this._root = this._nodeStack.pop();
        }

        public void property(String name, LexicalUnit value, boolean important)
                throws CSSException {
            CSSStyleDeclarationImpl decl =
                (CSSStyleDeclarationImpl) this._nodeStack.peek();
            try
            {
                Property property = 
                    new Property(name, new CSSValueImpl(value), important);
                this.addLocator(property);
                decl.addProperty(property);
            }
            catch (DOMException e)
            {
                // call ErrorHandler?
            }
        }
        
        private CSSRule getParentRule()
        {
            if (!this._nodeStack.empty() && this._nodeStack.size() > 1)
            {
                Object node = this._nodeStack.get(this._nodeStack.size() - 2);
                if (node instanceof CSSRule)
                {
                    return (CSSRule) node;
                }
            }
            return null;
        }
        
        private void addLocator(CSSOMObject cssomObject)
        {
            cssomObject.setUserData(UserDataConstants.KEY_LOCATOR,
                ((AbstractSACParser) CSSOMParser.this._parser).getLocator());
        }
    }

    public static void setProperty(String key, String val) {
        Properties props = System.getProperties();
        props.put(key, val);
        System.setProperties(props);
    }
}
