/*
 * 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.afp.fonts;

import java.awt.Rectangle;

/**
 * The IBM Font Object Content Architecture (FOCA) supports presentation
 * of character shapes by defining their characteristics, which include
 * Font-Description information for identifying the characters, Font-Metric
 * information for positioning the characters, and Character-Shape
 * information for presenting the character images.<br>
 *
 * Presenting a graphic character on a presentation surface requires
 * that you communicate this information clearly to rotate and position
 * characters correctly on the physical or logical page.<br>
 *
 * This class provides font metric information for a particular font
 * as by the orientation.<br>
 *
 * This information is obtained directly from the AFP font files which must
 * be installed in the classpath under in the location specified by the path
 * attribute in the afp-font.xml file.
 */
public class CharacterSetOrientation {

    /**
     * The ascender height for the character set
     */
    private int ascender;

    /**
     * The descender depth for the character set
     */
    private int descender;

    /**
     * The height of capital letters
     */
    private int capHeight;

    /**
     * The character widths in the character set (indexed using Unicode codepoints)
     */
    private IntegerKeyStore<CharacterMetrics> characterMetrics;

    /**
     * The height of lowercase letters
     */
    private int xHeight;

    /** The character set orientation */
    private final int orientation;
    /** space increment */
    private final int spaceIncrement;
    /** em space increment */
    private final int emSpaceIncrement;
    /** Nominal Character Increment */
    private final int nomCharIncrement;

    private int underscoreWidth;

    private int underscorePosition;

    /**
     * Constructor for the CharacterSetOrientation, the orientation is
     * expressed as the degrees rotation (i.e 0, 90, 180, 270)
     *
     * @param orientation   the character set orientation
     * @param spaceIncrement    the space increment
     * @param emSpaceIncrement  the em space increment
     * @param nomCharIncrement  the nominal character increment
     */
    public CharacterSetOrientation(int orientation, int spaceIncrement, int emSpaceIncrement,
            int nomCharIncrement) {
        this.orientation = orientation;
        this.spaceIncrement = spaceIncrement;
        this.emSpaceIncrement = emSpaceIncrement;
        this.nomCharIncrement = nomCharIncrement;
        this.characterMetrics = new IntegerKeyStore<CharacterMetrics>();
    }

    /**
     * Ascender height is the distance from the character baseline to the
     * top of the character box. A negative ascender height signifies that
     * all of the graphic character is below the character baseline. For
     * a character rotation other than 0, ascender height loses its
     * meaning when the character is lying on its side or is upside down
     * with respect to normal viewing orientation. For the general case,
     * Ascender Height is the character's most positive y-axis value.
     * For bounded character boxes, for a given character having an
     * ascender, ascender height and baseline offset are equal.
     * @return the ascender value in millipoints
     */
    public int getAscender() {
        return ascender;
    }

    /**
     * Cap height is the average height of the uppercase characters in
     * a font. This value is specified by the designer of a font and is
     * usually the height of the uppercase M.
     * @return the cap height value in millipoints
     */
    public int getCapHeight() {
        return capHeight;
    }

    /**
     * Descender depth is the distance from the character baseline to
     * the bottom of a character box. A negative descender depth signifies
     * that all of the graphic character is above the character baseline.
     * @return the descender value in millipoints
     */
    public int getDescender() {
        return descender;
    }

    /**
     *
     * @return  the underscore width
     */
    public int getUnderscoreWidth() {
        return underscoreWidth;
    }

    /**
     *
     * @return  the underscore position
     */
    public int getUnderscorePosition() {
        return underscorePosition;
    }

    /**
     * The orientation for these metrics in the character set
     * @return the orientation
     */
    public int getOrientation() {
        return orientation;
    }

    /**
     * XHeight refers to the height of the lower case letters above
     * the baseline.
     * @return heightX the typical height of characters
     */
    public int getXHeight() {
        return xHeight;
    }

    /**
     * Get the width (in 1/1000ths of a point size) of the character
     * identified by the parameter passed.
     * @param character the Unicode character to evaluate
     * @param size  the font size
     * @return the widths of the character
     */
    public int getWidth(char character, int size) {
        CharacterMetrics cm = getCharacterMetrics(character);
        return cm == null ? -1 : size * cm.width;
    }

    private CharacterMetrics getCharacterMetrics(char character) {
        return characterMetrics.get((int) character);
    }

    /**
     * Get the character box (rectangle with dimensions in 1/1000ths of a point size) of the character
     * identified by the parameter passed.
     * @param character the Unicode character to evaluate
     * @param size  the font size
     * @return the character box
     */
    public Rectangle getCharacterBox(char character, int size) {
        CharacterMetrics cm = getCharacterMetrics(character);
        return scale(cm == null ? getFallbackCharacterBox() : cm.characterBox, size);
    }

    private static Rectangle scale(Rectangle rectangle, int size) {
        if (rectangle == null) {
            return null;
        } else {
        return new Rectangle((int) (size * rectangle.getX()), (int) (size * rectangle.getY()),
                (int) (size * rectangle.getWidth()), (int) (size * rectangle.getHeight()));
        }
    }

    private Rectangle getFallbackCharacterBox() {
        // TODO replace with something sensible
        return new Rectangle(0, 0, 0, 0);
    }

    /**
     * Ascender height is the distance from the character baseline to the
     * top of the character box. A negative ascender height signifies that
     * all of the graphic character is below the character baseline. For
     * a character rotation other than 0, ascender height loses its
     * meaning when the character is lying on its side or is upside down
     * with respect to normal viewing orientation. For the general case,
     * Ascender Height is the character's most positive y-axis value.
     * For bounded character boxes, for a given character having an
     * ascender, ascender height and baseline offset are equal.
     * @param ascender the ascender to set
     */
    public void setAscender(int ascender) {
        this.ascender = ascender;
    }

    /**
     * Cap height is the average height of the uppercase characters in
     * a font. This value is specified by the designer of a font and is
     * usually the height of the uppercase M.
     * @param capHeight the cap height to set
     */
    public void setCapHeight(int capHeight) {
        this.capHeight = capHeight;
    }

    /**
     * Descender depth is the distance from the character baseline to
     * the bottom of a character box. A negative descender depth signifies
     * that all of the graphic character is above the character baseline.
     * @param descender the descender value in millipoints
     */
    public void setDescender(int descender) {
        this.descender = descender;
    }

    /**
     * TODO
     * @param underscoreWidth the underscore width value in millipoints
     */
    public void setUnderscoreWidth(int underscoreWidth) {
        this.underscoreWidth = underscoreWidth;
    }

    /**
     * TODO
     * @param underscorePosition the underscore position value in millipoints
     */
    public void setUnderscorePosition(int underscorePosition) {
        this.underscorePosition = underscorePosition;
    }

    /**
     * Set the width (in 1/1000ths of a point size) of the character
     * identified by the parameter passed.
     * @param character the Unicode character for which the width is being set
     * @param width the widths of the character
     * @param   characterBox    the character box
     */
    public void setCharacterMetrics(char character, int width, Rectangle characterBox) {
        characterMetrics.put((int) character, new CharacterMetrics(width, characterBox));
    }

    /**
     * XHeight refers to the height of the lower case letters above
     * the baseline.
     * @param xHeight the typical height of characters
     */
    public void setXHeight(int xHeight) {
        this.xHeight = xHeight;
    }

    /**
     * Returns the space increment.
     * @return the space increment
     */
    public int getSpaceIncrement() {
        return this.spaceIncrement;
    }

    /**
     * Returns the em space increment.
     * @return the em space increment
     */
    public int getEmSpaceIncrement() {
        return this.emSpaceIncrement;
    }

    /**
     * Returns the nominal character increment.
     * @return the nominal character increment
     */
    public int getNominalCharIncrement() {
        return this.nomCharIncrement;
    }

    private static class CharacterMetrics {

        public final int width;

        public final Rectangle characterBox;

        public CharacterMetrics(int width, Rectangle characterBox) {
            this.width = width;
            this.characterBox = characterBox;
        }
    }
}
