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

import org.trix.cuery.value.CSSLength;
import org.trix.cuery.value.CSSPercentage;
import org.trix.cuery.value.CSSValue;
import org.trix.cuery.value.CentimeterLength;
import org.trix.cuery.value.DegreeAngle;
import org.trix.cuery.value.EMLength;
import org.trix.cuery.value.EXLength;
import org.trix.cuery.value.GradianAngle;
import org.trix.cuery.value.HertzFrequency;
import org.trix.cuery.value.Identifier;
import org.trix.cuery.value.InchLength;
import org.trix.cuery.value.KiloHertzFrequency;
import org.trix.cuery.value.MillimeterLength;
import org.trix.cuery.value.MillisecondTime;
import org.trix.cuery.value.PicaLength;
import org.trix.cuery.value.PixelLength;
import org.trix.cuery.value.PointLength;
import org.trix.cuery.value.RadianAngle;
import org.trix.cuery.value.SecondTime;

import org.w3c.css.sac.LexicalUnit;

/**
 * DOCUMENT.
 * 
 * @author <a href="mailto:Teletha.T@gmail.com">Teletha Testarossa</a>
 * @version $ Id: PropertyDefinition.java,v 1.04 2005/09/23 11:12:38 Teletha Exp $
 */
public abstract class PropertyDefinition {

    /** The property name. */
    protected String name;

    /** The property inheritance. */
    protected boolean inherited;

    /** The property initial value. */
    protected CSSValue initialValue;

    /** The acceptable value list. */
    protected List acceptables = new ArrayList();

    /**
     * Return the defined property name.
     * 
     * @return A property name.
     */
    public final String getName() {
        return name;
    }

    /**
     * Return the defined initial value for this property.
     * 
     * @return A initial value.
     */
    public final CSSValue getInitialValue() {
        return initialValue;
    }

    /**
     * Check whether this property can inherit a value or not.
     * 
     * @return A result.
     */
    public final boolean isInheritable() {
        return inherited;
    }

    /**
     * Validate css value in this property definition.
     * 
     * @param value A css value.
     * @return A result.
     */
    public Map parse(CSSValue value) {
        // check null
        if (value == null) {
            return null;
        }

        for (int i = 0; i < acceptables.size(); i++) {
            Object acceptable = acceptables.get(i);

            if (acceptable instanceof String) {
                String identifier = (String) acceptable;

                if (identifier.equals(value.getStringValue())) {
                    return Collections.EMPTY_MAP;
                }
            } else if (acceptable instanceof Class) {
                Class clazz = (Class) acceptable;

                if (clazz.isAssignableFrom(value.getType())) {
                    return Collections.EMPTY_MAP;
                }

                // zero length without unit
                if (clazz.getName().equals(CSSLength.class.getName())
                        && value.getLexicalUnitType() == LexicalUnit.SAC_INTEGER && value.getIntegerValue() == 0) {
                    return Collections.EMPTY_MAP;
                }
            }
        }
        return null;
    }

    /**
     * <p>
     * Specified values may be absolute (i.e., they are not specified relative to another value, as
     * in 'red' or '2mm') or relative (i.e., they are specified relative to another value, as in
     * 'auto', '2em', and '12%'). For absolute values, no computation is needed to find the computed
     * value.
     * </p>
     * <p>
     * Relative values, on the other hand, must be transformed into computed values: percentages
     * must be multiplied by a reference value (each property defines which value that is), values
     * with relative units (em, ex, px) must be made absolute by multiplying with the appropriate
     * font or pixel size, 'auto' values must be computed by the formulas given with each property,
     * certain keywords ('smaller', 'bolder', 'inherit') must be replaced according to their
     * definitions.
     * </p>
     * <p>
     * In most cases, elements inherit computed values. However, there are some properties whose
     * specified value may be inherited (e.g., the number value for the <span
     * class="propinst-line-height">'line-height'</span> property). In the cases where child
     * elements do not inherit the computed value, this is described in the property definition.
     * </p>
     * 
     * @param value A current property value.
     * @param property A current property.
     * @param parent A parent property.
     * @return A computed value.
     */
    public CSSValue getComputedValue(CSSValue value, Property property, Property parent) {
        switch (value.getLexicalUnitType()) {
        case LexicalUnit.SAC_CENTIMETER:
            return computeCentimeterLength((CentimeterLength) value, property, parent);

        case LexicalUnit.SAC_DEGREE:
            return computeDegreeAngle((DegreeAngle) value, property, parent);

        case LexicalUnit.SAC_EM:
            return computeEMLength((EMLength) value, property, parent);

        case LexicalUnit.SAC_EX:
            return computeEXLength((EXLength) value, property, parent);

        case LexicalUnit.SAC_GRADIAN:
            return computeGradianAngle((GradianAngle) value, property, parent);

        case LexicalUnit.SAC_HERTZ:
            return computeHertzFrequency((HertzFrequency) value, property, parent);

        case LexicalUnit.SAC_IDENT:
            return computeIdentifier((Identifier) value, property, parent);

        case LexicalUnit.SAC_INCH:
            return computeInchLength((InchLength) value, property, parent);

        case LexicalUnit.SAC_INHERIT:
            return parent.getValue(name);

        case LexicalUnit.SAC_KILOHERTZ:
            return computeKiloHertzFrequency((KiloHertzFrequency) value, property, parent);

        case LexicalUnit.SAC_MILLIMETER:
            return computeMillimeterLength((MillimeterLength) value, property, parent);

        case LexicalUnit.SAC_MILLISECOND:
            return computeMillisecondTime((MillisecondTime) value, property, parent);

        case LexicalUnit.SAC_PERCENTAGE:
            return computePercentage((CSSPercentage) value, property, parent);

        case LexicalUnit.SAC_PICA:
            return computePicaLength((PicaLength) value, property, parent);

        case LexicalUnit.SAC_PIXEL:
            return computePixelLength((PixelLength) value, property, parent);

        case LexicalUnit.SAC_POINT:
            return computePointLength((PointLength) value, property, parent);

        case LexicalUnit.SAC_RADIAN:
            return computeRadianAngle((RadianAngle) value, property, parent);

        case LexicalUnit.SAC_SECOND:
            return computeSecondTime((SecondTime) value, property, parent);

        default:
            return value;
        }
    }

    /**
     * Set up this definition.
     * 
     * @param name A property name.
     * @param inherited Aproperty inheritance.
     * @param initialValue A property initial value.
     */
    protected void setup(String name, boolean inherited, CSSValue initialValue) {
        this.name = name;
        this.inherited = inherited;
        this.initialValue = initialValue;
    }

    /**
     * Add acceptable type.
     * 
     * @param acceptable A type.
     */
    protected void addAcceptable(Object acceptable) {
        if (acceptable == null || acceptables.contains(acceptable)) {
            return;
        }
        acceptables.add(acceptable);
    }

    /**
     * Compute centimeter length.
     * 
     * @param length A target value.
     * @param property A current property.
     * @param parent A parent property.
     * @return A computed value.
     */
    protected CSSValue computeCentimeterLength(CentimeterLength length, Property property, Property parent) {
        return length;
    }

    /**
     * Compute degree angle.
     * 
     * @param angle A target value.
     * @param property A current property.
     * @param parent A parent property.
     * @return A computed value.
     */
    protected CSSValue computeDegreeAngle(DegreeAngle angle, Property property, Property parent) {
        return angle;
    }

    /**
     * Compute em length.
     * 
     * @param length A target value.
     * @param property A current property.
     * @param parent A parent property.
     * @return A computed value.
     */
    protected CSSValue computeEMLength(EMLength length, Property property, Property parent) {
        return length;
    }

    /**
     * Compute ex length.
     * 
     * @param length A target value.
     * @param property A current property.
     * @param parent A parent property.
     * @return A computed value.
     */
    protected CSSValue computeEXLength(EXLength length, Property property, Property parent) {
        return length;
    }

    /**
     * Compute grad angle.
     * 
     * @param angle A target value.
     * @param property A current property.
     * @param parent A parent property.
     * @return A computed value.
     */
    protected CSSValue computeGradianAngle(GradianAngle angle, Property property, Property parent) {
        return angle;
    }

    /**
     * Compute hertz frequency.
     * 
     * @param frequency A target value.
     * @param property A current property.
     * @param parent A parent property.
     * @return A computed value.
     */
    protected CSSValue computeHertzFrequency(HertzFrequency frequency, Property property, Property parent) {
        return frequency;
    }

    /**
     * Compute identifier.
     * 
     * @param identifier A target value.
     * @param property A current property.
     * @param parent A parent property.
     * @return A computed value.
     */
    protected CSSValue computeIdentifier(Identifier identifier, Property property, Property parent) {
        return identifier;
    }

    /**
     * Compute inch length.
     * 
     * @param length A target value.
     * @param property A current property.
     * @param parent A parent property.
     * @return A computed value.
     */
    protected CSSValue computeInchLength(InchLength length, Property property, Property parent) {
        return length;
    }

    /**
     * Compute killo hertz frequency.
     * 
     * @param frequency A target value.
     * @param property A current property.
     * @param parent A parent property.
     * @return A computed value.
     */
    protected CSSValue computeKiloHertzFrequency(KiloHertzFrequency frequency, Property property, Property parent) {
        return frequency;
    }

    /**
     * Compute millimeter length.
     * 
     * @param length A target value.
     * @param property A current property.
     * @param parent A parent property.
     * @return A computed value.
     */
    protected CSSValue computeMillimeterLength(MillimeterLength length, Property property, Property parent) {
        return length;
    }

    /**
     * Compute millisecond time.
     * 
     * @param time A target value.
     * @param property A current property.
     * @param parent A parent property.
     * @return A computed value.
     */
    protected CSSValue computeMillisecondTime(MillisecondTime time, Property property, Property parent) {
        return time;
    }

    /**
     * Compute percentage.
     * 
     * @param percentage A target value.
     * @param property A current property.
     * @param parent A parent property.
     * @return A computed value.
     */
    protected CSSValue computePercentage(CSSPercentage percentage, Property property, Property parent) {
        return percentage;
    }

    /**
     * Compute pica length.
     * 
     * @param length A target value.
     * @param property A current property.
     * @param parent A parent property.
     * @return A computed value.
     */
    protected CSSValue computePicaLength(PicaLength length, Property property, Property parent) {
        return length;
    }

    /**
     * Compute pixel length.
     * 
     * @param length A target value.
     * @param property A current property.
     * @param parent A parent property.
     * @return A computed value.
     */
    protected CSSValue computePixelLength(PixelLength length, Property property, Property parent) {
        return length;
    }

    /**
     * Compute point length.
     * 
     * @param length A target value.
     * @param property A current property.
     * @param parent A parent property.
     * @return A computed value.
     */
    protected CSSValue computePointLength(PointLength length, Property property, Property parent) {
        return length;
    }

    /**
     * Compute radian angle.
     * 
     * @param angle A target value.
     * @param property A current property.
     * @param parent A parent property.
     * @return A computed value.
     */
    protected CSSValue computeRadianAngle(RadianAngle angle, Property property, Property parent) {
        return angle;
    }

    /**
     * Compute second time.
     * 
     * @param time A target value.
     * @param property A current property.
     * @param parent A parent property.
     * @return A computed value.
     */
    protected CSSValue computeSecondTime(SecondTime time, Property property, Property parent) {
        return time;
    }

    /**
     * @see java.lang.Object#toString()
     */
    public String toString() {
        return name + " [inheritable='" + Boolean.toString(inherited) + "', initial='" + initialValue + "']";
    }

}
