/*
 * 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.util.ArrayList;
import java.util.List;

import org.trix.cuery.CSS;
import org.trix.cuery.StyleContext;
import org.trix.cuery.util.DOMUtil;
import org.trix.cuery.value.CSSValue;

import org.w3c.dom.Element;

/**
 * DOCUMENT.
 * 
 * @author <a href="mailto:Teletha.T@gmail.com">Teletha Testarossa</a>
 * @version $ Id: CascadableProperty.java,v 1.06 2005/12/06 15:16:41 Teletha Exp $
 */
public class CascadableProperty extends AbstractProperty {

    /** The list of styles. */
    private List properties = new ArrayList();

    /** The position of a seperator between author and user. */
    private int author = 0;

    /** The position of a seperator between user and agent. */
    private int user = 0;

    /** The current element. */
    private Element element;

    /**
     * Create CascadableProperty instance.
     */
    public CascadableProperty() {
    }

    /**
     * Create CascadableProperty instance.
     * 
     * @param element A target element.
     */
    public CascadableProperty(Element element) {
        this.element = element;
    }

    /**
     * @see org.trix.cuery.property.Property#getValue(java.lang.String)
     */
    public CSSValue getValue(String name) {
        PropertyDefinition definition = PropertyRegistry.getDefinition(name);
        CSSValue value = getSpecifiedValue(definition);

        Property parent = StyleContext.getProperty(DOMUtil.getParentElement(element));
        return definition.getComputedValue(value, this, parent);
    }

    /**
     * Assign a specified value to a property. See also <a
     * href="http://www.w3.org/TR/REC-CSS2/cascade.html#specified-value">the mechanisms</a>.
     * 
     * @param definition A property definition.
     * @return A specified value.
     */
    private CSSValue getSpecifiedValue(PropertyDefinition definition) {
        String name = definition.getName();
        CSSValue result;

        // If the cascade results in a value, use it.
        // search in user stylesheet with important priority
        result = searchValue(name, author, user, true);

        if (result != null) {
            return result;
        }

        // search in author stylesheet with important priority
        result = searchValue(name, 0, author, true);

        if (result != null) {
            return result;
        }

        // search in author stylesheet
        result = searchValue(name, 0, author, false);

        if (result != null) {
            return result;
        }

        // search in user stylesheet
        result = searchValue(name, author, user, false);

        if (result != null) {
            return result;
        }

        // search in agent stylesheet
        result = searchValue(name, user, properties.size(), false);

        if (result != null) {
            return result;
        }

        // Otherwise, if the property is inherited, use the value of the parent element, generally
        // the computed value.
        Element parent = DOMUtil.getParentElement(element);

        if (parent != null && definition.isInheritable()) {
            return StyleContext.getProperty(parent).getValue(name);
        }

        // Otherwise use the property's initial value. The initial value of each property is
        // indicated in the property's definition.
        return definition.getInitialValue();
    }

    /**
     * Search value by various conditions.
     * 
     * @param name A property name.
     * @param start A start point.
     * @param end A end point.
     * @param important A important flag.
     * @return A found value.
     */
    private CSSValue searchValue(String name, int start, int end, boolean important) {
        for (int i = start; i < end; i++) {
            PropertyDescription description = (PropertyDescription) properties.get(i);
            Property property = description.property;

            // check important
            if (important && !property.isImportant(name)) {
                continue;
            }

            // try to retrive value
            CSSValue value = property.getValue(name);

            if (value == null) {
                continue;
            }
            return value;
        }
        return null;
    }

    /**
     * @see org.trix.cuery.property.Property#isImportant(java.lang.String)
     */
    public boolean isImportant(String name) {
        return false;
    }

    /**
     * Add property.
     * 
     * @param description A target property description.
     */
    public void addProperty(PropertyDescription description) {
        switch (description.origin) {
        case CSS.ORIGIN_AUTHOR:
            addProperty(description, 0, author);
            author++;
            user++;
            break;

        case CSS.ORIGIN_USER:
            addProperty(description, author, user);
            user++;
            break;

        case CSS.ORIGIN_AGENT:
            addProperty(description, user, properties.size());
            break;

        default:
            break;
        }
    }

    /**
     * Add property to a suitable position in list of properties.
     * 
     * @param description A property weight set.
     * @param start A start position for this property origin.
     * @param end A end position for this property origin.
     */
    private void addProperty(PropertyDescription description, int start, int end) {
        int specificity = description.filter.getSpecificity();

        for (int i = start; i < end; i++) {
            PropertyDescription target = (PropertyDescription) properties.get(i);
            int targetSpecificity = target.filter.getSpecificity();

            // check specificity
            if (specificity > targetSpecificity) {
                properties.add(i, description);
                return;
            }

            // check position
            if (specificity == targetSpecificity && description.position > target.position) {
                properties.add(i, description);
                return;
            }
        }
        // lowest weight
        properties.add(end, description);
    }

    /**
     * Clear cached style information.
     */
    public void clear() {
        properties.clear();
        author = 0;
        user = 0;
    }
}
