/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: LayoutImage.java
 *
 * Copyright (c) 2009, Static Free Software. All rights reserved.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.sun.electric.tool.user.dialogs;

import com.sun.electric.database.EditingPreferences;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.prototype.NodeProto;
import com.sun.electric.database.text.TextUtils;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.technology.PrimitiveNode;
import com.sun.electric.technology.Technology;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.JobException;
import com.sun.electric.tool.io.FileType;
import com.sun.electric.tool.user.User;
import com.sun.electric.tool.user.ui.WindowFrame;

import javax.imageio.stream.ImageInputStream;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.swing.*;
import java.awt.Canvas;
import java.awt.Frame;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.Toolkit;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.awt.image.Raster;
import java.util.Iterator;
import java.io.File;

/**
 * Class to handle the "Layout Image" dialog.  There is still a ton of
 * overlap left in here from LayoutText.java; at some point I will
 * either merge the two or else remove that overlap.  In particular,
 * LayoutText could benefit from "backporting" the new support for
 * grayscale images (which would give us antialiased layout text!)
 *
 * @author Adam Megacz <adam.megacz@sun.com>, derived from LayoutText.java
 */
public class LayoutImage extends EDialog {

    /** Creates new form LayoutImage */
    public LayoutImage(Frame parent) {
        super(parent, true);
        initComponents();

        // make all text fields select-all when entered
        EDialog.makeTextFieldSelectAllOnTab(largestDotWidth);
        EDialog.makeTextFieldSelectAllOnTab(smallestDotWidth);
        EDialog.makeTextFieldSelectAllOnTab(minimumGutter);
        EDialog.makeTextFieldSelectAllOnTab(fileName);

        largestDotWidth.setText("32");
        smallestDotWidth.setText("22");
        minimumGutter.setText("16");
        monochrome.setSelected(false);
        reverseVideo.setSelected(false);

        Technology tech = Technology.getCurrent();
        for(Iterator<PrimitiveNode> it = tech.getNodes(); it.hasNext(); )
        {
            PrimitiveNode np = it.next();
            if (np.getFunction() == PrimitiveNode.Function.NODE)
                textLayer.addItem(np.getName());
        }

        finishInitialization();
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {
        java.awt.GridBagConstraints gridBagConstraints;

        cancel = new javax.swing.JButton();
        ok = new javax.swing.JButton();
        jLabel1 = new javax.swing.JLabel();
        smallestDotWidth = new javax.swing.JTextField();
        jLabel2 = new javax.swing.JLabel();
        largestDotWidth = new javax.swing.JTextField();
        jLabel3 = new javax.swing.JLabel();
        minimumGutter = new javax.swing.JTextField();
        jLabel5 = new javax.swing.JLabel();
        textLayer = new javax.swing.JComboBox();
        reverseVideo = new javax.swing.JCheckBox();
        monochrome = new javax.swing.JCheckBox();
        fileName = new javax.swing.JTextField();
        jLabel4 = new javax.swing.JLabel();
        openFileButton = new javax.swing.JButton();

        setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
        setTitle("Layout Image");
        getContentPane().setLayout(new java.awt.GridBagLayout());

        cancel.setText("Cancel");
        cancel.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                cancel(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 9;
        gridBagConstraints.insets = new java.awt.Insets(4, 4, 4, 4);
        getContentPane().add(cancel, gridBagConstraints);

        ok.setText("OK");
        ok.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                ok(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 9;
        gridBagConstraints.insets = new java.awt.Insets(4, 4, 4, 4);
        getContentPane().add(ok, gridBagConstraints);

        jLabel1.setText("Width of smallest dot:");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.insets = new java.awt.Insets(4, 4, 4, 4);
        getContentPane().add(jLabel1, gridBagConstraints);

        smallestDotWidth.setColumns(8);
        smallestDotWidth.setText(" ");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.insets = new java.awt.Insets(4, 4, 4, 4);
        getContentPane().add(smallestDotWidth, gridBagConstraints);

        jLabel2.setText("Width of largest dot:");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 3;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.insets = new java.awt.Insets(4, 4, 4, 4);
        getContentPane().add(jLabel2, gridBagConstraints);

        largestDotWidth.setColumns(8);
        largestDotWidth.setText(" ");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 3;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.insets = new java.awt.Insets(4, 4, 4, 4);
        getContentPane().add(largestDotWidth, gridBagConstraints);

        jLabel3.setText("Minimum gutter between dots:");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 4;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.insets = new java.awt.Insets(4, 4, 4, 4);
        getContentPane().add(jLabel3, gridBagConstraints);

        minimumGutter.setColumns(8);
        minimumGutter.setText(" ");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 4;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.insets = new java.awt.Insets(4, 4, 4, 4);
        getContentPane().add(minimumGutter, gridBagConstraints);

        jLabel5.setText("Layer:");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 6;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.insets = new java.awt.Insets(4, 4, 4, 4);
        getContentPane().add(jLabel5, gridBagConstraints);
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 6;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.insets = new java.awt.Insets(4, 4, 4, 4);
        getContentPane().add(textLayer, gridBagConstraints);

        reverseVideo.setText("Reverse-video");
        reverseVideo.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 0, 0, 0));
        reverseVideo.setMargin(new java.awt.Insets(0, 0, 0, 0));
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 7;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.insets = new java.awt.Insets(4, 4, 4, 4);
        getContentPane().add(reverseVideo, gridBagConstraints);

        monochrome.setText("Monochrome");
        monochrome.setBorder(javax.swing.BorderFactory.createEmptyBorder(1, 1, 1, 1));
        monochrome.setMargin(new java.awt.Insets(0, 0, 0, 0));
        monochrome.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                monochromeActionPerford(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 8;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.insets = new java.awt.Insets(4, 4, 4, 4);
        getContentPane().add(monochrome, gridBagConstraints);
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.insets = new java.awt.Insets(4, 4, 1, 4);
        getContentPane().add(fileName, gridBagConstraints);

        jLabel4.setText("Image file name:");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.insets = new java.awt.Insets(4, 4, 4, 4);
        getContentPane().add(jLabel4, gridBagConstraints);

        openFileButton.setText("Load");
        openFileButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                openFileButtonActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.insets = new java.awt.Insets(1, 4, 12, 4);
        getContentPane().add(openFileButton, gridBagConstraints);

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void cancel(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancel
		setVisible(false);
		dispose();
    }//GEN-LAST:event_cancel

    private void ok(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_ok

        final Cell curCell = WindowFrame.needCurCell();
        if (curCell == null) return;

        final Technology tech = Technology.getCurrent();
        final int minimumGutter = (int)TextUtils.atofDistance(this.minimumGutter.getText(), tech);
        final int largestDotWidth = (int)TextUtils.atofDistance(this.largestDotWidth.getText(), tech);
        final int smallestDotWidth = (int)TextUtils.atofDistance(this.smallestDotWidth.getText(), tech);
//        final boolean reverseVideo = this.reverseVideo.isSelected();
        final String layer = (String)textLayer.getSelectedItem();

        // determine the primitive to use for the layout
        final NodeProto primNode = Technology.getCurrent().findNodeProto(layer);
        if (primNode == null) {
            System.out.println("Cannot find " + layer + " primitive");
            return;
        }

        try {
            // Checking if the file is actually a JPEG file
            File fileURL = new File(fileName.getText());
            ImageInputStream iis = ImageIO.createImageInputStream(fileURL);
            if (iis == null)
            {
            	JOptionPane.showMessageDialog(null, "Can't load file '" + fileName.getText() + "'", "Erorr in LoadImage",
                        JOptionPane.ERROR_MESSAGE);
                    return;
            }
            Iterator<ImageReader> readers = ImageIO.getImageReaders(iis);
            boolean validFormat = false;
            while (readers.hasNext())
            {
                ImageReader read = readers.next();
                // Another way to detect this could be "read instanceof JPEGImageReader
                String format = read.getFormatName().toLowerCase();
                // so far jpeg and png are the only valid formats tested.
                validFormat = format.equals("jpeg") || format.equals("png");
                if (validFormat)
                    break;
            }
            if (!validFormat)
            {
                String msg = "'" + fileName.getText() + "' is not a JPEG file";
                JOptionPane.showMessageDialog(null, msg, "Erorr in LoadImage",
                    JOptionPane.ERROR_MESSAGE);
                return;
            }

            Image img = Toolkit.getDefaultToolkit().createImage(fileName.getText());

            // dumb hack to force AWT to load the image
            MediaTracker mediatracker = new MediaTracker(new Canvas());
            mediatracker.addImage(img, 1);
            try { mediatracker.waitForAll(); } catch (InterruptedException e) { }
            mediatracker.removeImage(img);

            BufferedImage textImage = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_BYTE_GRAY);
            textImage.getGraphics().drawImage(img, 0, 0, null);
            Raster ras = textImage.getData();
            DataBufferByte dbb = (DataBufferByte)ras.getDataBuffer();

            setVisible(false);
            dispose();

            new LayoutImageJob(ras.getWidth(),
                               ras.getHeight(),
                               ras.getWidth()/2,
                               -ras.getHeight()/2,
                               dbb.getData(),
                               largestDotWidth+minimumGutter,
                               largestDotWidth,
                               smallestDotWidth,
                               primNode,
                               curCell);
        } catch (Exception e) { throw new RuntimeException(e); }
    }//GEN-LAST:event_ok

    private static class LayoutImageJob extends Job {
        int width;
        int height;
        int xOffset;
        int yOffset;
        byte[] samples;
        int scale;
        int largestDotWidth;
        int smallestDotWidth;
        NodeProto primNode;
        Cell curCell;
        public LayoutImageJob(int width, int height, int xOffset, int yOffset, byte[] samples, int scale, int largestDotWidth, int smallestDotWidth, NodeProto primNode, Cell curCell) {
            super("Create Layout Image", User.getUserTool(), Job.Type.CHANGE, null, null, Job.Priority.USER);
            this.xOffset = xOffset;
            this.yOffset = yOffset;
            this.width = width;
            this.height = height;
            this.samples = samples;
            this.scale = scale;
            this.largestDotWidth = largestDotWidth;
            this.smallestDotWidth = smallestDotWidth;
            this.primNode = primNode;
            this.curCell = curCell;
            startJob();
        }
        @Override
        public boolean doIt() throws JobException {
            EditingPreferences ep = getEditingPreferences();
            int samp = 0;
            for(int y=0; y<height; y++) {
                for(int x=0; x<width; x++) {
                    int w = ((int)(((255-(samples[samp++] & 0xff)) / 255f) *
                                         ((largestDotWidth-smallestDotWidth))))+smallestDotWidth;
                    if (w == smallestDotWidth) continue;
                    Point2D center = new Point2D.Double((x-xOffset)*scale, -(y+yOffset)*scale);
                    NodeInst.newInst(primNode, ep, center, w, w, curCell);
                }
            }
            return true;
        }
    }


    private void monochromeActionPerford(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_monochromeActionPerford
        // TODO add your handling code here:
    }//GEN-LAST:event_monochromeActionPerford

	private void openFileButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_openFileButtonActionPerformed
		String fn = OpenFile.chooseInputFile(FileType.ANY, null, null);
		if (fn == null) return;
		fileName.setText(fn);
	}//GEN-LAST:event_openFileButtonActionPerformed

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton cancel;
    private javax.swing.JTextField fileName;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JLabel jLabel2;
    private javax.swing.JLabel jLabel3;
    private javax.swing.JLabel jLabel4;
    private javax.swing.JLabel jLabel5;
    private javax.swing.JTextField largestDotWidth;
    private javax.swing.JTextField minimumGutter;
    private javax.swing.JCheckBox monochrome;
    private javax.swing.JButton ok;
    private javax.swing.JButton openFileButton;
    private javax.swing.JCheckBox reverseVideo;
    private javax.swing.JTextField smallestDotWidth;
    private javax.swing.JComboBox textLayer;
    // End of variables declaration//GEN-END:variables

}
