package com.clarabot.picoture;

import com.twelvemonkeys.servlet.image.IIOProviderContextListener;
import io.javalin.Javalin;
import io.javalin.http.HttpCode;
import net.coobird.thumbnailator.ThumbnailParameter;
import net.coobird.thumbnailator.Thumbnails;
import net.coobird.thumbnailator.filters.ImageFilter;
import net.coobird.thumbnailator.geometry.Positions;
import net.coobird.thumbnailator.resizers.DefaultResizerFactory;
import net.coobird.thumbnailator.resizers.Resizers;
import net.coobird.thumbnailator.tasks.UnsupportedFormatException;
import net.coobird.thumbnailator.tasks.io.InputStreamImageSource;
import net.sourceforge.argparse4j.ArgumentParsers;
import net.sourceforge.argparse4j.impl.Arguments;
import net.sourceforge.argparse4j.inf.ArgumentParser;
import net.sourceforge.argparse4j.inf.ArgumentParserException;
import net.sourceforge.argparse4j.inf.Namespace;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;

public class Picoture {
    private static final Logger logger = LogManager.getLogger(Picoture.class);

    private static class DrawWhiteCanvasFilter implements ImageFilter {
        @Override
        public BufferedImage apply(BufferedImage img) {
            final int w = img.getWidth();
            final int h = img.getHeight();
            BufferedImage newImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
            Graphics2D graphic = newImage.createGraphics();
            graphic.setColor(Color.white); // white canvas is the most common, so we add it manually
            graphic.fillRect(0, 0, w, h);
            graphic.drawRenderedImage(img, null);
            graphic.dispose();
            return newImage;
        }
    }

    private static String getVersion() {
        String version = null;
        try (InputStream stream = Picoture.class.getResourceAsStream("/META-INF/maven/com.clarabot.picoture/picoture/pom.properties")) {
            if (stream != null) {
                Properties properties = new Properties();
                properties.load(stream);
                version = properties.getProperty("version");
            }
        } catch (Exception e) {
            // ignore
        }
        if (version == null) {
            version = "?";
        }
        return version;
    }

    public static void main(String[] args) {
        System.setProperty("apple.awt.UIElement", "true");
        String name_version = "Picoture " + getVersion();
        ArgumentParser parser = ArgumentParsers.newFor("Picoture").build()
                .defaultHelp(true)
                .description("Small worker-server to create small pictures from input data.")
                .version(name_version);
        parser.addArgument("--version").action(Arguments.version());
        parser.addArgument("--host").setDefault("127.0.0.1").help("Specify the address to listen on");
        parser.addArgument("--port").type(Integer.class).choices(Arguments.range(1, 65535)).setDefault(7070).help("Specify the port to listen on");
        parser.addArgument("--width").type(Integer.class).choices(Arguments.range(16, 800)).setDefault(96).help("Specify the maximum width of generated pictures.");
        parser.addArgument("--height").type(Integer.class).choices(Arguments.range(16, 800)).setDefault(96).help("Specify the maximum width of generated pictures.");
        parser.addArgument("--quality").type(Float.class).choices(Arguments.range(0.f, 1.f)).setDefault(0.77f).help("Specify the quality of generated pictures.");
        Namespace ns = null;
        try {
            ns = parser.parseArgs(args);
        } catch (ArgumentParserException e) {
            parser.handleError(e);
            System.exit(1);
        }
        Javalin app = Javalin.create(config -> {
            config.configureServletContextHandler(handler -> {
                handler.addEventListener(new IIOProviderContextListener());
            });
        }).start(ns.getString("host"), ns.getInt("port"));
        final int maxWidth = ns.getInt("width");
        final int maxHeight = ns.getInt("height");
        final float quality = ns.getFloat("quality");
        app.get("/version", ctx -> ctx.result(name_version));
        app.post("/picture", ctx -> {
            final String contentType = ctx.contentType();
            BufferedImage thumbnail = null;
            List<ImageFilter> extraFilters = null;
            // Load input image by the relevant library selected by the content-type header.
            if ("application/pdf".equals(contentType)) {
                try {
                    PDDocument pdfDoc = PDDocument.load(ctx.bodyAsInputStream());
                    PDFRenderer pdfRender = new PDFRenderer(pdfDoc);
                    thumbnail = pdfRender.renderImageWithDPI(0, 72);
                } catch (IOException e) {
                    ctx.status(HttpCode.UNPROCESSABLE_ENTITY);
                }
            } else {
                // For all other cases we try to load an image file with ImageIO.
                // We need to have some tricky code here to extract the exif orientation fixes for later.
                ThumbnailParameter prepareParam = new ThumbnailParameter(
                        new Dimension(16, 16), null, true, null, null, 1.0f, 0, null, DefaultResizerFactory.getInstance(), true, true
                );
                InputStreamImageSource imageSource = new InputStreamImageSource(ctx.bodyAsInputStream());
                imageSource.setThumbnailParameter(prepareParam);
                try {
                    thumbnail = imageSource.read(); // TODO: getNumImage && index not supported by thumbnailator yet for selection from ICO for example
                    if (prepareParam.getImageFilters().size() > 0) {
                        extraFilters = prepareParam.getImageFilters();
                    }
                } catch (UnsupportedFormatException e) {
                    ctx.status(HttpCode.UNSUPPORTED_MEDIA_TYPE);
                }
            }
            if (thumbnail != null) {
                Thumbnails.Builder<BufferedImage> builder = Thumbnails.of(thumbnail);
                if (thumbnail.getColorModel().hasAlpha()) {
                    builder = builder.addFilter(new DrawWhiteCanvasFilter());
                }
                builder = builder.imageType(BufferedImage.TYPE_INT_ARGB); // workaround to issue 135 (alpha-palette)
                if (extraFilters != null) {
                    builder = builder.addFilters(extraFilters);
                }
                final int w = thumbnail.getWidth();
                final int h = thumbnail.getHeight();
                if (w > maxWidth || h > maxHeight) {
                    if (w * 8 < h) {
                        // The width is less than an eighth of the height, we need to cut from the height.
                        builder = builder.sourceRegion(0, (h - (w * 8)) >> 1, w, w * 8)
                                .crop(Positions.CENTER)
                                .size(maxWidth >> 3, maxHeight);
                    } else if (h * 8 < w) {
                        // The height is less than an eighth of the width, we need to cut from the width.
                        builder = builder.sourceRegion((w - (h * 8)) >> 1, 0, h * 8, h)
                                .crop(Positions.CENTER)
                                .size(maxWidth, maxHeight >> 3);
                    } else {
                        builder = builder.size(maxWidth, maxHeight);
                    }
                } else {
                    builder = builder.resizer(Resizers.NULL).scale(1.0);
                }
                try {
                    builder.outputFormat("jpg")
                            .outputQuality(quality)
                            .toOutputStream(ctx.contentType("image/jpeg").res.getOutputStream());
                } catch (IOException e) {
                    ctx.status(HttpCode.UNPROCESSABLE_ENTITY);
                }
            }
        });
    }
}
