/*
 * 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: AFPDataObjectFactory.java 1903806 2022-09-01 10:14:56Z ssteiner $ */

package org.apache.fop.afp;

import java.awt.geom.Rectangle2D;

import org.apache.xmlgraphics.image.codec.tiff.TIFFImage;
import org.apache.xmlgraphics.java2d.Graphics2DImagePainter;

import org.apache.fop.afp.ioca.IDEStructureParameter;
import org.apache.fop.afp.ioca.ImageContent;
import org.apache.fop.afp.modca.AbstractDataObject;
import org.apache.fop.afp.modca.AbstractNamedAFPObject;
import org.apache.fop.afp.modca.Document;
import org.apache.fop.afp.modca.GraphicsObject;
import org.apache.fop.afp.modca.ImageObject;
import org.apache.fop.afp.modca.IncludeObject;
import org.apache.fop.afp.modca.ObjectContainer;
import org.apache.fop.afp.modca.Overlay;
import org.apache.fop.afp.modca.PageSegment;
import org.apache.fop.afp.modca.Registry;
import org.apache.fop.afp.modca.ResourceObject;
import org.apache.fop.afp.modca.triplets.MappingOptionTriplet;
import org.apache.fop.afp.modca.triplets.ObjectClassificationTriplet;

/**
 * Factory for high level data objects (Image/Graphics etc)
 */
public class AFPDataObjectFactory {

    private final Factory factory;

    /**
     * Main constructor
     *
     * @param factory an object factory
     */
    public AFPDataObjectFactory(Factory factory) {
        this.factory = factory;
    }

    /**
     * Creates and configures an ObjectContainer.
     *
     * @param dataObjectInfo the object container info
     * @return a newly created Object Container
     */
    public ObjectContainer createObjectContainer(AFPDataObjectInfo dataObjectInfo) {
        ObjectContainer objectContainer = factory.createObjectContainer();

        // set data object viewport (i.e. position, rotation, dimension, resolution)
        objectContainer.setViewport(dataObjectInfo);

        // set object classification
        Registry.ObjectType objectType = dataObjectInfo.getObjectType();
        AFPResourceInfo resourceInfo = dataObjectInfo.getResourceInfo();
        AFPResourceLevel resourceLevel = resourceInfo.getLevel();
        final boolean dataInContainer = true;
        final boolean containerHasOEG = resourceLevel.isInline();
        final boolean dataInOCD = true;
        objectContainer.setObjectClassification(
                ObjectClassificationTriplet.CLASS_TIME_INVARIANT_PAGINATED_PRESENTATION_OBJECT,
                objectType, dataInContainer, containerHasOEG, dataInOCD);

        objectContainer.setData(dataObjectInfo.getData());
        return objectContainer;
    }

    /**
     * Creates and configures an IOCA Image Object.
     *
     * @param imageObjectInfo the image object info
     * @return a newly created IOCA Image Object
     */
    public ImageObject createImage(AFPImageObjectInfo imageObjectInfo) {
        // IOCA bitmap image
        ImageObject imageObj = factory.createImageObject();

        // set data object viewport (i.e. position, rotation, dimension, resolution)
        imageObj.setViewport(imageObjectInfo);

        if (imageObjectInfo.hasCompression()) {
            int compression = imageObjectInfo.getCompression();
            switch (compression) {
            case TIFFImage.COMP_FAX_G3_1D:
                imageObj.setEncoding(ImageContent.COMPID_G3_MH);
                break;
            case TIFFImage.COMP_FAX_G3_2D:
                imageObj.setEncoding(ImageContent.COMPID_G3_MR);
                break;
            case TIFFImage.COMP_FAX_G4_2D:
                imageObj.setEncoding(ImageContent.COMPID_G3_MMR);
                break;
            case ImageContent.COMPID_JPEG:
                imageObj.setEncoding((byte)compression);
                break;
            default:
                throw new IllegalStateException(
                        "Invalid compression scheme: " + compression);
            }
        }

        ImageContent content = imageObj.getImageSegment().getImageContent();
        int bitsPerPixel = imageObjectInfo.getBitsPerPixel();
        imageObj.setIDESize((byte) bitsPerPixel);
        IDEStructureParameter ideStruct;
        switch (bitsPerPixel) {
        case 1:
            //Skip IDE Structure Parameter
            break;
        case 4:
        case 8:
            //A grayscale image
            ideStruct = content.needIDEStructureParameter();
            ideStruct.setBitsPerComponent(new int[] {bitsPerPixel});
            ideStruct.setColorModel(IDEStructureParameter.COLOR_MODEL_YCBCR);
            break;
        case 24:
            ideStruct = content.needIDEStructureParameter();
            ideStruct.setDefaultRGBColorModel();
            break;
        case 32:
            ideStruct = content.needIDEStructureParameter();
            ideStruct.setDefaultCMYKColorModel();
            break;
        default:
            throw new IllegalArgumentException("Unsupported number of bits per pixel: "
                    + bitsPerPixel);
        }
        if (bitsPerPixel > 1 && imageObjectInfo.isSubtractive()) {
            ideStruct = content.needIDEStructureParameter();
            ideStruct.setSubtractive(imageObjectInfo.isSubtractive());
        }

        imageObj.setData(imageObjectInfo.getData());
        imageObj.setTransparencyMask(imageObjectInfo.getTransparencyMask());

        return imageObj;
    }

    /**
     * Creates and returns a new graphics object.
     *
     * @param graphicsObjectInfo the graphics object info
     * @return a new graphics object
     */
    public GraphicsObject createGraphic(AFPGraphicsObjectInfo graphicsObjectInfo) {
        // set newly created graphics object in g2d
        GraphicsObject graphicsObj = factory.createGraphicsObject();

        // set data object viewport (i.e. position, rotation, dimension, resolution)
        graphicsObj.setViewport(graphicsObjectInfo);

        AFPGraphics2D g2d = graphicsObjectInfo.getGraphics2D();
        g2d.setGraphicsObject(graphicsObj);

        //set color converter (i.e. an rgb to grayscale converter)
        graphicsObj.setColorConverter(g2d.getPaintingState().getColorConverter());

        // paint to graphics object
        Graphics2DImagePainter painter = graphicsObjectInfo.getPainter();
        Rectangle2D area = graphicsObjectInfo.getArea();
        g2d.scale(1, -1);
        g2d.translate(0, -area.getHeight());

        painter.paint(g2d, area);

        graphicsObj.setComplete(true);

        // return painted graphics object
        return graphicsObj;
    }

    /**
     * Creates and returns a new include object.
     *
     * @param includeName the include name
     * @param dataObjectInfo a data object info
     *
     * @return a new include object
     */
    public IncludeObject createInclude(String includeName, AFPDataObjectInfo dataObjectInfo) {
        IncludeObject includeObj = factory.createInclude(includeName);

        if (dataObjectInfo instanceof AFPImageObjectInfo) {
            // IOCA image object
            includeObj.setObjectType(IncludeObject.TYPE_IMAGE);
        } else if (dataObjectInfo instanceof AFPGraphicsObjectInfo) {
            // graphics object
            includeObj.setObjectType(IncludeObject.TYPE_GRAPHIC);
        } else {
            // object container
            includeObj.setObjectType(IncludeObject.TYPE_OTHER);

            // set mandatory object classification (type other)
            Registry.ObjectType objectType = dataObjectInfo.getObjectType();
            if (objectType != null) {
                // set object classification
                final boolean dataInContainer = true;
                final boolean containerHasOEG = false; // environment parameters set in include
                final boolean dataInOCD = true;
                includeObj.setObjectClassification(
                   // object scope not defined
                   ObjectClassificationTriplet.CLASS_TIME_VARIANT_PRESENTATION_OBJECT,
                   objectType, dataInContainer, containerHasOEG, dataInOCD);
            } else {
                throw new IllegalStateException(
                        "Failed to set Object Classification Triplet on Object Container.");
            }
        }

        AFPObjectAreaInfo objectAreaInfo = dataObjectInfo.getObjectAreaInfo();

        int xOffset = objectAreaInfo.getX();
        int yOffset = objectAreaInfo.getY();
        includeObj.setObjectAreaOffset(xOffset, yOffset);

        int width = objectAreaInfo.getWidth();
        int height = objectAreaInfo.getHeight();
        includeObj.setObjectAreaSize(width, height);

        int rotation = objectAreaInfo.getRotation();
        includeObj.setObjectAreaOrientation(rotation);

        int widthRes = objectAreaInfo.getWidthRes();
        int heightRes = objectAreaInfo.getHeightRes();
        includeObj.setMeasurementUnits(widthRes, heightRes);

        includeObj.setMappingOption(MappingOptionTriplet.SCALE_TO_FIT);

        return includeObj;
    }

    /**
     * Creates a resource object wrapper for named includable data objects
     *
     * @param namedObj an named object
     * @param resourceInfo resource information
     * @param objectType the object type
     * @return a new resource object wrapper
     */
    public ResourceObject createResource(AbstractNamedAFPObject namedObj,
            AFPResourceInfo resourceInfo, Registry.ObjectType objectType) {
        ResourceObject resourceObj = null;
        String resourceName = resourceInfo.getName();
        if (resourceName != null) {
            resourceObj = factory.createResource(resourceName);
        } else {
            resourceObj = factory.createResource();
        }

        if (namedObj instanceof Document) {
            resourceObj.setType(ResourceObject.TYPE_DOCUMENT);
        } else if (namedObj instanceof PageSegment) {
            resourceObj.setType(ResourceObject.TYPE_PAGE_SEGMENT);
        } else if (namedObj instanceof Overlay) {
            resourceObj.setType(ResourceObject.TYPE_OVERLAY_OBJECT);
        } else if (namedObj instanceof AbstractDataObject) {
            AbstractDataObject dataObj = (AbstractDataObject)namedObj;
            if (namedObj instanceof ObjectContainer) {
                resourceObj.setType(ResourceObject.TYPE_OBJECT_CONTAINER);

                // set object classification
                final boolean dataInContainer = true;
                final boolean containerHasOEG = false; // must be included
                final boolean dataInOCD = true;
                // mandatory triplet for object container
                resourceObj.setObjectClassification(
                    ObjectClassificationTriplet.CLASS_TIME_INVARIANT_PAGINATED_PRESENTATION_OBJECT,
                    objectType, dataInContainer, containerHasOEG, dataInOCD);
            } else if (namedObj instanceof ImageObject) {
                // ioca image type
                resourceObj.setType(ResourceObject.TYPE_IMAGE);
            } else if (namedObj instanceof GraphicsObject) {
                resourceObj.setType(ResourceObject.TYPE_GRAPHIC);
            } else {
                throw new UnsupportedOperationException(
                        "Unsupported resource object for data object type " + dataObj);
            }
        } else {
            throw new UnsupportedOperationException(
              "Unsupported resource object type " + namedObj);
        }

        // set the resource information/classification on the data object
        resourceObj.setDataObject(namedObj);
        return resourceObj;
    }

}
