/*
 * Copyright (c) 2007-2012 The Broad Institute, Inc.
 * SOFTWARE COPYRIGHT NOTICE
 * This software and its documentation are the copyright of the Broad Institute, Inc. All rights are reserved.
 *
 * This software is supplied without any warranty or guaranteed support whatsoever. The Broad Institute is not responsible for its use, misuse, or functionality.
 *
 * This software is licensed under the terms of the GNU Lesser General Public License (LGPL),
 * Version 2.1 which is available at http://www.opensource.org/licenses/lgpl-2.1.php.
 */
/**
 * SnapshotUtilities.java
 *
 * Created on November 29, 2007, 2:14 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */
package org.broad.igv.ui.util;

import org.apache.batik.dom.GenericDOMImplementation;
import org.apache.batik.svggen.SVGGraphics2D;
import org.apache.log4j.Logger;
import org.broad.igv.ui.panel.MainPanel;
import org.broad.igv.ui.panel.Paintable;
import org.broad.igv.util.RuntimeUtils;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.lang.reflect.Constructor;

/**
 * Utility methods for supporting saving of images as jpeg, png, and svg files.
 *
 * @author eflakes
 * @modified jrobinso
 */
public class SnapshotUtilities {

    private static Logger log = Logger.getLogger(SnapshotUtilities.class);



    private static String EPSClassName = "net.sf.epsgraphics.EpsGraphics";
    private static String EPSColorModeClassName = "net.sf.epsgraphics.ColorMode";


    /**
     * The maximum height in pixels for snapshots of a panel.
     */
    public static int DEFAULT_MAX_PANEL_HEIGHT = 1000;

    /**
     * We need to use a static for max panel height,  or alternatively much refactoring, however this class might
     * be accessed by multiple threads which set this to different values => use a thread local
     */
    private static ThreadLocal<Integer> maxPanelHeight = new ThreadLocal() {
        @Override
        protected Object initialValue() {
            return new Integer(DEFAULT_MAX_PANEL_HEIGHT);
        }
    };

    public static int getMaxPanelHeight() {
        return maxPanelHeight.get().intValue();
    }

    public static void setMaxPanelHeight(int h) {
        maxPanelHeight.set(h);
    }

    // Treat this class as a singleton, no instances allowed
    private SnapshotUtilities() {
    }


    public static String doComponentSnapshot(Component component, File file, SnapshotFileChooser.SnapshotFileType type, boolean paintOffscreen) throws IOException{

        //TODO Should really make this work for more components
        if (paintOffscreen && !(component instanceof Paintable)) {
            log.error("Component cannot be painted offscreen. Performing onscreen paint");
            paintOffscreen = false;
        }

        if(paintOffscreen){

            Rectangle rect = component.getBounds();

            if(component instanceof MainPanel){
                rect.height = ((MainPanel) component).getOffscreenImageHeight();
            }else{
                rect.height = Math.min(component.getHeight(), getMaxPanelHeight());
            }

            // translate to (0, 0) if necessary
            int dx = rect.x;
            int dy = rect.y;
            rect.x = 0;
            rect.y = 0;
            rect.width -= dx;
            rect.height -= dy;

            component.setBounds(rect);
        }

        int width = component.getWidth();
        int height = component.getHeight();

        // Call appropriate converter
        String format = null;
        String[] exts = null;
        switch (type) {
            case SVG:
                //log.debug("Exporting svg screenshot");
                exportScreenshotSVG(component, file, paintOffscreen);
                //exportScreenshotVector2D(component, file, paintOffscreen);
                break;
            case JPEG:
                format = "jpeg";
                exts = new String[]{".jpg", ".jpeg"};
                break;
            case PNG:
                format = "png";
                exts = new String[]{"." + format};
                break;
            case EPS:
                exportScreenshotEpsGraphics(component, file, paintOffscreen);
                //exportScreenshotEpsGraphicsNoRef(component, file, paintOffscreen);
                break;
        }
        if(format != null && exts != null){
            exportScreenShotBufferedImage(component, file, width, height, exts, format, paintOffscreen);
        }
        return "OK";
    }

//    private static void exportScreenshotVector2D(Component target, File selectedFile, boolean paintOffscreen) throws IOException{
//
//        de.erichseifert.vectorgraphics2d.VectorGraphics2D g = null;
//        String filePath = selectedFile.getAbsolutePath();
//
//        if(filePath.endsWith(".svg")){
//            g = new de.erichseifert.vectorgraphics2d.SVGGraphics2D(0.0, 0.0, target.getWidth(), target.getHeight());
//        }else if(filePath.endsWith(".eps")){
//            g = new de.erichseifert.vectorgraphics2d.EPSGraphics2D(0.0, 0.0, target.getWidth(), target.getHeight());
//        }
//        target.paintAll(g);
//
//        // Write the output to a file
//        FileOutputStream file = new FileOutputStream(selectedFile);
//        try {
//            file.write(g.getBytes());
//        } finally {
//            file.close();
//        }
//
//    }

    private static void exportScreenshotEpsGraphics(Component target, File selectedFile, boolean paintOffscreen) throws IOException{

        if(!SnapshotUtilities.canExportScreenshotEps()){
            String msg = "ERROR: EPS output requires EPSGraphics library. See https://www.broadinstitute.org/software/igv/third_party_tools#epsgraphics";
            log.error(msg);
            return;
        }

        Graphics2D g = null;
        FileOutputStream fos = null;
        try{
            Class colorModeClass = RuntimeUtils.loadClassForName(EPSColorModeClassName, null);
            Class graphicsClass = RuntimeUtils.loadClassForName(EPSClassName, null);

            Constructor constructor = graphicsClass.getConstructor(String.class, OutputStream.class,
                    int.class, int.class, int.class, int.class, colorModeClass);

            Object colorModeValue = Enum.valueOf(colorModeClass, "COLOR_RGB");

            // EpsGraphics stores directly in a file
            fos = new FileOutputStream(selectedFile);
            g = (Graphics2D) constructor.newInstance("eps", fos, 0, 0, target.getWidth(), target.getHeight(), colorModeValue);

            choosePaint(target, g, paintOffscreen);

            graphicsClass.getMethod("close").invoke(g);

        }catch (Exception e){
            log.error(e.getMessage(), e);
        } finally {
            if(fos != null){
                fos.flush();
                fos.close();
            }
        }

    }

    public static boolean canExportScreenshotEps(){
        Constructor constr = null;
        try {
            Class colorModeClass = RuntimeUtils.loadClassForName(EPSColorModeClassName, null);
            Class graphicsClass = RuntimeUtils.loadClassForName(EPSClassName, null);
            constr = graphicsClass.getConstructor(String.class, OutputStream.class,
                    int.class, int.class, int.class, int.class, colorModeClass);
        } catch (Exception e) {
            //pass
        }
        return constr != null;
    }

//    private static void exportScreenshotEpsGraphicsNoRef(Component target, File selectedFile, boolean paintOffscreen) throws IOException{
//
//        FileOutputStream fos = null;
//        try {
//            // EpsGraphics stores directly in a file
//            fos = new FileOutputStream(selectedFile);
//            Graphics2D g = new EpsGraphics("eps", fos, 0, 0, target.getWidth(), target.getHeight(), ColorMode.COLOR_RGB);
//            choosePaint(target, g, paintOffscreen);
//
//        } finally {
//            if(fos != null){
//                fos.flush();
//                fos.close();
//            }
//        }
//
//    }

    private static void exportScreenshotSVG(Component target, File selectedFile, boolean paintOffscreen) throws IOException {

        String format = "svg";
        selectedFile = fixFileExt(selectedFile, new String[]{format}, format);

        // Create an instance of org.w3c.dom.Document.
        DOMImplementation domImpl = GenericDOMImplementation.getDOMImplementation();
        String svgNS = "http://www.w3.org/2000/svg";
        Document document = domImpl.createDocument(svgNS, format, null);

        // Write image data into document
        SVGGraphics2D svgGenerator = new SVGGraphics2D(document);

        choosePaint(target, svgGenerator, paintOffscreen);

        Writer out = null;
        try {
            // Finally, stream out SVG to the standard output using
            // UTF-8 encoding.
            boolean useCSS = true; // we want to use CSS style attributes
            out = new BufferedWriter(new OutputStreamWriter( new FileOutputStream(selectedFile), "UTF-8"));
            svgGenerator.stream(out, useCSS);
        } finally {
            if (out != null) try {
                out.close();
            } catch (IOException e) {
                log.error("Error closing svg file", e);
            }
        }

    }

    private static void choosePaint(Component target, Graphics2D g, boolean paintOffscreen){
        log.debug("Painting to target " + target + " , offscreen " + paintOffscreen);
        if(paintOffscreen){
            ((Paintable) target).paintOffscreen(g, target.getBounds());
        }else{
            target.paintAll(g);
        }
    }

    /**
     *
     * Export the specified {@code target} component as a {@code BufferedImage} to the given file.
     * @param target
     * @param selectedFile
     * @param width
     * @param height
     * @param allowedExts
     * @param format Format, also appended as an extension if the file doesn't end with anything in {@code allowedExts}
     * @param paintOffscreen
     * @throws IOException
     */
    private static void exportScreenShotBufferedImage(Component target, File selectedFile, int width, int height,
                                                      String[] allowedExts, String format, boolean paintOffscreen) throws IOException{
        BufferedImage image = getDeviceCompatibleImage(width, height);
        Graphics2D g = image.createGraphics();

        choosePaint(target, g, paintOffscreen);

        selectedFile = fixFileExt(selectedFile, allowedExts, format);
        if (selectedFile != null) {
            log.debug("Writing image to " + selectedFile.getAbsolutePath());
            ImageIO.write(image, format, selectedFile);
        }
    }

    /**
     * Add a file extension to the file if it doesn't already
     * have an acceptable one
     * @param selectedFile
     * @param allowedExts  Strings which qualify as extensions
     * @param defExtension Default extension. A period be inserted in between the file path iff {@code defExtension}
     *                     does not already have it
     * @return Either the input File, if it had an extension contained in {@code allowedExts},
     *         or a new with with {@code defExtension} appended
     */
    private static File fixFileExt(File selectedFile, String[] allowedExts, String defExtension){
        boolean hasExt = false;
        if (selectedFile != null) {
            for(String ext: allowedExts){
                if (selectedFile.getName().toLowerCase().endsWith(ext)) {
                    hasExt = true;
                    break;
                }
            }
            if(!hasExt){
                String addExt = defExtension.startsWith(".") ? defExtension : "." + defExtension;
                String correctedFilename = selectedFile.getAbsolutePath() + addExt;
                selectedFile = new File(correctedFilename);
            }
        }
        return selectedFile;
    }


    /**
     * Creates a device compatible BufferedImage
     *
     * @param width  the width in pixels
     * @param height the height in pixels
     */
    public static BufferedImage getDeviceCompatibleImage(int width, int height) {

        GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice screenDevice = graphicsEnvironment.getDefaultScreenDevice();
        GraphicsConfiguration graphicConfiguration = screenDevice.getDefaultConfiguration();
        BufferedImage image = graphicConfiguration.createCompatibleImage(width, height);

        return image;
    }


}
