/**
 *  Licensed under GPL. For more information, see
 *    http://jaxodraw.sourceforge.net/license.html
 *  or the LICENSE file in the jaxodraw distribution.
 */
package net.sf.jaxodraw.io.exports;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;

import java.io.File;
import java.io.IOException;

import java.util.Vector;

import javax.imageio.ImageIO;

import javax.swing.ImageIcon;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;

import net.sf.jaxodraw.gui.panel.JaxoColorChooser;
import net.sf.jaxodraw.gui.panel.button.JaxoColorButton;
import net.sf.jaxodraw.io.JaxoPreview;
import net.sf.jaxodraw.plugin.JaxoPluginExecutionException;
import net.sf.jaxodraw.util.JaxoColor;
import net.sf.jaxodraw.util.JaxoDictionary;
import net.sf.jaxodraw.util.graphics.JaxoGraphics2D;


/** The class that is responsible for exporting to image formats: jpg,
 * or png are currently possible options. Asks for an export file name.
 * @since 2.0
 */
public class JaxoExportImg extends JaxoExport {
    private static final JaxoDictionary LANGUAGE = new JaxoDictionary(JaxoExportImg.class);

    /**
     * The export modes supported by this JaxoExport.
     */
    public enum Format { JPG, PNG }

    // private
    private final Format mode;
    private Color background;
    private boolean translucent;
    private boolean antialiasing;
    private float resolution; // dpi
    private JComponent panel;
    private JTextField resolutionText;
    private JaxoColorButton backgroundButton;
    private JLabel bgColorLabel;
    private JLabel noBackgroundPaintingLabel;
    private JLabel antialiasLabel;
    private JLabel resolutionLabel;
    private JFrame previewFrame;

    /** The unique plugin id. */
    private static final String PLUGIN_ID = JaxoExportImg.class.getName();

    // only used during export
    private Dimension size;
    private Point2D origin;
    private float scale;

    /**
     * Constructor.
     * @param exportMode The export mode.
     */
    public JaxoExportImg(final Format exportMode) {
        super();
        this.mode = exportMode;

        background = Color.white;
        translucent = !mode.equals(Format.JPG);
        resolution = 72;
    }

    /**
     * Resolution of produces images in dots-per-inch.
     * The default is 72, corresponding to the default
     * coordinate system.
     * @return The resolution.
     */
    public final float getResolution() {
        return resolution;
    }

    /**
     * Sets the resolution of the img export.
     * @param value The desired resolution.
     */
    public final void setResolution(final float value) {
        resolution = value;
    }

    /**
     * Returns the current export mode.
     * @return The export mode.
     */
    public final Format getMode() {
        return mode;
    }

    /** Sets the antialias setting.
     * @param value The new antialias setting
     */
    public final void setAntialiasing(final boolean value) {
        antialiasing = value;
    }

    /**
     * Returns the current antialias setting.
     * @return The current antialias setting.
     */
    public final boolean isAntialiasing() {
        return antialiasing;
    }

    /** {@inheritDoc} */
    public final String getFormatName() {
        return LANGUAGE.value("formatName."
            + getFileExtension());
    }

    /** {@inheritDoc} */
    public final String getFileExtension() {
        return (getMode().equals(Format.PNG)) ? "png" : "jpg";
    }

    /** {@inheritDoc} */
    public final String getFileExtensionDescription() {
        return LANGUAGE.value("fileDescription." + getFileExtension());
    }

    /** {@inheritDoc} */
    public String description() {
        return LANGUAGE.value("description." + getFileExtension());
    }

    /** {@inheritDoc} */
    public String pluginId() {
        return PLUGIN_ID;
    }

    /** {@inheritDoc} */
    public String getShortName() {
        return "img-export";
    }

    private boolean canBeTranslucent() {
        return getMode().equals(Format.PNG);
    }

    /** {@inheritDoc} */
    protected void exportTo(final String fileName)
        throws JaxoPluginExecutionException {
        try {
            final File file = new File(fileName);
            ImageIO.write(newRenderedImage(), getFileExtension(), file);
        } catch (IOException e) {
            throw new JaxoPluginExecutionException(
                errorDialogMessage(fileName), e, this);
        }
    }

    private RenderedImage newRenderedImage() {
        setupImage();

        return new ExportImage(new BufferedImage(1, 1,
            translucent ? BufferedImage.TYPE_INT_ARGB
                        : BufferedImage.TYPE_INT_RGB));
    }

    private BufferedImage newBufferedImage() {
        setupImage();

        final BufferedImage result =
            new BufferedImage(size.width, size.height,
                translucent ? BufferedImage.TYPE_INT_ARGB
                            : BufferedImage.TYPE_INT_RGB);

        paintToImage(result, 0);

        return result;
    }

    private void setupImage() {
        // clip to screen size?
        Rectangle bb = getGraph().getBounds();

        if (bb == null) {
            bb = new Rectangle();
        }

        final int x = bb.x;
        final int y = bb.y;
        final int w = Math.max(1, bb.width + 1);
        final int h = Math.max(1, bb.height + 1);

        scale = resolution / 72;
        origin = new Point2D.Double(-x * scale, -y * scale);
        size =
            new Rectangle2D.Double(0, 0, scale * w, scale * h).getBounds()
                                                              .getSize();
    }

    private void paintToImage(final BufferedImage m, final int dy) {
        final JaxoGraphics2D g = new JaxoGraphics2D(m.createGraphics());

        g.setPrinting(true);

        if (antialiasing) {
            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        }

        g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
            RenderingHints.VALUE_STROKE_PURE);

        if (translucent) {
            g.setColor(Color.black);
            g.setComposite(AlphaComposite.Clear);
            g.fillRect(0, 0, m.getWidth(), m.getHeight());
            g.setComposite(AlphaComposite.SrcOver);
        } else {
            g.setColor(background);
            g.fillRect(0, 0, m.getWidth(), m.getHeight());
        }

        g.translate(0, -dy);

        g.translate(origin.getX(), origin.getY());
        g.scale(scale, scale);

        getGraph().paintClipped(g);

        g.dispose();
    }

    /** {@inheritDoc} */
    public void preview(final JaxoPreview p, final boolean sameWindow) {
        final Color oldBackground = background;
        final boolean oldTranslucent = translucent;

        try {
            if (translucent) {
                translucent = false;
                background = Color.white;
            }

            if (!sameWindow || (previewFrame == null)) {
                previewFrame = new JFrame();
            }

            p.setTitle(LANGUAGE.message("preview%0Title."
                    + getFileExtension(), getShortGraphName()));
            p.showComponent(
                    new JLabel(new ImageIcon(newBufferedImage())),
                    previewFrame);
        } finally {
            translucent = oldTranslucent;
            background = oldBackground;
        }
    }

    /** {@inheritDoc} */
    public void commitConfiguration() {
        setResolution(Float.parseFloat(resolutionText.getText()));
        background = backgroundButton.getColor();
    }

    /** {@inheritDoc}
     * @return A panel to configure background color,
     * antialiasing and resolution.
     */
    public JComponent getConfigurationPanel() {
        if (panel == null) {
            panel = new JPanel(new GridBagLayout(), false);

            final GridBagConstraints labels = new GridBagConstraints();
            final GridBagConstraints components = new GridBagConstraints();

            labels.anchor = GridBagConstraints.LINE_END;
            labels.gridx = 0;
            labels.gridy = 0;
            components.anchor = GridBagConstraints.LINE_START;
            components.gridx = 1;
            components.gridy = 0;
            components.weightx = 1;

            backgroundButton = new JaxoColorButton(background);
            backgroundButton.setChooserMode(JaxoColor.ALL_COLORS_MODE);
            backgroundButton.setChooser(new JaxoColorChooser(backgroundButton));

            bgColorLabel = new JLabel();
            panel.add(bgColorLabel, labels);
            panel.add(backgroundButton, components);
            labels.gridy++;
            components.gridy++;

            final JCheckBox noBackgroundPainting = new JCheckBox();
            noBackgroundPainting.setSelected(true);
            noBackgroundPaintingLabel = new JLabel();
            if (canBeTranslucent()) {
                panel.add(noBackgroundPaintingLabel, labels);
                panel.add(noBackgroundPainting, components);
            }
            labels.gridy++;
            components.gridy++;

            noBackgroundPainting.addActionListener(new ActionListener() {
                    public void actionPerformed(final ActionEvent e) {
                        JaxoExportImg.this.translucent =
                            noBackgroundPainting.isSelected();
                    }
                });

            final JCheckBox antialiasingBox = new JCheckBox();

            antialiasLabel = new JLabel();
            panel.add(antialiasLabel, labels);
            panel.add(antialiasingBox, components);
            labels.gridy++;
            components.gridy++;

            antialiasingBox.addActionListener(new ActionListener() {
                    public void actionPerformed(final ActionEvent e) {
                        setAntialiasing(antialiasingBox.isSelected());
                    }
                });

            resolutionLabel = new JLabel();
            panel.add(resolutionLabel, labels);

            resolutionText = new JTextField(String.valueOf(resolution), 8);

            panel.add(resolutionText, components);

            labels.gridy++;
            components.gridy++;
        }

        updateLanguage();

        return panel;
    }

    /** {@inheritDoc} */
    public final void updateLanguage() {
        if (bgColorLabel != null) {
            bgColorLabel.setText(LANGUAGE.label("background"));
            noBackgroundPaintingLabel.setText(LANGUAGE.label("translucent"));
            antialiasLabel.setText(LANGUAGE.label("antialiasing"));
            resolutionLabel.setText(LANGUAGE.label("resolution"));
        }
    }

    // Image which produces slices of the graph image on demand
    // to reduce memory usage.
    private class ExportImage implements RenderedImage {
        private final BufferedImage prototype;
        private int y;
        private final int height;
        private BufferedImage partialImage;
        private BufferedImage completeImage;

        ExportImage(final BufferedImage proto) {
            this.prototype = proto;

            this.height = 128; // Fixed height for slices
        }

        public Vector<RenderedImage> getSources() {
            return null;
        }

        public Object getProperty(final String name) {
            return Image.UndefinedProperty;
        }

        public String[] getPropertyNames() {
            return null;
        }

        public ColorModel getColorModel() {
            return prototype.getColorModel();
        }

        public SampleModel getSampleModel() {
            return prototype.getSampleModel();
        }

        public int getWidth() {
            return size.width;
        }

        public int getHeight() {
            return size.height;
        }

        public int getMinX() {
            return 0;
        }

        public int getMinY() {
            return 0;
        }

        public int getNumXTiles() {
            return 1;
        }

        public int getNumYTiles() {
            return 1;
        }

        public int getMinTileX() {
            return 0;
        }

        public int getMinTileY() {
            return 0;
        }

        public int getTileWidth() {
            return getWidth();
        }

        public int getTileHeight() {
            return getHeight();
        }

        public int getTileGridXOffset() {
            return 0;
        }

        public int getTileGridYOffset() {
            return 0;
        }

        public Raster getTile(final int tileX, final int tileY) {
            return completeImage().getTile(tileX, tileY);
        }

        public Raster getData() {
            return completeImage().getData();
        }

        // Standard PNG writing seems to request the data in complete lines
        // (r.width == width, r.height = 1)
        public Raster getData(final Rectangle r) {
            if (completeImage != null) {
                return completeImage.getData(r);
            }

            // Could improve
            if (r.height > height) {
                return completeImage().getData(r);
            }

            if (partialImage == null) {
                partialImage =
                    new BufferedImage(size.width, height, prototype.getType());
                y = r.y;
                paintToImage(partialImage, y);
            } else if (!((y <= r.y) && ((y + height) >= (r.y + r.height)))) {
                y = r.y;
                paintToImage(partialImage, y);
            }

            final Raster result =
                partialImage.getData(new Rectangle(r.x, r.y - y, r.width,
                        r.height));

            // This is weird - is it not possible easier?
            return new Raster(result.getSampleModel(), result.getDataBuffer(),
                r, new Point(0, result.getSampleModelTranslateY() + y), null) {
                };
        }

        // Could improve - but is probably not used here
        public WritableRaster copyData(final WritableRaster r) {
            return completeImage().copyData(r);
        }

        private BufferedImage completeImage() {
            if (completeImage == null) {
                completeImage =
                    new BufferedImage(size.width, size.height,
                        prototype.getType());

                paintToImage(completeImage, 0);
            }

            return completeImage;
        }
    }
}
