/*
 * 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.fo.Constants;
import org.apache.fop.fo.FObj;
import org.apache.fop.fo.PropertyList;
import org.apache.fop.fo.expr.PropertyException;

/**
 * This subclass of LengthProperty.Maker handles the special treatment of
 * relative font sizes described in 7.8.4.
 */
public class FontSizePropertyMaker
    extends LengthProperty.Maker implements Constants {

    /** The default normal font size in mpt */
    private static final int FONT_SIZE_NORMAL = 12000;
    /** The factor to be applied when stepping font sizes upwards */
    private static final double FONT_SIZE_GROWTH_FACTOR = 1.2;

    /**
     * Create a length property which can handle relative font sizes
     * @param propId the font size property id.
     */
    public FontSizePropertyMaker(int propId) {
        super(propId);
    }


    /**
     * {@inheritDoc}
     * Contrary to basic lengths, percentages for font-size can be resolved
     * here already: if the property evaluates to a {@link PercentLength},
     * it is immediately replaced by the resolved {@link FixedLength}.
     */
    public Property make(PropertyList propertyList, String value, FObj fo)
            throws PropertyException {
        Property p = super.make(propertyList, value, fo);
        if (p instanceof PercentLength) {
            Property pp = propertyList.getFromParent(this.propId);
            p = FixedLength.getInstance(
                    pp.getLength().getValue() * ((PercentLength)p).getPercentage() / 100);
        }
        return p;
    }


    /**
     * {@inheritDoc}
     * Implements the parts of 7.8.4 relevant to relative font sizes
     */
    public Property convertProperty(Property p,
                                    PropertyList propertyList,
                                    FObj fo) throws PropertyException {
        if (p.getEnum() == EN_LARGER || p.getEnum() == EN_SMALLER) {
            // get the corresponding property from parent
            Property pp = propertyList.getFromParent(this.propId);
            int baseFontSize = computeClosestAbsoluteFontSize(pp.getLength().getValue());
            if (p.getEnum() == EN_LARGER) {
                return FixedLength.getInstance(
                        Math.round(baseFontSize * FONT_SIZE_GROWTH_FACTOR));
            } else {
                return FixedLength.getInstance(
                        Math.round(baseFontSize / FONT_SIZE_GROWTH_FACTOR));
            }
        }
        return super.convertProperty(p, propertyList, fo);
    }

    /**
     * Calculates the nearest absolute font size to the given
     * font size.
     * @param baseFontSize the font size in mpt of the parent fo
     * @return the closest absolute font size
     */
    private int computeClosestAbsoluteFontSize(int baseFontSize) {
        double scale = FONT_SIZE_GROWTH_FACTOR;
        int lastStepFontSize = FONT_SIZE_NORMAL;
        if (baseFontSize < FONT_SIZE_NORMAL) {
            // Need to shrink the font sizes = scale downwards
            scale = 1 / FONT_SIZE_GROWTH_FACTOR;
        }
        // Calculate the inital next step font size
        int nextStepFontSize = (int)Math.round(lastStepFontSize * scale);
        while (scale < 1 && nextStepFontSize > baseFontSize
                || scale > 1 && nextStepFontSize < baseFontSize) {
            // baseFontSize is still bigger (if we grow) or smaller (if we shrink)
            // than the last caculated step
            lastStepFontSize = nextStepFontSize;
            nextStepFontSize = (int)Math.round(lastStepFontSize * scale);
            if (nextStepFontSize == lastStepFontSize) {
                break;
            }
        }
        // baseFontSize is between last and next step font size
        // Return the step value closer to the baseFontSize
        if (Math.abs(lastStepFontSize - baseFontSize)
                <= Math.abs(baseFontSize - nextStepFontSize)) {
            return lastStepFontSize;
        }
        return nextStepFontSize;
    }

}
