/*
 * $Id: CSSStyleSheetImpl.java,v 1.2 2008/03/26 02:17:24 sdanig Exp $
 *
 * CSS Parser Project
 *
 * Copyright (C) 1999-2005 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.dom;

import java.io.IOException;
import java.io.Serializable;
import java.io.StringReader;

import org.w3c.dom.DOMException;
import org.w3c.dom.Node;

import org.w3c.dom.stylesheets.MediaList;
import org.w3c.dom.stylesheets.StyleSheet;

import org.w3c.dom.css.CSSImportRule;
import org.w3c.dom.css.CSSRule;
import org.w3c.dom.css.CSSRuleList;
import org.w3c.dom.css.CSSStyleSheet;

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

import com.steadystate.css.parser.CSSOMParser;
import com.steadystate.css.parser.SACParserCSS2;

/**
 * Implementation of {@link CSSStyleSheet}.
 * 
 * @author <a href="mailto:davidsch@users.sourceforge.net">David Schweinsberg</a>
 * @version $Id: CSSStyleSheetImpl.java,v 1.2 2008/03/26 02:17:24 sdanig Exp $
 */
public class CSSStyleSheetImpl implements CSSStyleSheet, Serializable {

    private static final long serialVersionUID = -2300541300646796363L;

    private boolean disabled = false;
    private Node ownerNode = null;
    private StyleSheet parentStyleSheet = null;
    private String href = null;
    private String title = null;
    private MediaList media = null;
    private CSSRule ownerRule = null;
    private boolean readOnly = false;
    private CSSRuleList cssRules = null;
    private String baseUri;

    public void setMedia(MediaList media)
    {
        this.media = media;
    }

    private String getBaseUri()
    {
        return this.baseUri;
    }

    public void setBaseUri(String baseUri)
    {
        this.baseUri = baseUri;
    }

    public CSSStyleSheetImpl() {
    }


    public String getType() {
        return "text/css";
    }

    public boolean getDisabled() {
        return this.disabled;
    }

    /**
     * We will need to respond more fully if a stylesheet is disabled, probably
     * by generating an event for the main application.
     */
    public void setDisabled(boolean disabled) {
        this.disabled = disabled;
    }

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

    public StyleSheet getParentStyleSheet() {
        return this.parentStyleSheet;
    }

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

    public String getTitle() {
        return this.title;
    }

    public MediaList getMedia() {
        return this.media;
    }

    public CSSRule getOwnerRule() {
        return this.ownerRule;
    }

    public CSSRuleList getCssRules() {
        if (this.cssRules == null)
        {
            this.cssRules = new CSSRuleListImpl();
        }
        return this.cssRules;
    }

    public int insertRule(String rule, int index) throws DOMException {
        if (this.readOnly) {
            throw new DOMExceptionImpl(
                DOMException.NO_MODIFICATION_ALLOWED_ERR,
                DOMExceptionImpl.READ_ONLY_STYLE_SHEET);
        }

        try {
            InputSource is = new InputSource(new StringReader(rule));
            CSSOMParser parser = new CSSOMParser();
            parser.setParentStyleSheet(this);
            CSSRule r = parser.parseRule(is);

            if (getCssRules().getLength() > 0) {

                // We need to check that this type of rule can legally go into
                // the requested position.
                int msg = -1;
                if (r.getType() == CSSRule.CHARSET_RULE) {

                    // Index must be 0, and there can be only one charset rule
                    if (index != 0) {
                        msg = DOMExceptionImpl.CHARSET_NOT_FIRST;
                    } else if (getCssRules().item(0).getType()
                            == CSSRule.CHARSET_RULE) {
                        msg = DOMExceptionImpl.CHARSET_NOT_UNIQUE;
                    }
                } else if (r.getType() == CSSRule.IMPORT_RULE) {

                    // Import rules must preceed all other rules (except
                    // charset rules)
                    if (index <= getCssRules().getLength()) {
                        for (int i = 0; i < index; i++) {
                            int rt = getCssRules().item(i).getType();
                            if ((rt != CSSRule.CHARSET_RULE)
                                    || (rt != CSSRule.IMPORT_RULE)) {
                                msg = DOMExceptionImpl.IMPORT_NOT_FIRST;
                                break;
                            }
                        }
                    }
                }

                if (msg > -1) {
                    throw new DOMExceptionImpl(
                        DOMException.HIERARCHY_REQUEST_ERR,
                        msg);
                }
            }

            // Insert the rule into the list of rules
            ((CSSRuleListImpl)getCssRules()).insert(r, index);

        } catch (ArrayIndexOutOfBoundsException e) {
            throw new DOMExceptionImpl(
                DOMException.INDEX_SIZE_ERR,
                DOMExceptionImpl.ARRAY_OUT_OF_BOUNDS,
                e.getMessage());
        } catch (CSSException e) {
            throw new DOMExceptionImpl(
                DOMException.SYNTAX_ERR,
                DOMExceptionImpl.SYNTAX_ERROR,
                e.getMessage());
        } catch (IOException e) {
            throw new DOMExceptionImpl(
                DOMException.SYNTAX_ERR,
                DOMExceptionImpl.SYNTAX_ERROR,
                e.getMessage());
        }
        return index;
    }

    public void deleteRule(int index) throws DOMException {
        if (this.readOnly) {
            throw new DOMExceptionImpl(
                DOMException.NO_MODIFICATION_ALLOWED_ERR,
                DOMExceptionImpl.READ_ONLY_STYLE_SHEET);
        }

        try {
            ((CSSRuleListImpl)getCssRules()).delete(index);
        } catch (ArrayIndexOutOfBoundsException e) {
            throw new DOMExceptionImpl(
                DOMException.INDEX_SIZE_ERR,
                DOMExceptionImpl.ARRAY_OUT_OF_BOUNDS,
                e.getMessage());
        }
    }

    public boolean isReadOnly() {
        return this.readOnly;
    }

    public void setReadOnly(boolean b) {
        this.readOnly = b;
    }

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

    public void setParentStyleSheet(StyleSheet parentStyleSheet) {
        this.parentStyleSheet = parentStyleSheet;
    }

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

    public void setTitle(String title) {
        this.title = title;
    }

    public void setMediaText(String mediaText) {
        InputSource source = new InputSource(new StringReader(mediaText));
        try
        {
            // TODO get SAC Parser version from System property?
            SACMediaList sml = new SACParserCSS2().parseMedia(source);
            this.media = new MediaListImpl(sml);
        }
        catch (IOException e)
        {
            // TODO handle exception
        }
    }

    public void setOwnerRule(CSSRule ownerRule) {
        this.ownerRule = ownerRule;
    }
    
    public void setCssRules(CSSRuleList rules) {
        this.cssRules = rules;
    }
    
    public String toString() {
        return this.getCssRules().toString();
    }
    
    /**
     * Imports referenced CSSStyleSheets.
     *
     * @param recursive <code>true</code> if the import should be done
     *   recursively, <code>false</code> otherwise
     */
    public void importImports(boolean recursive)
        throws DOMException
    {
        for (int i = 0; i < this.getCssRules().getLength(); i++)
        {
            CSSRule cssRule = this.getCssRules().item(i);
            if (cssRule.getType() == CSSRule.IMPORT_RULE)
            {
                CSSImportRule cssImportRule = (CSSImportRule) cssRule;
                try
                {
                    java.net.URI importURI = new java.net.URI(this.getBaseUri())
                        .resolve(cssImportRule.getHref());
                    CSSStyleSheetImpl importedCSS = (CSSStyleSheetImpl)
                        new CSSOMParser().parseStyleSheet(new InputSource(
                            importURI.toString()), null, importURI.toString());
                    if (recursive)
                    {
                        importedCSS.importImports(recursive);
                    }
                    MediaList mediaList = cssImportRule.getMedia();
                    if (mediaList.getLength() == 0)
                    {
                        mediaList.appendMedium("all");
                    }
                    CSSMediaRuleImpl cssMediaRule =
                        new CSSMediaRuleImpl(this, null, mediaList);
                    cssMediaRule.setRuleList(
                        (CSSRuleListImpl) importedCSS.getCssRules());
                    this.deleteRule(i);
                    ((CSSRuleListImpl) this.getCssRules()).insert(cssMediaRule, i);
                }
                catch (java.net.URISyntaxException e)
                {
                    // TODO handle exception
                    throw new DOMException(DOMException.SYNTAX_ERR,
                        e.getLocalizedMessage());
                }
                catch (IOException e)
                {
                    // TODO handle exception
                }
            }
        }
    }
}
