package imseProc.proc.seriesAvg;

import java.text.DecimalFormat;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map.Entry;
import java.util.Set;

import oneLiners.OneLiners;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.TableEditor;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
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.eclipse.swt.widgets.Spinner;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;

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

/** The table directly relfects the config data in the processor's map
* Edits are made into that cfg immediately.
*  
* @author oliford
*/
public class SeriesAverageSWTController implements ImagePipeController, SWTControllerInfoDraw, ImagePipeControllerROISettable {

	public static final DecimalFormat tableValueFormat[] = new DecimalFormat[]{
			new DecimalFormat("#"), new DecimalFormat("#"), new DecimalFormat("#.###"), new DecimalFormat("#"), new DecimalFormat("#"),
		};
	
	public static final String colNames[] = new String[] {
			"Name", "Index 0", "Index 1", "Sigma (idx)", "Interlace", "Rad Removal"
		};	

	private Group swtGroup;
	private SeriesAverageProcessor proc;
	private Button autoUpdateCheckbox;
	private Button updateButton;
	private Label statusLabel;

	private Spinner altPulseSpiner;
	private Button loadPointsButton;
	
	private boolean isSinkController;
	
	private Table cfgTable;
	private TableEditor cfgTableEditor;
	private TableItem tableItemEditing = null;
	
	public SeriesAverageSWTController(Composite parent, int style, SeriesAverageProcessor proc, boolean isSinkController) {
		this.isSinkController = isSinkController;
		this.proc = proc;
		swtGroup = new Group(parent, style);
		swtGroup.setText("FFT Control (" + proc.toShortString() + ")");		
		swtGroup.setLayout(new GridLayout(3, false));
		swtGroup.addListener(SWT.Dispose, new Listener() { @Override public void handleEvent(Event event) { destroy(); }});
		
		Label lStat = new Label(swtGroup, SWT.NONE); lStat.setText("Status:");
		statusLabel = new Label(swtGroup, SWT.NONE);
		statusLabel.setText("init");
		statusLabel.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 2, 1));
		
		autoUpdateCheckbox = new Button(swtGroup, SWT.CHECK);
		autoUpdateCheckbox.setText("Auto");
		autoUpdateCheckbox.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { autoUpdateEvent(event); } });
		autoUpdateCheckbox.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 2, 1));
		
		updateButton = new Button(swtGroup, SWT.PUSH);
		updateButton.setText("Update Now");
		updateButton.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { forceUpdateEvent(event); } });
		
		cfgTable = new Table(swtGroup, SWT.BORDER);				
		cfgTable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 3, 0));
		cfgTable.setHeaderVisible(true);
		cfgTable.setLinesVisible(true);
		
		TableColumn cols[] = new TableColumn[colNames.length];
		for(int i=0; i < colNames.length; i++){
			cols[i] = new TableColumn(cfgTable, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
			cols[i].setText(colNames[i]); 
			cols[i].pack();
		}

		proc.loadMapPoints(-1);
		configToTable();
		
		cfgTableEditor = new TableEditor(cfgTable);
		cfgTableEditor.horizontalAlignment = SWT.LEFT;
		cfgTableEditor.grabHorizontal = true;
		cfgTable.addListener(SWT.MouseDown, new Listener() { @Override public void handleEvent(Event event) { tableMouseDownEvent(event); } });
		
		Label lOP = new Label(swtGroup, SWT.NONE); lOP.setText("Load alternate pulse:");		
		altPulseSpiner = new Spinner(swtGroup, SWT.NONE);
		altPulseSpiner.setValues(0, 0, Integer.MAX_VALUE, 0, 1, 10);
		altPulseSpiner.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 2, 1));
		
		loadPointsButton = new Button(swtGroup, SWT.PUSH);
		loadPointsButton.setText("Load");
		loadPointsButton.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { loadAltPulseButtonEvent(event); } });
			
		swtGroup.pack();
		update();
	}
		
	private void autoUpdateEvent(Event e){
		proc.setAutoCalc(autoUpdateCheckbox.getSelection());
	}

	private void forceUpdateEvent(Event e){
		proc.invalidate();
		proc.calc();
	}
	
	private void loadAltPulseButtonEvent(Event e) {
		proc.loadMapPoints(altPulseSpiner.getSelection());		
	}

	private void configToTable() {
		
		Set<Entry<String, double[]>> dataSet = proc.getConfigMap().entrySet();
		
		//convert map to list and sort
		LinkedList<Entry<String, double[]>> dataList = new LinkedList<Entry<String,double[]>>(dataSet);
		Collections.sort(dataList, new Comparator<Entry<String, double[]>>() {
			@Override
			public int compare(Entry<String, double[]> o1,
					Entry<String, double[]> o2) {				
				return o1.getKey().compareTo(o2.getKey());
			}
		});
		
		//clear and repopulate table
		cfgTable.removeAll();
		for(Entry<String, double[]> entry : dataList){
			String pointName = entry.getKey();
			if(pointName.length() <= 0)
				continue;
			TableItem item = new TableItem(cfgTable, SWT.NONE);
			item.setText(0, pointName);
			double data[] = entry.getValue();
			if(data == null)
				data = new double[colNames.length-1];
			for(int i=0; i < Math.min(data.length, colNames.length-1); i++){
				
				item.setText(i+1, tableValueFormat[i].format(data[i]));
			}
		}
		
		//and finally a blank item for creating new point
		TableItem blankItem = new TableItem(cfgTable, SWT.NONE);
		blankItem.setText(0, "");
	}
		
	
	@Override
	public void generalControllerUpdate() {
		if(swtGroup.isDisposed())
			return;
		IMSEProc.ensureFinalSWTUpdate(swtGroup.getDisplay(), this, new Runnable() {
			@Override
			public void run() { update(); }
		});
	}
	
	private void update(){
		boolean autoUpdate = proc.getAutoCalc();
		if(autoUpdateCheckbox.getSelection() != autoUpdate)
			autoUpdateCheckbox.setSelection(autoUpdate);
		

		if(tableItemEditing == null)
			configToTable();
		
		int stat = proc.getCalcProgress();
		statusLabel.setText((stat < 0) ? "Idle" : (stat + " / " + proc.getConnectedSource().getNumImages()));
		
	}

	@Override
	public void movingPos(int x, int y) {	}

	@Override
	public void fixedPos(int x, int y) { }

	@Override
	public void setRect(int x0, int y0, int width, int height) {
		
	}
	
	@Override
	public void drawOnImage(GC gc, double scale, int imageWidth, int imageHeight, boolean asSource) {
		
	}
	
	/** Copied from 'Snippet123'  Copyright (c) 2000, 2004 IBM Corporation and others. 
	 * [ org/eclipse/swt/snippets/Snippet124.java ] */
	private void tableMouseDownEvent(Event event){
		
		Rectangle clientArea = cfgTable.getClientArea ();
		Point pt = new Point (event.x, event.y);
		int index = cfgTable.getTopIndex ();
		while (index < cfgTable.getItemCount ()) {
			boolean visible = false;
			final TableItem item = cfgTable.getItem (index);
			for (int i=0; i<cfgTable.getColumnCount (); i++) {
				Rectangle rect = item.getBounds (i);
				if (rect.contains (pt)) {
					final int column = i;
					final Text text = new Text(cfgTable, SWT.NONE);
					tableItemEditing = item;
					Listener textListener = new Listener () {
						public void handleEvent (final Event e) {
							switch (e.type) {
							case SWT.FocusOut:
								tableColumnModified(item, column, text.getText());								
								text.dispose ();
								tableItemEditing = null;
								break;
							case SWT.Traverse:
								switch (e.detail) {
								case SWT.TRAVERSE_RETURN:
									tableColumnModified(item, column, text.getText());
									//FALL THROUGH
								case SWT.TRAVERSE_ESCAPE:
									text.dispose ();
									tableItemEditing = null;
									e.doit = false;
								}
								break;
							}
						}
					};
					text.addListener (SWT.FocusOut, textListener);
					text.addListener (SWT.Traverse, textListener);
					cfgTableEditor.setEditor (text, item, i);
					text.setText (item.getText (i));
					text.selectAll ();
					text.setFocus ();
					return;
				}
				if (!visible && rect.intersects (clientArea)) {
					visible = true;
				}
			}
			if (!visible) return;
			index++;
		}
	}
	
	private void tableColumnModified(TableItem item, int column, String text){
		HashMap<String, double[]> pointsMap = proc.getConfigMap();
		
		String configName = item.getText(0);
		double configData[] = (configName.length() > 0) 
								? pointsMap.get(configName) 
								: new double[colNames.length-1];
				
		if(column == 0){
			//changed point name (or new point)
			if(configName.length() > 0)
				pointsMap.remove(configName);
			
			configName = text;
			item.setText(column, text);
			
		}else{
			double val = OneLiners.mustParseDouble(text);
			configData[column-1] = val;
			item.setText(column, tableValueFormat[column-1].format(val));			
		}

		pointsMap.put(configName, configData); //put it back
		
		TableItem lastItem = cfgTable.getItem(cfgTable.getItemCount()-1);
		if(lastItem.getText(0).length() > 0){
			TableItem blankItem = new TableItem(cfgTable, SWT.NONE);
			blankItem.setText(0, "");
		}
		
		proc.saveConfig();
		proc.configModified();
	}

	@Override
	public void destroy() {
		swtGroup.dispose();
		proc.controllerDestroyed(this);
		//proc.destroy(); //and actively kill it, for good measure
	}

	@Override
	public Object getInterfacingObject() { return swtGroup;	}

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