package org.foo;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;

import org.apache.chemistry.opencmis.commons.data.ContentStream;
import org.apache.chemistry.opencmis.commons.data.ExtensionsData;
import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl;
import org.apache.chemistry.opencmis.commons.server.CallContext;
import org.apache.chemistry.opencmis.commons.server.CmisService;
import org.apache.chemistry.opencmis.commons.server.TempStoreOutputStream;
import org.apache.chemistry.opencmis.server.shared.TempStoreOutputStreamFactory;
import org.apache.chemistry.opencmis.server.shared.ThresholdOutputStream;
import org.apache.chemistry.opencmis.server.support.wrapper.AbstractCmisServiceWrapper;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
import org.apache.pdfbox.pdmodel.graphics.xobject.PDPixelMap;
import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectImage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Example of a minimal CMIS Custom Service Wrapper (logging example)
 * 
 * Add the following ** to the repo.properties to have framework hook into chain
 * The number at the key is the position in the wrapper stack. Lower numbers are
 * outer wrappers, higher numbers are inner wrappers.
 *
 * ** add the following line to your repo.properties file in your servers war
 * 
 * <pre>
 * servicewrapper.1=org.apache.chemistry.opencmis.server.support.CmisCustomLoggingServiceWrapper
 * </pre>
 */
public class CmisCustomPdfWatermarkServiceWrapper extends AbstractCmisServiceWrapper {

    // slf4j example
    private static final Logger LOG = LoggerFactory.getLogger(CmisCustomPdfWatermarkServiceWrapper.class);

    // constants for this extension
    public static final String USER_FILTER_NAME = "userfilter";

    private String userToWatermark = null;

    // provide constructor
    public CmisCustomPdfWatermarkServiceWrapper(CmisService service) {
        super(service);
    }

    /**
     * Initializes the wrapper with a set of optional parameters from the
     * properties file
     */
    @Override
    public void initialize(Object[] params) {

        // Whenever CmisServiceWrapperManager.wrap() is called, a new wrapper
        // instance is created and the corresponding initialize() method is
        // called.
        // If the service factory creates a new CmisService object and wraps it
        // with every request, then new wrapper instances are created and
        // initialized for every request.
        // This is why in this case you will see initialize get called for every
        // request.
        // Depending on your implementation you may only see this called once.

        LOG.info("Initializing the CmisCustomPdfWatermarkServiceWrapper.");

        for (Object parm : params) {
            if (parm == null) {
                continue;
            }

            LOG.info("[CmisCustomPdfWatermarkServiceWrapper]Parameter:" + parm.toString());

            // Let's store the ID of the user that we want to flag for
            // watermarking.
            String[] parts = parm.toString().split("=");

            if (USER_FILTER_NAME.equalsIgnoreCase(parts[0])) {
                // we have a user
                userToWatermark = parts[1];
            }
        }
    }

    /**
     * slf logging version with dual output to console and slf
     */
    protected void slflog(String operation, String repositoryId) {
        if (repositoryId == null) {
            repositoryId = "<none>";
        }

        HttpServletRequest request = (HttpServletRequest) getCallContext().get(CallContext.HTTP_SERVLET_REQUEST);
        String userAgent = request.getHeader("User-Agent");
        if (userAgent == null) {
            userAgent = "<unknown>";
        }

        String binding = getCallContext().getBinding();

        LOG.info("Operation: {}, Repository ID: {}, Binding: {}, User Agent: {}", operation, repositoryId, binding,
                userAgent);
    }

    @Override
    public ContentStream getContentStream(String repositoryId, String objectId, String streamId, BigInteger offset,
            BigInteger length, ExtensionsData extension) {

        slflog("getContentStream override from Chameleon module --------------", repositoryId);
        long startTime = System.currentTimeMillis();

        CallContext sharedContext = this.getCallContext();

        // Get the native domain object from the call context if one is shared
        // by the vendor (example only)
        // Your CMIS vendor's documentation must expose the name of any shared
        // objects they place here for extensions.
        // Object objShared = sharedContext.get("shared_key_name_from_vendor");

        ContentStream retVal = getWrappedService().getContentStream(repositoryId, objectId, streamId, offset, length,
                extension);

        if (sharedContext.getUsername().equalsIgnoreCase(userToWatermark)) {
            if ((retVal != null) && (retVal.getMimeType().contains("pdf"))) {
                InputStream rawStream = retVal.getStream();

                // return a pdfbox document object
                // for debugging only - load to pdfbox and stream out
                // PDDocument modifiedPDF = watermarkPDF_loadOnly(rawStream);
                // actual watermark code
                PDDocument modifiedPDF = watermarkPDF(rawStream);

                // Extra credit here. Replace with TempStoreOutputStream or find
                // another way to handle very large objects in a small memory
                // footprint.
                // ByteArrayOutputStream out = new ByteArrayOutputStream();
                TempStoreOutputStream out;
                TempStoreOutputStreamFactory outFactory = (TempStoreOutputStreamFactory) sharedContext
                        .get(CallContext.STREAM_FACTORY);
                if (outFactory != null) {
                    // reuse the server factory configuration
                    out = outFactory.newOutputStream();
                } else {
                    // there is no default ThresholdOutputStreamFactory
                    // -> create a stream manually:
                    // default temp directory, max 4MiB in main memory,
                    // unlimited content size
                    out = new ThresholdOutputStream(null, 4 * 1024 * 1024, -1);
                }

                try {
                    modifiedPDF.save(out);
                    modifiedPDF.close();
                    InputStream modifiedInputStream = out.getInputStream(); // new
                                                                            // ByteArrayInputStream(out.toByteArray());

                    // Extra credit here. Handle offset and length if provided
                    // by the client.

                    // now write the stream back to the ContentStream object
                    retVal = new ContentStreamImpl(retVal.getFileName(), null, "application/pdf", modifiedInputStream);

                } catch (Exception e) {
                    slflog("error transposing stream getContentStream ", e.getMessage());

                    LOG.error("Could not watermark PDF document: {}", e.getMessage(), e);
                    throw new CmisRuntimeException("Could not watermark PDF document!");
                }
            } // if pdf stream
        } // if user matches filter param

        LOG.info("[CmisCustomServiceWrapper] Exiting method getContentStream. time (ms):"
                + (System.currentTimeMillis() - startTime));
        // System.out.println("[CmisCustomServiceWrapper] Exiting method getContentStream. time (ms):"
        // + (System.currentTimeMillis() - startTime));

        return retVal;
    }

    public PDDocument watermarkPDF(InputStream rawStream) {
        PDDocument pdf = null;
        try {

            pdf = PDDocument.load(rawStream);

            // Load watermark from classes directory cmis.jpg
            // Alternatives:
            // 1. Use a pdf document from the repository (via a fixed path) so
            // the watermark can be changed at runtime by an administrator.
            // 2. Dynamically generate a watermark based on the username / time
            // date and users ip address.
            InputStream watermarkStream = CmisCustomPdfWatermarkServiceWrapper.class.getClassLoader()
                    .getResourceAsStream("cmis.png");
            BufferedImage buffered = ImageIO.read(watermarkStream);

            // Exercise A - add image to all pages of document.
            // Loop through pages in PDF
            // List pages = pdf.getDocumentCatalog().getAllPages();
            // System.out.println("pdf loaded from stream. Pages found:" +
            // pages.size());
            // Iterator iter = pages.iterator();
            // while(iter.hasNext())
            // {
            // PDPage page = (PDPage)iter.next();
            // ...

            addImageToPage(pdf, 0, 0, 0, 1.0f, buffered);

            // example of how to set metadata on the document
            pdf.getDocumentInformation().setTitle("Modified by fileBridge");
        } catch (Exception e) {
            slflog("error watermarking pdf in getContentStream ", e.getMessage());
        }

        return pdf;
    }

    // Original code taken from Nick Russler's example here:
    // https://stackoverflow.com/questions/8929954/watermarking-with-pdfbox
    // and modified to use a BufferedImage directly.
    // Thank's Nick.
    //
    public void addImageToPage(PDDocument document, int pdfpage, int x, int y, float scale, BufferedImage tmp_image)
            throws IOException {
        // Convert the image to TYPE_4BYTE_ABGR so PDFBox won't throw exceptions
        // (e.g. for transparent png's).
        BufferedImage image = new BufferedImage(tmp_image.getWidth(), tmp_image.getHeight(),
                BufferedImage.TYPE_4BYTE_ABGR);
        image.createGraphics().drawRenderedImage(tmp_image, null);

        PDXObjectImage ximage = new PDPixelMap(document, image);
        PDPage page = (PDPage) document.getDocumentCatalog().getAllPages().get(pdfpage);
        PDPageContentStream contentStream = new PDPageContentStream(document, page, true, true);

        contentStream.drawXObject(ximage, x, y, ximage.getWidth() * scale, ximage.getHeight() * scale);
        contentStream.close();
    }
}
