package cc.catalysts.boot.report.pdf.elements;

import cc.catalysts.boot.report.pdf.utils.ReportAlignType;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;

/**
 * <p><b>IMPORTANT:</b> Although this class is publicly visible, it is subject to change and may not be implemented by clients!</p>
 *
 * @author Klaus Lehner
 */
public class ReportImage extends AbstractReportElement implements ReportElement {

    private BufferedImage img;
    private ImagePrintIntent intent;
    private float width;
    private float height;
    private ReportAlignType align = ReportAlignType.LEFT;

    /**
     * The image will be placed as is. Please provide the greatest size as possible.
     *
     * @param img    Buffered image
     * @param width  width of image on report
     * @param height height of image on report
     */
    public ReportImage(BufferedImage img, float width, float height) {
        this.img = img;
        this.width = width;
        this.height = height;
    }

    @Override
    public float print(PDDocument document, PDPageContentStream stream, int pageNumber, float leftX, float startY, float allowedWidth) throws IOException {
        intent = new ImagePrintIntent(this, pageNumber, calcStartX(leftX, allowedWidth, width), startY);
        return startY - height;
    }

    @Override
    public float getHeight(float allowedWidth) {
        return height;
    }

    /**
     * <p>Call this method to print images. <b>Make sure that the streams are closed before calling this method </b></p>
     * <p>Normal print method doesn't work since: http://stackoverflow.com/questions/9326245/how-to-exactly-position-an-image-inside-an-existing-pdf-page-using-pdfbox</p>
     *
     * @param document   the pdDocument.
     * @param pageNumber page of image
     * @param x          location of image
     * @param y          location of image
     * @throws java.io.IOException in case there are problems at reading or writing the image
     */
    public void printImage(PDDocument document, int pageNumber, float x, float y) throws IOException {
        PDImageXObject obj = LosslessFactory.createFromImage(document, img);

        PDPageContentStream currentStream = new PDPageContentStream(document,
                document.getDocumentCatalog().getPages().get(pageNumber), PDPageContentStream.AppendMode.APPEND, false);

        currentStream.drawImage(obj, x, y - height, width, height);
        currentStream.close();
    }

    private float calcStartX(float leftX, float allowedWidth, float imgWidth) {
        switch (getAlign()) {
            case LEFT:
                return leftX;
            case CENTER:
                return (allowedWidth - imgWidth) / 2 + leftX;
            default:
                throw new IllegalStateException("align type not implemented");
        }
    }

    @Override
    public Collection<ImagePrintIntent> getImageIntents() {
        if (intent == null) {
            throw new IllegalStateException("print must be called before getImage intents");
        }
        return Collections.singletonList(intent);
    }

    public void setAlign(ReportAlignType align) {
        this.align = align;
    }

    public ReportAlignType getAlign() {
        return align;
    }

    /**
     * This class is required, since pdf image printing has a bug. The img element must be created before the pdf box stream.
     */
    public static class ImagePrintIntent {
        private ReportImage img;
        private int page;
        private float x;
        private float y;

        public ImagePrintIntent(ReportImage img, int page, float x, float y) {
            this.img = img;
            this.page = page;
            this.x = x;
            this.y = y;
        }

        public ReportImage getImg() {
            return img;
        }

        public int getPage() {
            return page;
        }

        public float getX() {
            return x;
        }

        public float getY() {
            return y;
        }
    }

    public void setWidth(float width) {
        this.width = width;
    }

    public void setHeight(float height) {
        this.height = height;
    }

    public float getWidth() {
        return width;
    }

    public float getHeight() {
        return height;
    }
}