/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */

/* $Id$ */

package org.apache.fop.fo.properties;

import org.apache.fop.datatypes.CompoundDatatype;
import org.apache.fop.fo.Constants;
import org.apache.fop.fo.FObj;
import org.apache.fop.fo.PropertyList;
import org.apache.fop.fo.expr.PropertyException;

/**
 * This class extends Property.Maker with support for sub-properties.
 */
public class CompoundPropertyMaker extends PropertyMaker {
    /**
     *  The list of subproperty makers supported by this compound maker.
     */
    private PropertyMaker[] subproperties
        = new PropertyMaker[Constants.COMPOUND_COUNT];

    /**
     *  The first subproperty maker which has a setByShorthand of true.
     */
    private PropertyMaker shorthandMaker;

    /**
     * Construct an instance of a CompoundPropertyMaker for the given property.
     * @param propId The Constant ID of the property to be made.
     */
    public CompoundPropertyMaker(int propId) {
        super(propId);
    }

    /**
     * {@inheritDoc}
     */
    public void useGeneric(PropertyMaker generic) {
        super.useGeneric(generic);
        if (generic instanceof CompoundPropertyMaker) {
            CompoundPropertyMaker compoundGeneric = (CompoundPropertyMaker) generic;
            for (int i = 0; i < Constants.COMPOUND_COUNT; i++) {
                PropertyMaker submaker = compoundGeneric.subproperties[i];
                if (submaker != null) {
                    addSubpropMaker((PropertyMaker) submaker.clone());
                }
            }
        }
    }

    /**
     * Add a subproperty to this maker.
     * @param subproperty the sub property
     */
    public void addSubpropMaker(PropertyMaker subproperty) {
        // Place the base propId in the propId of the subproperty.
        subproperty.propId &= Constants.COMPOUND_MASK;
        subproperty.propId |= propId;

        subproperties[getSubpropIndex(subproperty.getPropId())] = subproperty;

        // Store the first subproperty with a setByShorthand. That subproperty
        // will be used for converting a value set on the base property.
        if (shorthandMaker == null && subproperty.setByShorthand) {
            shorthandMaker = subproperty;
        }
    }


    /**
     * Return a Maker object which is used to set the values on components
     * of compound property types, such as "space".
     * Overridden by property maker subclasses which handle
     * compound properties.
     * @param subpropertyId the id of the component for which a Maker is to
     * returned, for example CP_OPTIMUM, if the FO attribute is
     * space.optimum='10pt'.
     * @return the Maker object specified
     */
    public PropertyMaker getSubpropMaker(int subpropertyId) {
        return subproperties[getSubpropIndex(subpropertyId)];
    }

    /**
     * Calculate the real value of a subproperty by unmasking and shifting
     * the value into the range [0 - (COMPOUND_COUNT-1)].
     * The value is used as index into the subproperties array.
     * @param subpropertyId the property id of the sub property.
     * @return the array index.
     */
    private int getSubpropIndex(int subpropertyId) {
        return ((subpropertyId & Constants.COMPOUND_MASK) >> Constants.COMPOUND_SHIFT) - 1;
    }

    /**
     * For compound properties which can take enumerate values.
     * Delegate the enumeration check to one of the subpropeties.
     * @param value the string containing the property value
     * @return the Property encapsulating the enumerated equivalent of the
     * input value
     */
    protected Property checkEnumValues(String value) {
        Property result = null;
        if (shorthandMaker != null) {
            result = shorthandMaker.checkEnumValues(value);
        }
        if (result == null) {
            result = super.checkEnumValues(value);
        }
        return result;
    }

    /**
     * Return the property on the current FlowObject. Depending on the passed flags,
     * this will try to compute it based on other properties, or if it is
     * inheritable, to return the inherited value. If all else fails, it returns
     * the default value.
     * @param subpropertyId  The subproperty id of the property being retrieved.
     *        Is 0 when retriving a base property.
     * @param propertyList The PropertyList object being built for this FO.
     * @param tryInherit true if inherited properties should be examined.
     * @param tryDefault true if the default value should be returned.
     * @return the property
     * @throws PropertyException if a property exception occurs
     */
    public Property get(int subpropertyId, PropertyList propertyList, boolean tryInherit,
            boolean tryDefault) throws PropertyException {
        Property p = super.get(subpropertyId, propertyList, tryInherit, tryDefault);
        if (subpropertyId != 0 && p != null) {
            p = getSubprop(p, subpropertyId);
        }
        return p;
    }

    /**
     * Return a Property object based on the passed Property object.
     * This method is called if the Property object built by the parser
     * isn't the right type for this compound property.
     * @param p The Property object return by the expression parser
     * @param propertyList The PropertyList object being built for this FO.
     * @param fo The parent FO for the FO whose property is being made.
     * @return A Property of the correct type or null if the parsed value
     * can't be converted to the correct type.
     * @throws PropertyException for invalid or inconsistent FO input
     */
    protected Property convertProperty(Property p,
                                    PropertyList propertyList,
                                    FObj fo) throws PropertyException {
        // Delegate to the subproperty maker to do conversions.
        p = shorthandMaker.convertProperty(p, propertyList, fo);

        if (p != null) {
            Property prop = makeCompound(propertyList, fo);
            CompoundDatatype pval = (CompoundDatatype) prop.getObject();
            for (int i = 0; i < Constants.COMPOUND_COUNT; i++) {
                PropertyMaker submaker = subproperties[i];
                if (submaker != null && submaker.setByShorthand) {
                    pval.setComponent(submaker.getPropId() & Constants.COMPOUND_MASK, p, false);
                }
            }
            return prop;
        }
        return null;
    }

    /**
     * Make a compound property with default values.
     * @param propertyList The PropertyList object being built for this FO.
     * @return the Property object corresponding to the parameters
     * @throws PropertyException for invalid or inconsisten FO input
     */
    public Property make(PropertyList propertyList) throws PropertyException {
        if (defaultValue != null) {
            return make(propertyList, defaultValue, propertyList.getParentFObj());
        } else {
            return makeCompound(propertyList, propertyList.getParentFObj());
        }
    }

    /**
     * Create a Property object from an attribute specification.
     * @param propertyList The PropertyList object being built for this FO.
     * @param value The attribute value.
     * @param fo The parent FO for the FO whose property is being made.
     * @return The initialized Property object.
     * @throws PropertyException for invalid or inconsistent FO input
     */
    public Property make(PropertyList propertyList, String value,
                         FObj fo) throws PropertyException {
        Property p = super.make(propertyList, value, fo);
        p = convertProperty(p, propertyList, fo);
        return p;
    }

    /**
     * Return a property value for a compound property. If the property
     * value is already partially initialized, this method will modify it.
     * @param baseProperty The Property object representing the compound property,
     * for example: SpaceProperty.
     * @param subpropertyId The Constants ID of the subproperty (component)
     *        whose value is specified.
     * @param propertyList The propertyList being built.
     * @param fo The parent FO for the FO whose property is being made.
     * @param value the value of the
     * @return baseProperty (or if null, a new compound property object) with
     * the new subproperty added
     * @throws PropertyException for invalid or inconsistent FO input
     */
    public Property make(Property baseProperty, int subpropertyId,
                         PropertyList propertyList, String value,
                         FObj fo) throws PropertyException {
        if (baseProperty == null) {
            baseProperty = makeCompound(propertyList, fo);
        }

        PropertyMaker spMaker = getSubpropMaker(subpropertyId);

        if (spMaker != null) {
            Property p = spMaker.make(propertyList, value, fo);
            if (p != null) {
                return setSubprop(baseProperty, subpropertyId & Constants.COMPOUND_MASK, p);
            }
        } else {
            //getLogger().error("compound property component "
            //                       + partName + " unknown.");
        }
        return baseProperty;
    }

    /**
     * Create a empty compound property and fill it with default values for
     * the subproperties.
     * @param propertyList The propertyList being built.
     * @param parentFO The parent FO for the FO whose property is being made.
     * @return a Property subclass object holding a "compound" property object
     *         initialized to the default values for each component.
     * @throws PropertyException ...
     */
    protected Property makeCompound(PropertyList propertyList, FObj parentFO)
            throws PropertyException {
        Property p = makeNewProperty();
        CompoundDatatype data = (CompoundDatatype) p.getObject();
        for (int i = 0; i < Constants.COMPOUND_COUNT; i++) {
            PropertyMaker subpropertyMaker = subproperties[i];
            if (subpropertyMaker != null) {
                Property subproperty = subpropertyMaker.make(propertyList);
                data.setComponent(subpropertyMaker.getPropId()
                                  & Constants.COMPOUND_MASK, subproperty, true);
            }
        }
        return p;
    }
}
