/* Copyright (c) 2010, Carl Burch. License information is located in the
 * com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */

package com.cburch.logisim.gui.main;

import com.cburch.logisim.circuit.Circuit;
import com.cburch.logisim.circuit.CircuitState;
import com.cburch.logisim.comp.ComponentDrawContext;
import com.cburch.logisim.data.Bounds;
import com.cburch.logisim.file.Loader;
import com.cburch.logisim.proj.Project;
import com.cburch.logisim.util.GifEncoder;
import com.cburch.logisim.util.StringGetter;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.List;

import javax.imageio.ImageIO;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.ProgressMonitor;
import javax.swing.SwingConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.filechooser.FileFilter;

class ExportImage {
	private static final int SLIDER_DIVISIONS = 6;

	private static final int FORMAT_GIF = 0;
	private static final int FORMAT_PNG = 1;
	private static final int FORMAT_JPG = 2;
	
	private static final int BORDER_SIZE = 5;

	private ExportImage() { }

	static void doExport(Project proj) {
		// First display circuit/parameter selection dialog
		Frame frame = proj.getFrame();
		CircuitJList list = new CircuitJList(proj, true);
		if (list.getModel().getSize() == 0) {
			JOptionPane.showMessageDialog(proj.getFrame(),
					Strings.get("exportEmptyCircuitsMessage"),
					Strings.get("exportEmptyCircuitsTitle"),
					JOptionPane.YES_NO_OPTION);
			return;
		}
		OptionsPanel options = new OptionsPanel(list);
		int action = JOptionPane.showConfirmDialog(frame,
				options, Strings.get("exportImageSelect"),
				JOptionPane.OK_CANCEL_OPTION,
				JOptionPane.QUESTION_MESSAGE);
		if (action != JOptionPane.OK_OPTION) return;
		List<Circuit> circuits = list.getSelectedCircuits();
		double scale = options.getScale();
		boolean printerView = options.getPrinterView();
		if (circuits.isEmpty()) return;
		
		ImageFileFilter filter;
		int fmt = options.getImageFormat();
		switch (options.getImageFormat()) {
		case FORMAT_GIF:
			filter = new ImageFileFilter(fmt, Strings.getter("exportGifFilter"),
				new String[] { "gif" });
			break;
		case FORMAT_PNG:
			filter = new ImageFileFilter(fmt, Strings.getter("exportPngFilter"),
				new String[] { "png" });
			break;
		case FORMAT_JPG:
			filter = new ImageFileFilter(fmt, Strings.getter("exportJpgFilter"),
				new String[] { "jpg", "jpeg", "jpe", "jfi", "jfif", "jfi" });
			break;
		default:
			System.err.println("unexpected format; aborted"); //OK
			return;
		}
		
		// Then display file chooser
		Loader loader = proj.getLogisimFile().getLoader();
		JFileChooser chooser = loader.createChooser();
		if (circuits.size() > 1) {
			chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
			chooser.setDialogTitle(Strings.get("exportImageDirectorySelect"));
		} else {
			chooser.setFileFilter(filter);
			chooser.setDialogTitle(Strings.get("exportImageFileSelect"));
		}
		int returnVal = chooser.showDialog(frame, Strings.get("exportImageButton"));
		if (returnVal != JFileChooser.APPROVE_OPTION) return;

		// Determine whether destination is valid
		File dest = chooser.getSelectedFile();
		chooser.setCurrentDirectory(dest.isDirectory() ? dest : dest.getParentFile());
		if (dest.exists()) {
			if (!dest.isDirectory()) {
				int confirm = JOptionPane.showConfirmDialog(proj.getFrame(),
					Strings.get("confirmOverwriteMessage"),
					Strings.get("confirmOverwriteTitle"),
					JOptionPane.YES_NO_OPTION);
				if (confirm != JOptionPane.YES_OPTION) return;
			}
		} else {
			if (circuits.size() > 1) {
				boolean created = dest.mkdir();
				if (!created) {
					JOptionPane.showMessageDialog(proj.getFrame(),
							Strings.get("exportNewDirectoryErrorMessage"),
							Strings.get("exportNewDirectoryErrorTitle"),
							JOptionPane.YES_NO_OPTION);
					return;
				}
			}
		}

		// Create the progress monitor
		ProgressMonitor monitor = new ProgressMonitor(frame,
				Strings.get("exportImageProgress"),
				null,
				0, 10000);
		monitor.setMillisToDecideToPopup(100);
		monitor.setMillisToPopup(200);
		monitor.setProgress(0);

		// And start a thread to actually perform the operation
		// (This is run in a thread so that Swing will update the
		// monitor.)
		new ExportThread(frame, frame.getCanvas(), dest, filter,
				circuits, scale, printerView, monitor).start();

	}

	private static class OptionsPanel extends JPanel implements ChangeListener {
		JSlider slider;
		JLabel curScale;
		JCheckBox printerView;
		JRadioButton formatPng;
		JRadioButton formatGif;
		JRadioButton formatJpg;
		GridBagLayout gridbag;
		GridBagConstraints gbc;
		Dimension curScaleDim;

		OptionsPanel(JList list) {
			// set up components
			formatPng = new JRadioButton("PNG");
			formatGif = new JRadioButton("GIF");
			formatJpg = new JRadioButton("JPEG");
			ButtonGroup bgroup = new ButtonGroup();
			bgroup.add(formatPng);
			bgroup.add(formatGif);
			bgroup.add(formatJpg);
			formatPng.setSelected(true);

			slider = new JSlider(JSlider.HORIZONTAL,
					-3 * SLIDER_DIVISIONS, 3 * SLIDER_DIVISIONS, 0);
			slider.setMajorTickSpacing(10);
			slider.addChangeListener(this);
			curScale = new JLabel("222%");
			curScale.setHorizontalAlignment(SwingConstants.RIGHT);
			curScale.setVerticalAlignment(SwingConstants.CENTER);
			curScaleDim = new Dimension(curScale.getPreferredSize());
			curScaleDim.height = Math.max(curScaleDim.height,
					slider.getPreferredSize().height);
			stateChanged(null);

			printerView = new JCheckBox();
			printerView.setSelected(true);

			// set up panel
			gridbag = new GridBagLayout();
			gbc = new GridBagConstraints();
			setLayout(gridbag);

			// now add components into panel
			gbc.gridy = 0;
			gbc.gridx = GridBagConstraints.RELATIVE;
			gbc.anchor = GridBagConstraints.NORTHWEST;
			gbc.insets = new Insets(5, 0, 5, 0);
			gbc.fill = GridBagConstraints.NONE;
			addGb(new JLabel(Strings.get("labelCircuits") + " "));
			gbc.fill = GridBagConstraints.HORIZONTAL;
			addGb(new JScrollPane(list));
			gbc.fill = GridBagConstraints.NONE;
			
			gbc.gridy++;
			addGb(new JLabel(Strings.get("labelImageFormat") + " "));
			Box formatsPanel = new Box(BoxLayout.Y_AXIS);
			formatsPanel.add(formatPng);
			formatsPanel.add(formatGif);
			formatsPanel.add(formatJpg);
			addGb(formatsPanel);
			
			gbc.gridy++;
			addGb(new JLabel(Strings.get("labelScale") + " "));
			addGb(slider);
			addGb(curScale);
			
			gbc.gridy++;
			addGb(new JLabel(Strings.get("labelPrinterView") + " "));
			addGb(printerView);
		}
		
		private void addGb(JComponent comp) {
			gridbag.setConstraints(comp, gbc);
			add(comp);
		}

		double getScale() {
			return Math.pow(2.0, (double) slider.getValue() / SLIDER_DIVISIONS);
		}
		
		boolean getPrinterView() { return printerView.isSelected(); }
		
		int getImageFormat() {
			if (formatGif.isSelected()) return FORMAT_GIF;
			if (formatJpg.isSelected()) return FORMAT_JPG;
			return FORMAT_PNG;
		}

		public void stateChanged(ChangeEvent e) {
			double scale = getScale();
			curScale.setText((int) Math.round(100.0 * scale) + "%");
			if (curScaleDim != null) curScale.setPreferredSize(curScaleDim);
		}
	}
	
	private static class ImageFileFilter extends FileFilter {
		private int type;
		private String[] extensions;
		private StringGetter desc;
		
		private ImageFileFilter(int type, StringGetter desc, String[] exts) {
			this.type = type;
			this.desc = desc;
			extensions = new String[exts.length];
			for (int i = 0; i < exts.length; i++) {
				extensions[i] = "." + exts[i].toLowerCase();    
			}
		}
		
		@Override
		public boolean accept(File f) {
			String name = f.getName().toLowerCase();
			for (int i = 0; i < extensions.length; i++) {
				if (name.endsWith(extensions[i])) return true;
			}
			return f.isDirectory();
		}

		@Override
		public String getDescription() {
			return desc.get();
		}
	}

	private static class ExportThread extends Thread {
		Frame frame;
		Canvas canvas;
		File dest;
		ImageFileFilter filter;
		List<Circuit> circuits;
		double scale;
		boolean printerView;
		ProgressMonitor monitor;

		ExportThread(Frame frame, Canvas canvas, File dest, ImageFileFilter f,
				List<Circuit> circuits, double scale, boolean printerView,
				ProgressMonitor monitor) {
			this.frame = frame;
			this.canvas = canvas;
			this.dest = dest;
			this.filter = f;
			this.circuits = circuits;
			this.scale = scale;
			this.printerView = printerView;
			this.monitor = monitor;
		}

		@Override
		public void run() {
			for (Circuit circ : circuits) {
				export(circ);
			}
		}
		
		private void export(Circuit circuit) {
			Bounds bds = circuit.getBounds(canvas.getGraphics())
				.expand(BORDER_SIZE);
			int width = (int) Math.round(bds.getWidth() * scale);
			int height = (int) Math.round(bds.getHeight() * scale);
			BufferedImage img = new BufferedImage(width, height,
					BufferedImage.TYPE_INT_RGB);
			Graphics base = img.getGraphics();
			Graphics g = base.create();
			g.setColor(Color.white);
			g.fillRect(0, 0, width, height);
			g.setColor(Color.black);
			if (g instanceof Graphics2D) {
				((Graphics2D) g).scale(scale, scale);
				((Graphics2D) g).translate(-bds.getX(), -bds.getY());
			} else {
				JOptionPane.showMessageDialog(frame,
						Strings.get("couldNotCreateImage"));
				monitor.close();
			}

			CircuitState circuitState = canvas.getProject().getCircuitState(circuit);
			ComponentDrawContext context = new ComponentDrawContext(canvas,
					circuit, circuitState, base, g, printerView);
			circuit.draw(context, null);

			File where;
			if (dest.isDirectory()) {
				where = new File(dest, circuit.getName() + filter.extensions[0]);
			} else if (filter.accept(dest)) {
				where = dest;
			} else {
				String newName = dest.getName() + filter.extensions[0];
				where = new File(dest.getParentFile(), newName);
			}
			try {
				switch (filter.type) {
				case FORMAT_GIF: GifEncoder.toFile(img, where, monitor); break;
				case FORMAT_PNG: ImageIO.write(img, "PNG", where); break;
				case FORMAT_JPG: ImageIO.write(img, "JPEG", where); break;
				}
			} catch (Exception e) {
				JOptionPane.showMessageDialog(frame,
						Strings.get("couldNotCreateFile"));
				monitor.close();
				return;
			}
			g.dispose();
			monitor.close();
		}
	}
}
