/**
 * Copyright (C) 2006, Laboratorio di Valutazione delle Prestazioni - Politecnico di Milano

 * This program 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 2 of the License, or
 * (at your option) any later version.

 * This program 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, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
package battery.graph;

import ptolemy.plot.Plot;

import javax.swing.*;
import javax.imageio.ImageIO;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.awt.*;
import java.util.Vector;
import java.util.Iterator;

import battery.utility.*;
import dotplots.*;
import endu.Main;

/**
 * <p>
 * Title: What-If analysis Plot
 * </p>
 * <p>
 * Description: This class provides a customization of Ptolemy Plot object used
 * for JMVA what-if analysis.
 * </p>
 * 
 * @author Bertoli Marco Date: 1-giu-2006 Time: 15.44.43
 */
public class WhatIfPlot extends Plot {
	// Zoom factor for zoom-in / zoom-out
	public static final double PLOT_ZOOM_FACTOR = 0.5;
	// Values for x-axis
	private long[] xAxis;
	// Popup menu
	private PlotPopupMenu popup = new PlotPopupMenu();
	// Rescale listeners
	private Vector listeners = new Vector();

	public WhatIfPlot(long[] xAxis) {
		super();
		this.xAxis = xAxis;
		// Adds popup menu
		this.addMouseListener(new MouseAdapter() {
			/**
			 * Invoked when the mouse has been clicked on a component.
			 */
			public void mouseClicked(MouseEvent e) {
				if (e.getButton() == MouseEvent.BUTTON3)
					popup.show(WhatIfPlot.this, e.getX(), e.getY());
			}
		});
	}

	/**
	 * Draw given values on selected line with selected caption
	 * 
	 * @param num
	 *            number of dataset to be drawn
	 * @param values
	 *            values to be drawn
	 * @throws IllegalArgumentException
	 *             if dataset size does not match with x-axis values
	 */
	public void draw(int num, long[] values) throws IllegalArgumentException {
		System.out.println("values.length: " + values.length);
		System.out.println("xAxis.length: " + xAxis.length);

		if (values.length != xAxis.length)
			throw new IllegalArgumentException(
					"Length of provided dataset does not match x-axis points.");
		// Clears target dataset
		clear(num);
		// Adds values
		for (int i = 0; i < xAxis.length; i++)
			addPoint(num, xAxis[i], values[i], true);
	}

	// /***************************************************
	
	public void drawPoints(int num, int index) throws IllegalArgumentException {
		//clear(num);
		
		for (int i = 0; i < Points.row; i++) {
			//System.out.println("Draw points: =========: " +	i+" "+ Points.x[i]);
			//clear(num + i);
			
			addPoint(num + i*11, Points.x[i] - 0.05, Points.y[i] - 0.05, true);
			addPoint(num + i*11, Points.x[i] - 0.05, Points.y[i] + 0.05, true);
			addPoint(num + i*11, Points.x[i] + 0.05, Points.y[i] + 0.05, true);
			addPoint(num + i*11, Points.x[i] + 0.05, Points.y[i] - 0.05, true);
			addPoint(num + i*11, Points.x[i] - 0.05, Points.y[i] - 0.05, true);
			addPoint(num + i*11, Points.x[i] + 0.05, Points.y[i] + 0.05, true);
			addPoint(num + i*11, Points.x[i] - 0.05, Points.y[i] + 0.05, true);
			addPoint(num + i*11, Points.x[i] + 0.05, Points.y[i] - 0.05, true);
		}
	}

	public void drawEdges(int num, int index) throws IllegalArgumentException {
		for (int i = 0; i < Points.row; i++) {
			int s = Points.S[i];
			addPoint(num + 33 + i*11, Points.x[Points.P[i]],
					Points.y[Points.P[i]], true);
			addPoint(num + 33 + i*11, Points.x[s], Points.y[s], true);
		}

	}

//***************************************************
	public void drawPrimSimulation(int num, int index)
			throws IllegalArgumentException {
		int l = GraphPanel.times;
		//System.out.println("Times: " + l);
		for (int i = 1; i <= l; i++) {
			int s = Points.S[i];
			addPoint(num + 33 + i*11, Points.x[Points.P[i]],
					Points.y[Points.P[i]], true);
			addPoint(num + 33 + i*11, Points.x[s], Points.y[s], true);
		}
	}

	public void drawStocks(int num, int index) throws IllegalArgumentException {
		//clear(num);
		for (int row = 0; row < Stock.row; row++) {
			addPoint(num, row, Stock.stock[index][row], true);
		}
	}
	public void drawStocks2(int num, int index, int lastDate) throws IllegalArgumentException {
		//clear(num);
		for (int row = 0; row <= lastDate; row++) {
			addPoint(num, row+0.04, Stock.stock[index][row], true);
		}
	}


	public void drawBuyDate(int num, int stockId, int BuyDate) throws IllegalArgumentException {
		addPoint(num , BuyDate - 0.05, Stock.stock[stockId][BuyDate] - 0.05, true);
		addPoint(num , BuyDate - 0.05, Stock.stock[stockId][BuyDate] + 0.05, true);
		addPoint(num , BuyDate + 0.05, Stock.stock[stockId][BuyDate] + 0.05, true);
		addPoint(num , BuyDate + 0.05, Stock.stock[stockId][BuyDate] - 0.05, true);
		addPoint(num , BuyDate - 0.05, Stock.stock[stockId][BuyDate] - 0.05, true);
		addPoint(num , BuyDate + 0.05, Stock.stock[stockId][BuyDate] + 0.05, true);
		addPoint(num , BuyDate - 0.05, Stock.stock[stockId][BuyDate] + 0.05, true);
		addPoint(num , BuyDate + 0.05, Stock.stock[stockId][BuyDate] - 0.05, true);		
	}
	public void drawSellDate(int num, int stockId, int SellDate) throws IllegalArgumentException {
		addPoint(num , SellDate - 0.05, Stock.stock[stockId][SellDate] - 0.05, true);
		addPoint(num , SellDate - 0.05, Stock.stock[stockId][SellDate] + 0.05, true);
		addPoint(num , SellDate + 0.05, Stock.stock[stockId][SellDate] + 0.05, true);
		addPoint(num , SellDate + 0.05, Stock.stock[stockId][SellDate] - 0.05, true);
		addPoint(num , SellDate - 0.05, Stock.stock[stockId][SellDate] - 0.05, true);
		addPoint(num , SellDate + 0.05, Stock.stock[stockId][SellDate] + 0.05, true);
		addPoint(num , SellDate - 0.05, Stock.stock[stockId][SellDate] + 0.05, true);
		addPoint(num , SellDate + 0.05, Stock.stock[stockId][SellDate] - 0.05, true);		
	}
	public void drawMinStock(int num, int stockId, int date) throws IllegalArgumentException {
		addPoint(num , date - 0.1, Stock.stock[stockId][date] - 0.1, true);
		addPoint(num , date - 0.1, Stock.stock[stockId][date] + 0.1, true);
		addPoint(num , date + 0.1, Stock.stock[stockId][date] + 0.1, true);
		addPoint(num , date + 0.1, Stock.stock[stockId][date] - 0.1, true);
		addPoint(num , date - 0.1, Stock.stock[stockId][date] - 0.1, true);
	}

	
//***************************************************
	public void drawCapacity(int num, int index)
			throws IllegalArgumentException {
		clear(num);
		clear(num + 11);
		for (int i = 0; i < Battery.lenght[index]; i++)
			addPoint(num, Battery.datetime2[index][i], Battery.capacity[index],
					true);
	}

	public void drawAverage(int num, int index) throws IllegalArgumentException {
		clear(num);
		for (int i = 0; i < 1440; i++)
			addPoint(num, i, AverageBattery.level[index][i], true);
	}


	public void drawAverageCluster(int num, int clusterIndex)
			throws IllegalArgumentException {
		clear(num);
		if (clusterIndex == 0) {
			for (int index = 0; index < GraphPanel.jca.getKValue(); index++) {
				for (int i = 0; i < 1440; i++) {
					addPoint(num + index * 11, i,
							GraphPanel.jca.getCentroidData(GraphPanel.jca
									.getLoopsNumber() - 1, index)[i], true);

				}
			}
		}

		for (int index = 0; index < 1440; index++) {
			addPoint(3, index, AverageBattery.level[23][index], true);
		}
		int index = clusterIndex - 1;
		int l = GraphPanel.jca.getLoopsNumber() - 1;
		System.out.println("****************************l " + l);
		if (index >= 0 && index < GraphPanel.jca.getKValue()) {
			int[] sensors = GraphPanel.jca.decode(l, index);
			for (int s = 0; s < sensors.length; s++) {
				int indexS = Battery.getIndex(sensors[s]);
				for (int a = 0; a < 1440; a++) {
					addPoint(num + s * 11, a, AverageBattery.level[indexS][a],
							true);
				}
			}
		} else {
		}

	}

	public void drawAverageCluster2(int num, int clusterIndex)
			throws IllegalArgumentException {
		if (clusterIndex == 0) {
			for (int index = 0; index < GraphPanel.jca.getKValue(); index++) {
				for (int i = 0; i < 1440; i++) {
					// System.out.println(" loop:" +GraphPanel.loop);
					// clear(num + index * 11);
					addPoint(num + index * 11, i, GraphPanel.jca
							.getCentroidData(GraphPanel.times, index)[i], true);

				}
			}
		}
		int index = clusterIndex - 1;
		int l = GraphPanel.times;
		// System.out.println(" GraphPanel.jca.getKValue() "+
		// GraphPanel.jca.getKValue());
		// System.out.println(" index "+ index);

		if (index >= 0 && index < GraphPanel.jca.getKValue()) {
			int[] sensors = GraphPanel.jca.decode(l, index);
			for (int s = 0; s < sensors.length; s++) {
				int indexS = Battery.getIndex(sensors[s]);
				for (int a = 0; a < 1440; a++) {
					addPoint(num + s * 11, a, AverageBattery.level[indexS][a],
							true);
				}
			}

		} else {
			// System.out.println("****************************CLUSTER INDEX "
			// + clusterIndex);

		}
		// GraphPanel.times=0;
	}

	// --- Callbacks for rescale
	// --------------------------------------------------------------
	/**
	 * Adds a rescale listener that is notified each time graph is rescaled.
	 * 
	 * @param listener
	 *            listener to be added
	 */
	public void addRescaleListener(RescaleListener listener) {
		listeners.add(listener);
	}

	/**
	 * Removes a rescale listener
	 * 
	 * @param listener
	 *            listener to be removed
	 */
	public void removeRescaleListener(RescaleListener listener) {
		listeners.remove(listener);
	}

	/**
	 * Called each time a rescale event occurrs
	 */
	protected void fireRescaleEvent() {
		Iterator i = listeners.iterator();
		while (i.hasNext())
			((RescaleListener) i.next()).Rescaled();
	}

	/**
	 * Interface used for rescaling callbacks
	 */
	public interface RescaleListener {
		/**
		 * Called each time graph is rescaled
		 */
		public void Rescaled();
	}

	/**
	 * Overrides default method to add firing of rescale change events
	 */
	public synchronized void setXRange(double v, double v1) {
		super.setXRange(v, v1);
		fireRescaleEvent();
	}

	/**
	 * Overrides default method to add firing of rescale change events Avoid
	 * problems with machine precision on constant measures too.
	 */
	public synchronized void setYRange(double v, double v1) {
		// Avoid rescaling to a too small scale (for machine precision problems)
		if (Math.abs(v - v1) > 1e-8)
			super.setYRange(v, v1);
		else {
			double mean = (v + v1) / 2;
			super.setYRange(mean, mean);
		}
		fireRescaleEvent();
	}

	// ----------------------------------------------------------------------------------------

	// --- Methods for popup menu
	// -------------------------------------------------------------
	/**
	 * A simple JPopupMenu used to manage operations on plot. It gives the
	 * choice to zoom in and out on the plot, restore original view and save
	 * plot to images (in EPS or PNG format)
	 */
	protected class PlotPopupMenu extends JPopupMenu {
		public JMenuItem restore;
		public JMenuItem zoomIn;
		public JMenuItem zoomOut;
		public JMenuItem saveAs;

		public PlotPopupMenu() {
			restore = new JMenuItem("Original view");
			zoomIn = new JMenuItem("Zoom in");
			zoomOut = new JMenuItem("Zoom out");
			saveAs = new JMenuItem("Save as...");
			this.add(restore);
			this.add(zoomIn);
			this.add(zoomOut);
			this.addSeparator();
			this.add(saveAs);
			addListeners();
		}

		public void addListeners() {
			restore.addActionListener(new AbstractAction() {
				public void actionPerformed(ActionEvent e) {
					fillPlot();
				}
			});

			zoomIn.addActionListener(new AbstractAction() {
				public void actionPerformed(ActionEvent e) {
					// Gets maximum and minimum values
					double[] XBounds = getXAutoRange();
					double[] YBounds = getYAutoRange();

					double[] xRange = getXRange();
					double[] yRange = getYRange();
					double width = xRange[1] - xRange[0];
					double height = yRange[1] - yRange[0];
					double newWidth = (xRange[1] - xRange[0])
							* PLOT_ZOOM_FACTOR;
					double newHeight = (yRange[1] - yRange[0])
							* PLOT_ZOOM_FACTOR;
					double newXMin = xRange[0] + (width - newWidth) / 2;
					// The next 7 lines check that the new range is valid
					if (newXMin < XBounds[0])
						newXMin = XBounds[0];
					double newXMax = xRange[0] + (width - newWidth) / 2
							+ newWidth;
					if (newXMax > XBounds[1])
						newXMax = XBounds[1];
					double newYMin = yRange[0] + (height - newHeight) / 2;
					if (newYMin < YBounds[0])
						newYMin = YBounds[0];
					double newYMax = yRange[0] + (height - newHeight) / 2
							+ newHeight;
					if (newYMax > YBounds[1])
						newYMax = YBounds[1];
					setXRange(newXMin, newXMax);
					setYRange(newYMin, newYMax);
					WhatIfPlot.this.repaint();
				}
			});

			zoomOut.addActionListener(new AbstractAction() {
				public void actionPerformed(ActionEvent e) {
					// Gets maximum and minimum values
					double[] XBounds = getXAutoRange();
					double[] YBounds = getYAutoRange();

					double[] xRange = getXRange();
					double[] yRange = getYRange();
					double width = xRange[1] - xRange[0];
					double height = yRange[1] - yRange[0];
					double newWidth = (xRange[1] - xRange[0])
							/ PLOT_ZOOM_FACTOR;
					double newHeight = (yRange[1] - yRange[0])
							/ PLOT_ZOOM_FACTOR;
					double newXMin = xRange[0] + (width - newWidth) / 2;
					// The next 7 lines check that the new range is valid
					if (newXMin < XBounds[0])
						newXMin = XBounds[0];
					double newXMax = xRange[0] + (width - newWidth) / 2
							+ newWidth;
					if (newXMax > XBounds[1])
						newXMax = XBounds[1];
					double newYMin = yRange[0] + (height - newHeight) / 2;
					if (newYMin < YBounds[0])
						newYMin = YBounds[0];
					double newYMax = yRange[0] + (height - newHeight) / 2
							+ newHeight;
					if (newYMax > YBounds[1])
						newYMax = YBounds[1];
					setXRange(newXMin, newXMax);
					setYRange(newYMin, newYMax);
					WhatIfPlot.this.repaint();
				}
			});

			saveAs.addActionListener(new AbstractAction() {
				public void actionPerformed(ActionEvent e) {
					PlotImagesFileFilter PNGfilter = new PlotImagesFileFilter(
							".png", "Portable Network Graphics images");
					PlotImagesFileFilter EPSfilter = new PlotImagesFileFilter(
							".eps", "Encapsulated Post Script images");
					PlotImagesFileChooser fileChooser = new PlotImagesFileChooser(
							PNGfilter);
					fileChooser.setFileFilter(PNGfilter);
					fileChooser.addChoosableFileFilter(EPSfilter);
					int r = fileChooser.showSaveDialog(WhatIfPlot.this);
					if (r == JFileChooser.APPROVE_OPTION) {
						File file = fileChooser.getSelectedFile();
						if (fileChooser.getFileFilter().equals(EPSfilter)) {
							WhatIfPlot plot = WhatIfPlot.this;
							try {
								FileOutputStream fileStream = new FileOutputStream(
										file);
								plot.export(fileStream);
								fileStream.close();
							} catch (FileNotFoundException fnf) {
								JOptionPane.showMessageDialog(fileChooser,
										"File not found",
										"File output - Error",
										JOptionPane.ERROR_MESSAGE);
							} catch (IOException ioe) {
								JOptionPane.showMessageDialog(fileChooser,
										"I/O exception", "File output - Error",
										JOptionPane.ERROR_MESSAGE);
							}
						} else {
							WhatIfPlot plot = WhatIfPlot.this;
							BufferedImage image = plot.exportImage();
							try {
								BufferedImage originalImage = convertType(
										image, BufferedImage.TYPE_INT_RGB);
								ImageIO.write(originalImage, "png", file);
								ImageIO.createImageOutputStream(file).close();
							} catch (IOException fnf) {
								JOptionPane.showMessageDialog(null,
										"File not found");
							}
						}
					}
				}

			});
		}

		BufferedImage convertType(BufferedImage src, int targetType) {
			if (src.getType() == targetType)
				return src;
			BufferedImage tgt = new BufferedImage(src.getWidth(), src
					.getHeight(), targetType);
			Graphics2D g = tgt.createGraphics();
			g.drawRenderedImage(src, null);
			g.dispose();
			return tgt;
		}
	}

	/**
	 * Custom file chooser class
	 */
	protected static class PlotImagesFileChooser extends JFileChooser {
		protected PlotImagesFileFilter defaultFilter;

		/**
		 * Creates a File chooser in the appropriate directory user deafault.
		 * 
		 * @param defaultFilter
		 *            default file filter
		 */
		public PlotImagesFileChooser(PlotImagesFileFilter defaultFilter) {
			super(new File(System.getProperty("user.dir")));
			this.defaultFilter = defaultFilter;
		}

		/**
		 * Overrides default method to provide a warning if saving over an
		 * existing file
		 */
		public void approveSelection() {
			// Gets the choosed file name
			String name = getSelectedFile().getName();
			String parent = getSelectedFile().getParent();
			if (getDialogType() == OPEN_DIALOG) {
				super.approveSelection();
			}
			if (getDialogType() == SAVE_DIALOG) {
				PlotImagesFileFilter used = ((PlotImagesFileFilter) this
						.getFileFilter());
				if (!name.toLowerCase().endsWith(used.getExtension())) {
					name = name + used.getExtension();
					setSelectedFile(new File(parent, name));
				}
				if (getSelectedFile().exists()) {
					int resultValue = JOptionPane
							.showConfirmDialog(
									this,
									"<html>File <font color=#0000ff>"
											+ name
											+ "</font> already exists in this folder.<br>Do you want to replace it?</html>",
									"File save - Warning",
									JOptionPane.OK_CANCEL_OPTION,
									JOptionPane.WARNING_MESSAGE);
					if (resultValue == JOptionPane.OK_OPTION) {
						getSelectedFile().delete();
						super.approveSelection();
					}
				} else {
					super.approveSelection();
				}
			}
		}
	}

	/**
	 * Inner class used to create simple file filters with only extension check
	 */
	protected static class PlotImagesFileFilter extends
			javax.swing.filechooser.FileFilter {
		private String extension, description;

		/**
		 * Creates a new filefilter with specified extension and description
		 * 
		 * @param extension
		 *            extension of this filter (for example ".jmt")
		 * @param description
		 *            description of this filter
		 */
		public PlotImagesFileFilter(String extension, String description) {
			this.extension = extension;
			this.description = description;
		}

		/**
		 * Whether the given file is accepted by this filter.
		 */
		public boolean accept(File f) {
			String name = f.getName().toLowerCase();
			return name.endsWith(extension) || f.isDirectory();
		}

		/**
		 * The description of this filter
		 * 
		 * @see javax.swing.filechooser.FileView#getName
		 */
		public String getDescription() {
			return description + " (*" + extension + ")";
		}

		/**
		 * Gets extension of this filter
		 * 
		 * @return extension of this filter
		 */
		public String getExtension() {
			return extension;
		}
	}
	// ----------------------------------------------------------------------------------------
}
