package imseProc.graph;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Set;

import imseProc.core.IMSEProc;
import imseProc.core.ImagePipeController;
import imseProc.core.ImagePipeControllerROISettable;
import imseProc.core.ImgPipe;
import imseProc.graph.shapeFit.FuncFitterSWTControl;
import imseProc.gui.swt.SWTControllerInfoDraw;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Path;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.xy.DefaultXYDataset;
import org.jfree.data.xy.XYDataset;
import org.jfree.experimental.chart.swt.ChartComposite;

import binaryMatrixFile.BinaryMatrixFile;

import otherSupport.ScientificNumberFormat;

/** The Graph Util GUI component */
public class GraphUtilSWTControl implements ImagePipeController, ImagePipeControllerROISettable, SWTControllerInfoDraw {
	
	private ScientificNumberFormat fmt = new ScientificNumberFormat();
	
	private Group swtGroup;
	
	private SashForm swtSashForm;
	private ScrolledComposite swtTopScrollComp;
	private Composite swtTopComp;
	
	private Combo typeSelectV;
	private Button roiAvgCheckbox;
	private Button subtractX0Checkbox;
	private Button subtractY0Checkbox;
	private Button includeNanCheckbox;
	private Button includeZeroCheckbox;
	private Button rangeHoldCheckbox;
	private Combo metaDataNamesV;	
	private Combo timebaseMetaInterp;
	private Combo timebaseMetaSeries;
	private Button saveTracesButton;
	private Button dispFittingCheckbox;
	private Button dispSignalsCheckbox;

	private FuncFitterSWTControl fitterControl;
	private ExtSignalsSWTControl signalsControl;
		
	private GraphUtilProcessor proc;
	private Label basicInfo;
		
	private JFreeChartGraph graph;
	
	public GraphUtilSWTControl(GraphUtilProcessor proc, Composite parent, int style) {
		this.proc = proc;
		
		swtGroup = new Group(parent, style);
		swtGroup.setText("Graph Util");
		swtGroup.setLayout(new FillLayout());	
		swtGroup.addListener(SWT.Dispose, new Listener() { @Override public void handleEvent(Event event) { destroy(); }});
		
		swtSashForm =  new SashForm(swtGroup, SWT.VERTICAL | SWT.BORDER);
        swtSashForm.setLayout(new FillLayout());
        
        swtTopScrollComp = new ScrolledComposite(swtSashForm, SWT.V_SCROLL);
        //swtTopScrollComp.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        
        swtTopComp = new Composite(swtTopScrollComp, SWT.NONE);
        swtTopComp.setLayout(new GridLayout(4, false));
        
		Label lTV = new Label(swtTopComp, SWT.NONE); lTV.setText("Scan: ");		
		typeSelectV = new Combo(swtTopComp, SWT.DROP_DOWN | SWT.READ_ONLY);
		typeSelectV.setItems(GraphUtilProcessor.scanDirNames);
		typeSelectV.select(GraphUtilProcessor.SCAN_DIR_X);
		typeSelectV.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
		typeSelectV.addListener(SWT.Modify, new Listener() { @Override public void handleEvent(Event event) { typeChangeEvent(event); }});

		metaDataNamesV = new Combo(swtTopComp, SWT.DROP_DOWN | SWT.READ_ONLY);
		metaDataNamesV.setItems(new String[0]);
		metaDataNamesV.addListener(SWT.Modify, new Listener() { @Override public void handleEvent(Event event) { metaDataNameVEvent(event); }});
		metaDataNamesV.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 2, 1));
		metaDataNamesV.setEnabled(false);
		
		Label lTT = new Label(swtTopComp, SWT.NONE); lTT.setText("Timebase: ");
		timebaseMetaInterp = new Combo(swtTopComp, SWT.DROP_DOWN | SWT.READ_ONLY);
		timebaseMetaInterp.setItems(new String[]{ "- None -" });
		timebaseMetaInterp.select(0);
		timebaseMetaInterp.addListener(SWT.Modify, new Listener() { @Override public void handleEvent(Event event) { timebaseEvent(event); }});
		timebaseMetaInterp.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
		timebaseMetaInterp.setEnabled(false);
		
		timebaseMetaSeries = new Combo(swtTopComp, SWT.DROP_DOWN | SWT.READ_ONLY);
		timebaseMetaSeries.setItems(new String[]{ "- None -" });
		timebaseMetaSeries.select(0);
		timebaseMetaSeries.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 2, 1));
		timebaseMetaSeries.addListener(SWT.Modify, new Listener() { @Override public void handleEvent(Event event) { timebaseEvent(event); }});
		timebaseMetaSeries.setEnabled(false);
		
		Group settingsGroupLHS = new Group(swtTopComp, SWT.BORDER);
		settingsGroupLHS.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 2, 1));
		settingsGroupLHS.setLayout(new GridLayout(2, false));
	    
		dispFittingCheckbox = new Button(settingsGroupLHS, SWT.CHECK);
		dispFittingCheckbox.setText("Shape Fitting");
		dispFittingCheckbox.setSelection(true);
		dispFittingCheckbox.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { subControlsEvent(event); }});
		dispFittingCheckbox.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 2, 1));
	
		dispSignalsCheckbox = new Button(settingsGroupLHS, SWT.CHECK);
		dispSignalsCheckbox.setText("Signals");
		dispSignalsCheckbox.setSelection(true);
		dispSignalsCheckbox.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { subControlsEvent(event); }});
		dispSignalsCheckbox.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 2, 1));
	
		basicInfo = new Label(settingsGroupLHS, SWT.NONE);		
		basicInfo.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 2, 1));
		
		Group settingsGroupRHS = new Group(swtTopComp, SWT.BORDER);
		settingsGroupRHS.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 2, 1));
		settingsGroupRHS.setLayout(new GridLayout(2, false));
	       
		saveTracesButton = new Button(settingsGroupRHS, SWT.PUSH);
		saveTracesButton.setText("Save Traces");
		saveTracesButton.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { saveTracesButtonEvent(event); }});
		
		roiAvgCheckbox = new Button(settingsGroupRHS, SWT.CHECK);
		roiAvgCheckbox.setText("ROI Avg");
		roiAvgCheckbox.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { typeChangeEvent(event); }});
		
		includeZeroCheckbox = new Button(settingsGroupRHS, SWT.CHECK);
		includeZeroCheckbox.setText("Inc. y=0");
		includeZeroCheckbox.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { graphSettingsChangeEvent(event); }});
		
		rangeHoldCheckbox = new Button(settingsGroupRHS, SWT.CHECK);
		rangeHoldCheckbox.setText("Hold Range");
		rangeHoldCheckbox.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { graphSettingsChangeEvent(event); }});
		
		subtractX0Checkbox = new Button(settingsGroupRHS, SWT.CHECK);
		subtractX0Checkbox.setText("Subtact X0");
		subtractX0Checkbox.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { typeChangeEvent(event); }});
		subtractX0Checkbox.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
		
		subtractY0Checkbox = new Button(settingsGroupRHS, SWT.CHECK);
		subtractY0Checkbox.setText("Subtact Y0");
		subtractY0Checkbox.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { typeChangeEvent(event); }});
		subtractY0Checkbox.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
		
		includeNanCheckbox = new Button(settingsGroupRHS, SWT.CHECK);
		includeNanCheckbox.setText("Include NaN in average");
		includeNanCheckbox.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { typeChangeEvent(event); }});
		includeNanCheckbox.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 2, 1));
		
		
		fitterControl = new FuncFitterSWTControl(swtTopComp, SWT.BORDER, proc);
		fitterControl.getGroup().setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 4, 1));
		fitterControl.getGroup().setVisible(true);
		
		signalsControl = new ExtSignalsSWTControl(swtTopComp, SWT.BORDER, proc);
		signalsControl.getGroup().setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 4, 1));		
		signalsControl.getGroup().setVisible(true);
				 
		graph = new JFreeChartGraph(swtSashForm, SWT.NONE);
		//graph.getComposite().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
		generalControllerUpdate();
		
		swtTopComp.pack();
		swtTopScrollComp.setContent(swtTopComp);		
		//swtTopScrollComp.setExpandVertical(true); 
		swtTopScrollComp.setExpandHorizontal(true);
		//swtTopComp.computeSize(500, 500);
	}
	
	private void subControlsEvent(Event event){
		fitterControl.getGroup().setVisible(dispFittingCheckbox.getSelection());
		((GridData)fitterControl.getGroup().getLayoutData()).exclude = !dispFittingCheckbox.getSelection();
		signalsControl.getGroup().setVisible(dispSignalsCheckbox.getSelection());
		((GridData)signalsControl.getGroup().getLayoutData()).exclude = !dispSignalsCheckbox.getSelection();
		swtTopComp.layout();
		//swtTopComp.pack();
	}
		
		
	private void metaDataNameVEvent(Event event){
		int selScan = typeSelectV.getSelectionIndex();
		proc.setMetaDataNameV(selScan, metaDataNamesV.getText());
	}

	private void timebaseEvent(Event event){
		String seqName = (timebaseMetaInterp.getSelectionIndex() == 0) ? null : timebaseMetaInterp.getText();		
		String seriesName =  (timebaseMetaSeries.getSelectionIndex() == 0) ? null : timebaseMetaSeries.getText();
		
		proc.setTimebase(seqName, seriesName);
	}
	
	private void fillNameSet(Combo metaDataNames, Set<String> nameSet, String sel){
		if(nameSet != null){
			ArrayList<String> nameList = new ArrayList<String>(nameSet);
			Collections.sort(nameList);
			for(String name : nameList){
				metaDataNames.add(name);
				if(name.equals(sel))
					metaDataNames.select(metaDataNames.getItemCount()-1);
			}
		}
		metaDataNames.setEnabled((nameSet != null && nameSet.size() > 0) ? true : false);
	}
		
	private void typeChangeEvent(Event event){
		
		int selScanV = typeSelectV.getSelectionIndex();
		String current = metaDataNamesV.getText();
		metaDataNamesV.setItems(new String[0]);
		if(selScanV == GraphUtilProcessor.SCAN_META_SERIES){
			fillNameSet(metaDataNamesV, proc.getMetaSeriesNames(), current);
		}
				
		int scanDir = proc.getScanDir();
		boolean isAvg = proc.isScanROIAvg();		
		boolean subX0 = proc.getSubtractX0();
		boolean subY0 = proc.getSubtractY0();
		boolean includeNaNInAvg = proc.getIncludeNaNInAvg();
		if(selScanV != scanDir || roiAvgCheckbox.getSelection() != isAvg 
				|| subtractX0Checkbox.getSelection() != subX0
				|| subtractY0Checkbox.getSelection() != subY0
				|| includeNanCheckbox.getSelection() != includeNaNInAvg){
			proc.setScanType(selScanV, roiAvgCheckbox.getSelection(),
					includeNanCheckbox.getSelection(),
					subtractX0Checkbox.getSelection(), 
					subtractY0Checkbox.getSelection());
		}
		

		current = timebaseMetaInterp.getText();
		timebaseMetaInterp.setItems(new String[]{ "- None -" });
		timebaseMetaInterp.select(0);
		fillNameSet(timebaseMetaInterp,  proc.getMetaSeriesNames(), current);
		
		current = timebaseMetaSeries.getText();
		timebaseMetaSeries.setItems(new String[]{ "- None -" });
		timebaseMetaSeries.select(0);
		fillNameSet(timebaseMetaSeries,  proc.getMetaSeriesNames(), current);
	}
	
	private void graphSettingsChangeEvent(Event event){
		graph.setAutoRangeIncludesZero(includeZeroCheckbox.getSelection());
		graph.setRangeHold(rangeHoldCheckbox.getSelection());
		
	}
		
	private void saveTracesButtonEvent(Event event) {
		List<Series> seriesList = proc.getSeriesList();
		
		for(int i=0; i < seriesList.size(); i++){
			Series series = seriesList.get(i);
			
			BinaryMatrixFile.mustWrite(System.getProperty("java.io.tmpdir") + "/graphData/"+ ((series.name != null) ? series.name : i) + ".bin",
						new double[][]{ series.x, series.data }, true);
		}
	}

	@Override
	public Composite getInterfacingObject() {
		return swtGroup;
	}	
	
	public void generalControllerUpdate() {	
		if(swtGroup.isDisposed()) return;
		IMSEProc.ensureFinalSWTUpdate(swtGroup.getDisplay(), this, new Runnable(){	
			@Override
			public void run() { doGeneralUpdate();  }
		});
	}
	
	private void doGeneralUpdate(){
		if(swtGroup.isDisposed()) return;
		
		basicInfo.setText(
				"Pos: (" + fmt.format(proc.getPosX()) + ", " + fmt.format(proc.getPosY()) + ") = " + fmt.format(proc.getValue()) + "\n"
				+ "ROI: (" + fmt.format(proc.getROIX0()) + ", " + fmt.format(proc.getROIY0()) + ") "+proc.getROIWidth() + " x " + proc.getROIHeight() + "\n"
				+ "Mean: " + fmt.format(proc.getMean()) + "\n"
				+ "Sum: " + fmt.format(proc.getSum()) + "\n"
				);
		
		graph.setData(proc.getSeriesList());
		
		int scanDir = proc.getScanDir();
		boolean isAvg = proc.isScanROIAvg();
		if(typeSelectV.getSelectionIndex() != scanDir || roiAvgCheckbox.getSelection() != isAvg){  
			typeSelectV.select(scanDir); //this actually calls this back, so only do it when there's a change
			roiAvgCheckbox.setSelection(isAvg);
		}
		
		fitterControl.update();
		signalsControl.update();
	}

	@Override
	public void movingPos(int x, int y) { proc.setPoint(x, y); }

	@Override
	public void fixedPos(int x, int y)  { proc.setPoint(x, y); }

	@Override
	public void setRect(int x0, int y0, int width, int height) { proc.setRect(x0, y0, width, height); }

	@Override
	public void drawOnImage(GC gc, double scale, int imageWidth, int imageHeight, boolean asSource) {
		gc.setForeground(swtGroup.getDisplay().getSystemColor(SWT.COLOR_WHITE));
		if(proc.isScanROIAvg()){//typeSelectV.getSelectionIndex() >= GraphUtilProcessor.SCAN_DIR_X){
			gc.drawRectangle(
					(int)(proc.getROIX0() * scale),
					(int)(proc.getROIY0() * scale),
					(int)(proc.getROIWidth() * scale),
					(int)(proc.getROIHeight() * scale));
		}
	}

	@Override
	public void destroy() {
		swtGroup.dispose();
		proc.controllerDestroyed(this);
	}

	@Override
	public ImgPipe getPipe() { return proc;	}

	

}
