package ij.gui;
import ij.*;
import ij.process.*;
import ij.measure.CurveFitter;
import ij.measure.Minimizer;
import ij.text.TextWindow;
import ij.measure.ResultsTable;
import ij.plugin.Colors;
import ij.plugin.frame.Recorder;
import ij.util.Tools;
import java.awt.*;
import java.awt.event.*;
import java.util.Arrays;
import java.util.Vector;
import java.util.ArrayList;

/** This class implements the Plot Window's Data>"Add from Plot", "Add form Table", "Add Fit" and
 *  "More>Contents Style" dialogs
 */
public class PlotContentsDialog implements DialogListener {
	/** Types of dialog (ERROR suppresses the dialog after an invalid call of a constructor) */
	public final static int ERROR=-1, STYLE=0, ADD_FROM_PLOT=1, ADD_FROM_TABLE=2, ADD_FROM_ARRAYS=3, ADD_FIT=4;
	/** Dialog headings for each dialogType >= 0 */
	private static final String[] HEADINGS = new String[] {"Plot Contents Style", "Add From Plot", "Plot From Table", "Add Plot Data", "Add Fit"};
	private Plot          plot;                     // the plot we work on
	private int           dialogType;               // determines what to do: ADD_FORM_PLOT, etc.
	private double[]      savedLimits;              // previous plot range, for undo upon cancel
	GenericDialog         gd;
	private int currentObjectIndex = -1;
	private Choice        objectChoice;
	private Choice        symbolChoice;
	private TextField     colorField, color2Field, labelField, widthField;
	private Checkbox      visibleCheckbox;
	private boolean       creatingPlot;              // for creating plots; dialogType determines source
	private Choice        plotChoice;                // for "Add from Plot"
	private Plot[]        allPlots;
	private String[]      allPlotNames;
	private static Plot   previousPlot;
	private static int    previousPlotObjectIndex;
	private int           defaultPlotIndex, defaultObjectIndex;
	private int           currentPlotNumObjects;
	private Choice        tableChoice;               // for "Add from Table"
	final static int      N_COLUMNS = 4;             // number of data columns that we can have; x, y, xE, yE
	private int           nColumnsToUse = N_COLUMNS; // user may restrict to 2, for not having error bars
	private Choice[]      columnChoice = new Choice[N_COLUMNS];
	private final static String[] COLUMN_NAMES = new String[] {"X:", "Y:", "X Error:", "Y Error:"};
	private final static boolean[] COLUMN_ALLOW_NONE = new boolean[] {true, false, true, true}; //y data cannot be null
	private ResultsTable[] allTables;
	private String[]      allTableNames;
	private static String previousTableName;
	private static int[]  previousColumns = new int[]{1, 1, 0, 0}; //must be N_COLUMNS elements
	private static int    defaultTableIndex;
	private static int[]  defaultColumnIndex = new int[N_COLUMNS];
	private String[]      arrayHeadings;             // for "Add from Arrays"
	private ArrayList<float[]> arrayData;
	private Choice        fitDataChoice;             // for "Add Fit"
	private Choice        fitFunctionChoice;
	private Thread        fittingThread;
	private static String lastFitFunction = CurveFitter.fitList[0];
	private String        curveFitterStatusString;
	private static String previousColor="blue", previousColor2="#a0a0ff", previousSymbol="Circle";
	private static double previousLineWidth = 1;
	private static final String[] PLOT_COLORS = new String[] {"blue", "red", "black", "#00c0ff", "#00e000", "gray", "#c08060", "magenta"};


	/** Prepares a new PlotContentsDialog for an existing plot. Use showDialog thereafter.
	 *  @param dialogType may be STYLE (contents style), ADD_FROM_PLOT (add object from other plot), ADD_FROM_TABLE, and ADD_FIT */
	public PlotContentsDialog(Plot plot, int dialogType) {
		this.plot = plot;
		this.dialogType = plot == null ? ERROR : dialogType;
		if (plot != null) currentPlotNumObjects = plot.getNumPlotObjects();
	}

	/** Prepares a new PlotContentsDialog for creating a new plot using data from a ResultsTable.
	 *  Use showDialog thereafter. */
	public PlotContentsDialog(String title, ResultsTable rt) {
		creatingPlot = true;
		dialogType = ADD_FROM_TABLE;
		if (rt == null || !isValid(rt)) {
			IJ.error("Cant Create Plot","No (results) table or no data in "+title);
			dialogType = ERROR;
		}
		plot = new Plot("Plot of "+title, "x", "y");
		allTables = new ResultsTable[] {rt};
		allTableNames = new String[] {title};
	}

	/** Prepares a new PlotContentsDialog for creating a new plot using float[] arrays as data.
	 *  Each 'data' array in the ArrayList must have a corresponding element in the 'headings' array.
	 *  'defaultHeadings' may contain the headings of the items selected initially for x, y, x error and y error, respectively.
	 *  'defaultHeadings' and each of its entries may be null, and the array may have any length. */
	public PlotContentsDialog(String title, String[] headings, String[] defaultHeadings, ArrayList<float[]> data) {
		creatingPlot = true;
		dialogType = ADD_FROM_ARRAYS;
		this.arrayHeadings = headings;
		this.arrayData = data;
		plot = new Plot(title, "x", "y");
		setDefaultColumns(defaultHeadings);
	}

	/** Prepares a new PlotContentsDialog for adding data from float[] arrays to a plot.
	 *  Each 'data' array in the ArrayList must have a corresponding element in the 'headings' array.
	 *  'defaultHeadings' may contain the headings of the items selected initially for x, y, x error and y error, respectively.
	 *  'defaultHeadings' and each of its entries may be null, and the array may have any length. */
	public PlotContentsDialog(Plot plot, String[] headings, String[] defaultHeadings, ArrayList<float[]> data) {
		dialogType = ADD_FROM_ARRAYS;
		this.plot = plot;
		this.arrayHeadings = headings;
		this.arrayData = data;
		setDefaultColumns(defaultHeadings);
	}

	/** Avoids showing a selection for the error bars; must be called before showDialog. */
	public void noErrorBars() {
		nColumnsToUse = 2; // only x, y columns can be selected
	}

	/** Shows the dialog, with a given parent Frame (may be null) */
	public void showDialog(Frame parent) {
		if (dialogType == ERROR) return;
		if (!creatingPlot) savedLimits = plot.getLimits();
		plot.savePlotObjects();
		String[] designations = plot.getPlotObjectDesignations();
		if (dialogType == STYLE && designations.length==0) {
			IJ.error("Empty Plot");
			return;
		} else if (dialogType == ADD_FROM_PLOT) {
			prepareAddFromPlot();
			if (allPlots.length == 0) return;	//should never happen; we have at least the current plot
		} else if (dialogType == ADD_FROM_TABLE && !creatingPlot) {
			prepareAddFromTable();
			if (allTables.length == 0) return;	//should never happen; PlotWindow should not enable if no table
		}
		if ((dialogType == ADD_FROM_TABLE || dialogType == ADD_FROM_ARRAYS) && !creatingPlot)
			suggestColor();
		if (parent == null && plot.getImagePlus() != null)
			parent = plot.getImagePlus().getWindow();
		gd = parent == null ? new GenericDialog(HEADINGS[dialogType]) :
				new GenericDialog(HEADINGS[dialogType], parent);
		IJ.wait(100);			//sometimes needed to avoid hanging?
		if (dialogType == STYLE) {
			gd.addChoice("Item:", designations, designations[0]);
			objectChoice = (Choice)(gd.getChoices().get(0));
			currentObjectIndex = objectChoice.getSelectedIndex();
		} else if (dialogType == ADD_FROM_PLOT) {
			gd.addChoice("Select Plot:", allPlotNames, allPlotNames[defaultPlotIndex]);
			gd.addChoice("Item to Add:", new String[]{""}, "");  // will be set up by makeSourcePlotObjects
			Vector choices = gd.getChoices();
			plotChoice = (Choice)(choices.get(0));
			objectChoice = (Choice)(choices.get(1));
			makeSourcePlotObjects();
		} else if (dialogType == ADD_FROM_TABLE) {
			gd.addChoice("Select Table:", allTableNames, allTableNames[defaultTableIndex]);
			tableChoice = (Choice)(gd.getChoices().get(0));
			if (creatingPlot) tableChoice.setEnabled(false);     // we can't select the table, we have only one
		} else if (dialogType == ADD_FIT) {
			String[] dataSources = plot.getDataObjectDesignations();
			if (dataSources.length == 0) {
				IJ.error("No Data For Fitting");
				return;
			}
			gd.addChoice("Fit Data Set:", dataSources, dataSources[0]);
			gd.addChoice("Fit Function:", new String[0], "");
			Vector choices = gd.getChoices();
			fitDataChoice = (Choice)(choices.get(0));
			fitFunctionChoice = (Choice)(choices.get(1));
			if (dataSources.length == 1)
				fitDataChoice.setEnabled(false);
			for (int i=0; i<CurveFitter.fitList.length; i++)
				fitFunctionChoice.addItem(CurveFitter.fitList[CurveFitter.sortedTypes[i]]);
			fitFunctionChoice.select(lastFitFunction);
		}
		if (dialogType == ADD_FROM_TABLE || dialogType == ADD_FROM_ARRAYS) {
			for (int i=0; i<nColumnsToUse; i++) {
				gd.addChoice(COLUMN_NAMES[i], new String[]{""}, "");  // will set up by makeSourceColumns
				Vector choices = gd.getChoices();
				columnChoice[i] = (Choice)(choices.get(choices.size()-1));
			}
			makeSourceColumns();
			if (!creatingPlot)
				suggestNewYColumn();
		}
		gd.addStringField("Color:", previousColor, 10);
		gd.addStringField("Secondary (fill) color:", previousColor2, 10);
		gd.addNumericField("Line width: ", previousLineWidth, 1);
		gd.addChoice("Symbol:", Plot.SORTED_SHAPES, dialogType == ADD_FIT ? "line" : previousSymbol);
		gd.addStringField("Label:", "", 20);
		gd.setInsets(10, 60, 0);
		gd.addCheckbox("Visible", true);
		Vector choices = gd.getChoices();
		symbolChoice = (Choice)(choices.get(choices.size()-1));
		Vector stringFields = gd.getStringFields();
		colorField = (TextField)(stringFields.get(0));
		color2Field = (TextField)(stringFields.get(1));
		labelField = (TextField)(stringFields.get(2));
		widthField = (TextField)(gd.getNumericFields().get(0));
		visibleCheckbox = (Checkbox)(gd.getCheckboxes().get(0));
		gd.addDialogListener(this);
		IJ.wait(100);			//sometimes needed to avoid hanging?
		if (dialogType == STYLE)
			setDialogStyleFields(objectChoice.getSelectedIndex());
		else if (dialogType == ADD_FROM_PLOT)
			addObjectFromPlot();
		else if (dialogType == ADD_FROM_TABLE || dialogType == ADD_FROM_ARRAYS)
			addObjectFromTable();
		else if (dialogType == ADD_FIT)
			addFitCurve();
		plot.updateImage();
		if (creatingPlot) {
			boolean recording = Recorder.record;
			Recorder.record = false; // don't record creating the image as image selection
			plot.show();
			if (recording) Recorder.record = true;
		}

		gd.showDialog();

		if (fittingThread != null) {
			fittingThread.interrupt();
			try {
				fittingThread.join();
			} catch (InterruptedException e) {}
		}
		if (gd.wasCanceled()) {
			if (creatingPlot) {
				ImagePlus imp = plot.getImagePlus();
				if (imp != null) imp.close();
			} else {
				plot.restorePlotObjects();
				if (savedLimits != null)
					plot.setLimits(savedLimits[0], savedLimits[1], savedLimits[2], savedLimits[3]);
				plot.updateImage();
				plot.killPlotObjectsSnapshot();
			}
			return;
		}
		plot.killPlotObjectsSnapshot();
		if (dialogType == ADD_FROM_TABLE || dialogType == ADD_FROM_ARRAYS || dialogType == ADD_FIT) {
			previousColor = colorField.getText();
			previousColor2 = color2Field.getText();
			previousSymbol = symbolChoice.getSelectedItem();
			previousLineWidth = Tools.parseDouble(widthField.getText());
		}
		if (dialogType == ADD_FIT) {
			lastFitFunction = fitFunctionChoice.getSelectedItem();
			IJ.log(curveFitterStatusString);
		}
		if (Recorder.record && !Recorder.scriptMode()) {
			if (dialogType == ADD_FROM_PLOT) {
				Recorder.recordString("Plot.addFromPlot(\""+plotChoice.getSelectedItem()+"\", "+objectChoice.getSelectedIndex()+");\n");
			} else if (dialogType == ADD_FROM_TABLE) {
				if (creatingPlot)
					Recorder.recordString("Plot.create(\""+plot.getTitle()+"\", \""+plot.getLabel('x')+"\", \""+plot.getLabel('y')+"\");\n");
				String xSource = columnChoice[0].getSelectedIndex() == 0 ? "" :
						"Table.getColumn(\""+columnChoice[0].getSelectedItem()+"\", \""+tableChoice.getSelectedItem()+"\"), ";
				String ySource = "Table.getColumn(\""+columnChoice[1].getSelectedItem()+"\", \""+tableChoice.getSelectedItem()+"\")";
				Recorder.recordString("Plot.add(\""+symbolChoice.getSelectedItem()+"\", "+xSource+ySource+");\n");
				if (columnChoice[2].getSelectedIndex() > 0)
					Recorder.recordString("Plot.add(\"xerror\", Table.getColumn(\""+
							columnChoice[2].getSelectedItem()+"\", \""+tableChoice.getSelectedItem()+"\"));\n");
				if (columnChoice[3].getSelectedIndex() > 0)
					Recorder.recordString("Plot.add(\"error\", Table.getColumn(\""+
							columnChoice[3].getSelectedItem()+"\", \""+tableChoice.getSelectedItem()+"\"));\n");
			}
			Recorder.recordString("Plot.setStyle("+currentObjectIndex+", \""+getStyleString()+"\");\n");
		}
	}

	public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) {
		if (e==null)
			return true;	//gets called with e=null upon OK
		boolean setStyle = false;
		if (dialogType == STYLE) {
			int objectIndex = objectChoice.getSelectedIndex();  // no getNextChoice since Choices depend on dialog type
			setStyle = (e.getSource() != objectChoice);
			if (e.getSource() == objectChoice) {
				setDialogStyleFields(objectIndex);
				currentObjectIndex = objectIndex;
			} else
				setStyle = true;
		} else if (dialogType == ADD_FROM_PLOT) {
			if (e.getSource() == plotChoice) {
				makeSourcePlotObjects();
				addObjectFromPlot();
			} else if (e.getSource() == objectChoice) {
				addObjectFromPlot();
			} else
				setStyle = true;
		} else if (dialogType == ADD_FROM_TABLE || dialogType == ADD_FROM_ARRAYS) {
			if (e.getSource() == tableChoice) {
				makeSourceColumns();
				addObjectFromTable();
			} else {
				boolean columnChanged = false;
				for (int c=0; c<nColumnsToUse; c++)
					if (e.getSource() == columnChoice[c]) {
						columnChanged = true;
						break;
					}
				if (columnChanged)
					addObjectFromTable();
				else
					setStyle = true;
			}
		} else if (dialogType == ADD_FIT) {
			if (e.getSource() == fitDataChoice || e.getSource() == fitFunctionChoice)
				addFitCurve();
			else
				setStyle = true;
		}
		if (setStyle)
			setPlotObjectStyle();
		return true;
	}

	private void setPlotObjectStyle() {
		if (currentObjectIndex < 0) return;
		String style = getStyleString();
		plot.setPlotObjectStyle(currentObjectIndex, style);
		if (labelField.isEnabled()) {
			String label = labelField.getText();
			plot.setPlotObjectLabel(currentObjectIndex, label.length() > 0 ? label : null);
		}
	}

	/** Returns the style String for Plot.setPlotObjectStyle from the dialog fields */
	private String getStyleString() {
		String color = colorField.getText();
		String color2 = color2Field.getText();
		double width = Tools.parseDouble(widthField.getText());
		String symbol = symbolChoice.getSelectedItem();
		Boolean visible = visibleCheckbox.getState();
		String style = color.trim()+","+color2.trim()+","+(float)width+","+ symbol+(visible?"":"hidden");
		return style;
	}

	/** Sets the style fields of the dialog according to the style of the PlotObject having the
	 *  index given. Does nothing with index < 0 */
	private void setDialogStyleFields(int index) {
		if (index < 0) return;
		Checkbox visibleC = (Checkbox)gd.getCheckboxes().get(0);
		String styleString = plot.getPlotObjectStyle(index);
		String designation = plot.getPlotObjectDesignations()[index].toLowerCase();
		boolean isData = designation.startsWith("data");
		boolean isText = designation.startsWith("text");
		boolean isBox = designation.startsWith("shapes") &&
				(designation.contains("boxes") || designation.contains("rectangles"));
		boolean isGrid = designation.startsWith("shapes") && designation.contains("redraw_grid");

		String[] items = styleString.split(",");
		colorField.setText(items[0]);
		color2Field.setText(items[1]);
		widthField.setText(items[2]);
		if (items.length >= 4)
			symbolChoice.select(items[3]);
		labelField.setText(isData ? plot.getPlotObjectLabel(index) : "");
		visibleC.setState(!styleString.contains("hidden"));

		colorField.setEnabled(!isGrid);	        //grid color is fixed
		color2Field.setEnabled(isData || isBox);//only (some) data symbols and boxes have secondary (fill) color
		widthField.setEnabled(!isText  && !isGrid); //all non-Text types have line width
		// visibleC.setEnabled(!isGrid);        //allow to hide everything
		symbolChoice.setEnabled(isData);        //only data have a symbol to choose
		labelField.setEnabled(isData);          //only data have a label in the legend
	}


	/** Prepare the lists 'allPlots', 'allPlotNames' for the "Add from Plot" dialog.
	 *  Also sets 'defaultPlotIndex', 'defaultObjectIndex' */
	private void prepareAddFromPlot() {
		int[] windowIDlist = WindowManager.getIDList();
		ArrayList<ImagePlus> plotImps = new ArrayList<ImagePlus>();
		ImagePlus currentPlotImp = null;
		for (int windowID : windowIDlist) {
			ImagePlus imp = WindowManager.getImage(windowID);
			if (imp == null || imp.getWindow() == null) continue;
			Plot thePlot = (Plot)(imp.getProperty(Plot.PROPERTY_KEY));
			if (thePlot != null) {
				if (thePlot == plot)
					currentPlotImp = imp;
				else
					plotImps.add(imp);
			}
		}
		if (currentPlotImp != null)
			plotImps.add(currentPlotImp);     // add current plot as the last one (usually not used)
		if (plotImps.size() == 0) return;     // should never happen; we have at least the current plot

		allPlots = new Plot[plotImps.size()];
		allPlotNames = new String[plotImps.size()];
		defaultPlotIndex = 0;
		for (int i=0; i<allPlots.length; i++) {
			ImagePlus imp = plotImps.get(i);
			allPlots[i] = (Plot)(imp.getProperty(Plot.PROPERTY_KEY));
			if (allPlots[i] == previousPlot)
				defaultPlotIndex = i;
			allPlotNames[i] = imp.getWindow().getTitle();
			if (imp == currentPlotImp)
				allPlotNames[i] = "THIS PLOT: " + allPlotNames[i];
		}
	}

	/** Set up the Choice of Plot objects for the source plot after selecting that plot */
	private void makeSourcePlotObjects() {
		int plotIndex = plotChoice.getSelectedIndex();
		String[] plotObjectNames = allPlots[plotIndex].getPlotObjectDesignations();
		objectChoice.removeAll();
		int nPlotObjects = allPlots[plotIndex] == plot ? currentPlotNumObjects : plotObjectNames.length; //for the own plot, the number of objects may have changed in the meanwhile
		for (int i=0; i<nPlotObjects; i++)
			objectChoice.addItem(plotObjectNames[i]);
		objectChoice.select(Math.min(nPlotObjects-1, previousPlotObjectIndex));
	}

	/** For "Add from Plot", adds item to the plot according to the current Choice settings
	 *  and sets the Style fields for it. */
	private void addObjectFromPlot() {
		int plotIndex = plotChoice.getSelectedIndex();
		int objectIndex = objectChoice.getSelectedIndex();
		plot.restorePlotObjects();
		if (savedLimits != null)
			plot.setLimits(savedLimits);
		currentObjectIndex = plot.addObjectFromPlot(allPlots[plotIndex], objectIndex); //no updateImage; will be done later
		plot.fitRangeToLastPlotObject();
		setDialogStyleFields(currentObjectIndex);
		previousPlot = allPlots[plotIndex];
		previousPlotObjectIndex = objectIndex;
	}

	/** Prepare the lists 'allTables', "allTableNames' for the "Add from Table" dialog.
	 *  Also sets 'defaultTableIndex' */
	private void prepareAddFromTable() {
		ArrayList<TextWindow> tableWindows = new ArrayList<TextWindow>();
		Frame[] windows = WindowManager.getNonImageWindows();
		for (Frame win : windows) {
			if (!(win instanceof TextWindow)) continue;
			ResultsTable rt = ((TextWindow)win).getResultsTable();
			if (isValid(rt))
				tableWindows.add((TextWindow)win);
		}
		allTables = new ResultsTable[tableWindows.size()];
		allTableNames = new String[tableWindows.size()];
		defaultTableIndex =  0;
		for (int i=0; i<allTables.length; i++) {
			TextWindow tw = tableWindows.get(i);
			allTables[i] = tw.getResultsTable();
			if (allTableNames[i]!=null && allTableNames[i].equals(previousTableName))
				defaultTableIndex = i;
			allTableNames[i] = tw.getTitle();
		}
	}

	/** Returns whether there is at least one table that can be used for "Add from Table" */
	public static boolean tableWindowExists() {
		Frame[] windows = WindowManager.getNonImageWindows();
		for (Frame win : windows) {
			if (win instanceof TextWindow) {
				ResultsTable rt = ((TextWindow)win).getResultsTable();
				if (isValid(rt)) return true;
			}
		}
		return false;
	}

	/** Set up the Choices for the source columns for "Add from Table" and "Add from Arrays" */
	private void makeSourceColumns() {
		String[] columnHeadings = null;
		if (dialogType == ADD_FROM_TABLE) {
			int tableIndex = tableChoice.getSelectedIndex();
			ResultsTable rt = allTables[tableIndex];
			String columnHeadingStr = rt.getColumnHeadings();
			if (!columnHeadingStr.startsWith(" \t"))
				columnHeadingStr = " \t"+columnHeadingStr;	//add empty field at beginning (if we don't have one)
			columnHeadings = columnHeadingStr.split("\t");
			int nBadColumns = 0;
			for (int i=1; i<columnHeadings.length; i++) {	//check for columns with no data
				int index = rt.getColumnIndex(columnHeadings[i]);
				if (!rt.columnExists(index)) {
					columnHeadings[i] = null;
					nBadColumns++;
				}
			}
			if (nBadColumns > 0) {
				String[] newHeadings = new String[columnHeadings.length - nBadColumns];
				int i=0;
				for (String heading : columnHeadings)		//copy 'good' headings
					if (heading != null)
						newHeadings[i++] = heading;
				columnHeadings = newHeadings;
			}
		} else {   // if (dialogType == ADD_FROM_ARRAYS)
			columnHeadings = new String[arrayHeadings.length+1];
			System.arraycopy(arrayHeadings, 0, columnHeadings, 1, arrayHeadings.length);
		}
		columnHeadings[0] = "---";
		for (int c=0; c<nColumnsToUse; c++) {
			columnChoice[c].removeAll();
			for (int i=COLUMN_ALLOW_NONE[c] ? 0 : 1; i<columnHeadings.length; i++)
					columnChoice[c].addItem(columnHeadings[i]);
			if (columnChoice[c].getItemCount() > 0 && previousColumns[c] >= 0)
				columnChoice[c].select(Math.min(columnChoice[c].getItemCount()-1, previousColumns[c]));
		}
	}

	/** For "Add from Table" and "Add from Arrays" adds item to the plot according to the current Choice settings
	 *  and sets the Style fields for it. */
	private void addObjectFromTable() {
		float[][] data = getDataArrays();
		if (data[1] == null) return;  //no y data? then can't plot
		String label = columnChoice[1].getSelectedItem();	//take label from y
		int shape = Plot.toShape(symbolChoice.getSelectedItem());
		float lineWidth = (float)(Tools.parseDouble(widthField.getText()));
		if (lineWidth > 0)
			plot.setLineWidth(lineWidth);
		plot.restorePlotObjects();
		if (savedLimits != null)
			plot.setLimits(savedLimits);
		plot.setColor(colorField.getText(), color2Field.getText());
		plot.addPoints(data[0], data[1], data[3], shape, label);
		if (data[2] != null)
			plot.addHorizontalErrorBars(data[2]);
		if (creatingPlot) {
			plot.setXYLabels(data[0]==null ? "x" : columnChoice[0].getSelectedItem(), columnChoice[1].getSelectedItem());
			plot.setLimitsToFit(false);
		} else
			plot.fitRangeToLastPlotObject();
		currentObjectIndex = plot.getNumPlotObjects()-1;
		setDialogStyleFields(currentObjectIndex);
		if (dialogType == ADD_FROM_TABLE)
			previousTableName = allTableNames[tableChoice.getSelectedIndex()];
	}

	/** For "Add from Table" and "Add from Arrays", tries to set a 'y' data column that has not been plotted yet. */
	private void suggestNewYColumn() {
		int nYcolumns = columnChoice[1].getItemCount();
		int currentIndex = columnChoice[1].getSelectedIndex();
		for (int i=0; i<nYcolumns; i++) {
			if (columnChoice[0].getSelectedIndex()==0 ||    //if x is a data column, don't suggest it as y
					!columnChoice[1].getSelectedItem().equals(columnChoice[0].getSelectedItem())) {
				float[][] data = getDataArrays();
				data = new float[][] {data[0], data[1]};	//only compare x & y, not the error bars
				if (plot.getPlotObjectIndex(data) < 0)
					return; //current data are ok (not duplicate)
			}
			columnChoice[1].select((currentIndex+i+1)%nYcolumns); //try the next one
		}
	}

	/** For "Add from Table" and "Add from Arrays", retrieves the data arrays according to the columnChoices */
	private float[][] getDataArrays() {
		float[][] data = new float[N_COLUMNS][];
		if (dialogType == ADD_FROM_TABLE) {
			ResultsTable rt = allTables[tableChoice.getSelectedIndex()];
			for (int c=0; c<N_COLUMNS; c++) {
				String heading = columnChoice[c].getSelectedItem();
				int index = rt.getColumnIndex(heading);
				if (index >= 0)
					data[c] = rt.getColumn(index);
				previousColumns[c] = columnChoice[c].getSelectedIndex();
			}
		} else { //if (dialogType == ADD_FROM_ARRAYS)
			for (int c=0; c<nColumnsToUse; c++) {
				String heading = columnChoice[c].getSelectedItem();
				int index = getIndex(arrayHeadings, heading);
				if (index >= 0)
					data[c] = arrayData.get(index);
				previousColumns[c] = columnChoice[c].getSelectedIndex();
			}
		}
		return data;
	}

	/** Does the curve fit and adds the fit curve to the plot */
	private void addFitCurve() {
		plot.restorePlotObjects();
		if (savedLimits != null)
			plot.setLimits(savedLimits);
		int dataIndex = fitDataChoice.getSelectedIndex();
		float[][] data = plot.getDataObjectArrays(dataIndex);
		String fitName = fitFunctionChoice.getSelectedItem();
		int fitType = getIndex(CurveFitter.fitList, fitName);
		CurveFitter cf = new CurveFitter(Tools.toDouble(data[0]), Tools.toDouble(data[1]));
		cf.doFit(fitType);
		String statusString = "Fit: "+Minimizer.STATUS_STRING[cf.getStatus()];
		if (cf.getStatus() == Minimizer.SUCCESS)
			statusString += ", sum residuals ^2 = "+(float)(cf.getSumResidualsSqr());
		IJ.showStatus(statusString);
		curveFitterStatusString = "Fit for "+plot.getTitle()+": "+fitDataChoice.getSelectedItem()+cf.getResultString(); //will be shown in Log when done

		double[] plotMinMax = plot.getLimits();
		double[] dataMinMax = Tools.getMinMax(data[0]);
		double min = Math.min(plotMinMax[0], dataMinMax[0]);
		double max = Math.max(plotMinMax[1], dataMinMax[1]);
		double plotSpan = Math.abs(plotMinMax[1] - plotMinMax[0]);
		double dataSpan = Math.abs(dataMinMax[1] - dataMinMax[0]);
		double rangeFactor = Math.max(plotSpan/dataSpan, dataSpan/plotSpan);
		if (rangeFactor > 20) rangeFactor = 20;
		int nPoints = (int)(1000*rangeFactor); //finer data point spacing if we will want to zoom out or in
		float[] xFit = new float[nPoints];
		float[] yFit = new float[nPoints];
		for (int i=0; i<nPoints; i++) {
			xFit[i] = (float)(min + i*((max-min)/(nPoints-1)));
			yFit[i] = (float)(cf.f(xFit[i]));
		}
		plot.addPoints(xFit, yFit, Plot.LINE);
		currentObjectIndex = plot.getNumPlotObjects()-1;
		labelField.setText("Fit: "+fitName);
		setPlotObjectStyle();
	}

	/** Returns whether the table is non-null and has at least one non-trivial data column */
	private static boolean isValid(ResultsTable rt) {
		if (rt == null) return false;
		String columnHeadingStr = rt.getColumnHeadings();
		if (columnHeadingStr.startsWith(" \t"))
			return columnHeadingStr.length() >=3;
		else
			return columnHeadingStr.length() >= 1;

	}

	/** For "Add from Arrays", sets default columns from the colums titles */
	private void setDefaultColumns(String[] defaultHeadings) {
		if (defaultHeadings == null) return;
		for (int i=0; i<Math.min(defaultHeadings.length, nColumnsToUse); i++) {
			if (defaultHeadings[i] == null || defaultHeadings[i].length()==0)
				previousColumns[i] = 0;
			else
				previousColumns[i] = Math.max(0,  //default column index; index=0 means 'none' for all except y
						getIndex(arrayHeadings, defaultHeadings[i]) + (COLUMN_ALLOW_NONE[i] ? 1 : 0));
		}
	}

	/** Finds the index of a searchTerm in the String array; returns -1 if not found */
	private static int getIndex(String[] strArray, String searchTerm) {
		for (int i=0; i<strArray.length; i++)
			if (strArray[i].equals(searchTerm)) return i;
		return -1;
	}

	/** Sets the 'previousColor', 'previousColor2' with a suggestion for new colors */
	private void suggestColor() {
		boolean[] colorUsed = new boolean[PLOT_COLORS.length];
		String[] designations = plot.getPlotObjectDesignations();
		for (int i=0; i<designations.length; i++) {
			if (!(designations[i].toLowerCase().startsWith("data"))) continue;
			String styleString = plot.getPlotObjectStyle(i);
			for (int j=0; j<PLOT_COLORS.length; j++)
				if (styleString.startsWith(PLOT_COLORS[j]))
					colorUsed[j] = true;
		}
		String newColor = previousColor;
		for (int j=0; j<PLOT_COLORS.length; j++)
			if (!colorUsed[j]) {
				newColor = PLOT_COLORS[j];
				break;
			}
		if (previousColor2 != null && previousColor2.equals(previousColor))  //if fill color was main color, keep it such
			previousColor2 = newColor;
		else if (Colors.decode(previousColor2, null) != null) {              //if we had a separate fill color, make main color brighter
			Color newC = Colors.decode(newColor,Color.BLACK);
			Color newC2 = new Color(makeBrighter(newC.getRed()), makeBrighter(newC.getGreen()), makeBrighter(newC.getBlue()));
			previousColor2 = Colors.colorToString(newC2);
		}
		previousColor = newColor;
	}

	/** Creates an 8-bit color value closer to 255 */
	private static int makeBrighter(int v) {
		return v> 190 ? 255 : 255 - (int)(0.4*(255-v));
	}
}
