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

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.List;

import org.apache.fop.afp.AFPConstants;
import org.apache.fop.afp.fonts.AFPFont;
import org.apache.fop.afp.fonts.CharacterSet;
import org.apache.fop.afp.fonts.DoubleByteFont;
import org.apache.fop.afp.fonts.FontRuntimeException;
import org.apache.fop.afp.fonts.OutlineFont;
import org.apache.fop.afp.fonts.RasterFont;
import org.apache.fop.afp.util.BinaryUtils;

/**
 * The Map Coded Font structured field maps a unique coded font resource local
 * ID, which may be embedded one or more times within an object's data and
 * descriptor, to the identifier of a coded font resource object. Additionally,
 * the Map Coded Font structured field specifies a set of resource attributes
 * for the coded font.
 */
public class MapCodedFont extends AbstractStructuredObject {

    /** the collection of map coded fonts (maximum of 254) */
    private final List<FontDefinition> fontList
        = new java.util.ArrayList<FontDefinition>();

    /**
     * Main constructor
     */
    public MapCodedFont() {
    }

    /** {@inheritDoc} */
    public void writeToStream(OutputStream os) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        byte[] startData = new byte[9];
        copySF(startData, Type.MAP, Category.CODED_FONT);
        baos.write(startData);

        for (Object aFontList : fontList) {
            FontDefinition fd = (FontDefinition) aFontList;

            // Start of repeating groups (occurs 1 to 254)
            baos.write(0x00);

            if (fd.scale == 0) {
                // Raster Font
                baos.write(0x22); // Length of 34
            } else {
                // Outline Font
                baos.write(0x3A); // Length of 58
            }

            // Font Character Set Name Reference
            baos.write(0x0C); //TODO Relax requirement for 8 chars in the name
            baos.write(0x02);
            baos.write((byte) 0x86);
            baos.write(0x00);
            baos.write(fd.characterSet);

            // Font Code Page Name Reference
            baos.write(0x0C); //TODO Relax requirement for 8 chars in the name
            baos.write(0x02);
            baos.write((byte) 0x85);
            baos.write(0x00);
            baos.write(fd.codePage);

            //TODO idea: for CIDKeyed fonts, maybe hint at Unicode encoding with X'50' triplet
            //to allow font substitution.

            // Character Rotation
            baos.write(0x04);
            baos.write(0x26);
            baos.write(fd.orientation);
            baos.write(0x00);

            // Resource Local Identifier
            baos.write(0x04);
            baos.write(0x24);
            baos.write(0x05);
            baos.write(fd.fontReferenceKey);

            if (fd.scale != 0) {
                // Outline Font (triplet '1F')
                baos.write(0x14);
                baos.write(0x1F);
                baos.write(0x00);
                baos.write(0x00);

                baos.write(BinaryUtils.convert(fd.scale, 2)); // Height
                baos.write(new byte[]{0x00, 0x00}); // Width

                baos.write(new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                        0x00, 0x00, 0x00, 0x00, 0x00});

                baos.write(0x60);

                // Outline Font (triplet '5D')
                baos.write(0x04);
                baos.write(0x5D);
                baos.write(BinaryUtils.convert(fd.scale, 2));
            }
        }

        byte[] data = baos.toByteArray();

        // Set the total record length
        byte[] rl1 = BinaryUtils.convert(data.length - 1, 2);
        data[1] = rl1[0];
        data[2] = rl1[1];

        os.write(data);
    }

    /**
     * Add a font definition on the the map coded font object.
     *
     * @param fontReference
     *            the font number used as the resource identifier
     * @param font
     *            the font
     * @param size
     *            the size of the font
     * @param orientation
     *            the orientation of the font
     * @throws MaximumSizeExceededException if the maximum number of fonts have been exceeded
     */
    public void addFont(int fontReference, AFPFont font, int size, int orientation)
        throws MaximumSizeExceededException {

        FontDefinition fontDefinition = new FontDefinition();

        fontDefinition.fontReferenceKey = BinaryUtils.convert(fontReference)[0];

        switch (orientation) {
            case 90:
                fontDefinition.orientation = 0x2D;
                break;
            case 180:
                fontDefinition.orientation = 0x5A;
                break;
            case 270:
                fontDefinition.orientation = (byte) 0x87;
                break;
            default:
                fontDefinition.orientation = 0x00;
                break;
        }

        try {
            if (font instanceof RasterFont) {
                RasterFont raster = (RasterFont) font;
                CharacterSet cs = raster.getCharacterSet(size);
                if (cs == null) {
                    String msg = "Character set not found for font "
                        + font.getFontName() + " with point size " + size;
                    LOG.error(msg);
                    throw new FontRuntimeException(msg);
                }

                fontDefinition.characterSet = cs.getNameBytes();

                if (fontDefinition.characterSet.length != 8) {
                    throw new IllegalArgumentException("The character set "
                        + new String(fontDefinition.characterSet,
                        AFPConstants.EBCIDIC_ENCODING)
                        + " must have a fixed length of 8 characters.");
                }

                fontDefinition.codePage = cs.getCodePage().getBytes(
                    AFPConstants.EBCIDIC_ENCODING);

                if (fontDefinition.codePage.length != 8) {
                    throw new IllegalArgumentException("The code page "
                        + new String(fontDefinition.codePage,
                        AFPConstants.EBCIDIC_ENCODING)
                        + " must have a fixed length of 8 characters.");
                }

            } else if (font instanceof OutlineFont) {
                OutlineFont outline = (OutlineFont) font;
                CharacterSet cs = outline.getCharacterSet();
                fontDefinition.characterSet = cs.getNameBytes();

                // There are approximately 72 points to 1 inch or 20 1440ths per point.

                fontDefinition.scale = 20 * size / 1000;

                fontDefinition.codePage = cs.getCodePage().getBytes(
                    AFPConstants.EBCIDIC_ENCODING);

                if (fontDefinition.codePage.length != 8) {
                    throw new IllegalArgumentException("The code page "
                        + new String(fontDefinition.codePage,
                        AFPConstants.EBCIDIC_ENCODING)
                        + " must have a fixed length of 8 characters.");
                }
            }  else if (font instanceof DoubleByteFont) {
                DoubleByteFont outline = (DoubleByteFont) font;
                CharacterSet cs = outline.getCharacterSet();
                fontDefinition.characterSet = cs.getNameBytes();

                // There are approximately 72 points to 1 inch or 20 1440ths per point.

                fontDefinition.scale = 20 * size / 1000;

                fontDefinition.codePage = cs.getCodePage().getBytes(
                    AFPConstants.EBCIDIC_ENCODING);

                //TODO Relax requirement for 8 characters
                if (fontDefinition.codePage.length != 8) {
                    throw new IllegalArgumentException("The code page "
                        + new String(fontDefinition.codePage,
                        AFPConstants.EBCIDIC_ENCODING)
                        + " must have a fixed length of 8 characters.");
                }
            } else {
                String msg = "Font of type " + font.getClass().getName()
                    + " not recognized.";
                LOG.error(msg);
                throw new FontRuntimeException(msg);
            }

            if (fontList.size() > 253) {
                // Throw an exception if the size is exceeded
                throw new MaximumSizeExceededException();
            } else {
                fontList.add(fontDefinition);
            }

        } catch (UnsupportedEncodingException ex) {
            throw new FontRuntimeException("Failed to create font "
                + " due to a UnsupportedEncodingException", ex);
        }
    }


    /**
     * Private utility class used as a container for font attributes
     */
    private static final class FontDefinition {

        private FontDefinition() {
        }

        /**
         * The code page of the font
         */
        private byte[] codePage;

        /**
         * The character set of the font
         */
        private byte[] characterSet;

        /**
         * The font reference key
         */
        private byte fontReferenceKey;

        /**
         * The orientation of the font
         */
        private byte orientation;

        /**
         * The scale (only specified for outline fonts)
         */
        private int scale;
    }

}
