package jp.hasc.hasctool.ui.views;

import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.NavigableSet;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;

import jp.hasc.hasctool.core.data.DefaultVectorSignalComparator;
import jp.hasc.hasctool.core.data.LabelInfo;
import jp.hasc.hasctool.core.data.LabelSignalMessage;
import jp.hasc.hasctool.core.data.NullSignalMessage;
import jp.hasc.hasctool.core.data.ObjectSignalMessage;
import jp.hasc.hasctool.core.data.SignalMessage;
import jp.hasc.hasctool.core.data.VectorSignalMessage;
import jp.hasc.hasctool.core.data.VectorSignalMessages;
import jp.hasc.hasctool.core.messaging.MessageProcessor;
import jp.hasc.hasctool.core.runtime.AbstractRuntimeBean;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.ImageLoader;
import org.eclipse.swt.widgets.Display;

/**
 * WaveImageを出力します
 * @author hiro,iwasaki
 */
public class WaveImageWidget extends AbstractRuntimeBean{

	private String filePath_;
	private int height_ = 200;
	private int width_ = 600;

	private boolean showScale_=true;

	private Long timeMarkerBegin_=null, timeMarkerEnd_=null;
	private NavigableSet<VectorSignalMessage> waveData_=new TreeSet<VectorSignalMessage>(new DefaultVectorSignalComparator());
	private NavigableSet<LabelSignalMessage> labelData_=new TreeSet<LabelSignalMessage>();
	private long viewTimeMax_=0;
	private long viewTimeWidth_=SignalMessage.TIME_UNIT.convert(1000, TimeUnit.MILLISECONDS);
	private long viewTimeOffset_=0;
	private double valueMin_=-2;
	private double valueMax_=-2;

	private Color[] waveColores_=new Color[]{new Color(null,0,0,255), new Color(null,255,0,0),
			new Color(null,0,255,0), new Color(null,0,255,255), new Color(null,255,0,255)};
	private Color[] labelColors_=new Color[]{new Color(null,255,255,128), new Color(null,200,255,255), new Color(null,255,200,255),
			new Color(null,255,200,128), new Color(null,200,200,255), new Color(null,200,255,128), new Color(null,200,200,200) };
	private Map<String,Color> labelColorMap_=new HashMap<String,Color>();


	public static double timeToSecond(Long time) {
		return SignalMessage.TIME_UNIT.toMillis(time)/1000.0;
	}

	/** draw view */
	private Image onCanvasPaint(Image img){
		GC gc = new GC(img);
		// bg
		gc.setBackground(new Color(null,255,255,255));
		gc.fillRectangle(0,0,width_,height_);
		// pick labels
		NavigableSet<LabelSignalMessage> labels=null;
		synchronized(labelData_) {
			if (!labelData_.isEmpty()){
				LabelSignalMessage first=labelData_.lower(LabelSignalMessage.createEmpty(viewTimeMax_-viewTimeWidth_));
				if (first==null) first=labelData_.first();
				LabelSignalMessage last=labelData_.ceiling(LabelSignalMessage.createEmpty(viewTimeMax_+1));
				if (last==null) last=labelData_.last();
				labels = labelData_.subSet(first,true,last,true);
			}
		}

		// label BG
		if (labels!=null){
			HashMap<LabelInfo,LabelSignalMessage> beginSigs=new HashMap<LabelInfo, LabelSignalMessage>();
			for(LabelSignalMessage lblSig:labels) {
				gc.setBackground(getLabelBgColor(lblSig.getLabelInfo()));
				switch(lblSig.getType()) {
				case BEGIN:
					beginSigs.put(lblSig.getLabelInfo(), lblSig);
					break;
				case END:
					LabelSignalMessage beginSig = beginSigs.get(lblSig.getLabelInfo());
					int x0=0;
					int x1=getWaveX(lblSig.getTime(), width_);
					if (beginSig!=null) {
						beginSigs.remove(lblSig.getLabelInfo());
						x0=getWaveX(beginSig.getTime(), width_);
					}
					gc.fillRectangle(x0, 0, x1-x0, height_);
					break;
				}
			}
			for(LabelSignalMessage beginSig : beginSigs.values()) {
				gc.setBackground(getLabelBgColor(beginSig.getLabelInfo()));
				int x0=getWaveX(beginSig.getTime(), width_);
				gc.fillRectangle(x0, 0, width_-x0, height_);
			}
			beginSigs.clear();
		}    

		// timeMarker BG
		gc.setBackground(new Color(null,200,200,200));
		if (timeMarkerBegin_!=null && timeMarkerEnd_!=null) {
			int x0=getWaveX(timeMarkerBegin_, width_);
			int x1=getWaveX(timeMarkerEnd_, width_);
			gc.fillRectangle(x0, 0, x1-x0, height_);
		}

		// wave
		do {
			synchronized(waveData_) {
				if (waveData_.isEmpty()) break;
				//
				VectorSignalMessage first=waveData_.lower(VectorSignalMessages.createEmpty(viewTimeMax_-viewTimeWidth_));
				if (first==null) first=waveData_.first();
				VectorSignalMessage last=waveData_.ceiling(VectorSignalMessages.createEmpty(viewTimeMax_+1));
				if (last==null) last=waveData_.last();
				//
				int maxVectorSize=1;
				for(int idx=0;idx<maxVectorSize;++idx) {
					SignalMessage prevElem=null;
					int prevX=0,prevY=0;
					Color color = waveColores_[idx%waveColores_.length];
					gc.setForeground(color);
					gc.setBackground(color);
					for(VectorSignalMessage elem: waveData_.subSet(first,true,last,true)) {
						int vsize = elem.getVectorSize();
						if (vsize<=idx) continue;
						if (maxVectorSize<vsize) maxVectorSize=vsize;
						//
						int x=getWaveX(elem.getTime(), width_);
						int y=getWaveY(elem.getVectorElement(idx), height_);
						if (prevElem!=null) {
							gc.drawLine(prevX, prevY, x, y);
						}
						gc.fillRectangle(x-1, y-1, 3,3);
						prevElem=elem;
						prevX=x; prevY=y;
					}
				}
			}
		}while(false);

		// zero line
		gc.setForeground(new Color(null,0,0,0));
		int zeroy=getWaveY(0.0,height_);
		gc.drawLine(0,zeroy,width_,zeroy);

		// scale
		if (isShowScale()) {
			gc.setForeground(new Color(null,0,0,0));
			FontMetrics fm = gc.getFontMetrics();
			// t scale
			String s;
			s=getTimeString(viewTimeMax_-viewTimeWidth_-viewTimeOffset_);
			int zeroty = zeroy+1; //-fm.getHeight()-1;
			gc.drawText(s, 0, zeroty, true);
			s=getTimeString(viewTimeMax_-viewTimeOffset_);
			gc.drawText(s, width_-gc.stringExtent(s).x-4, zeroty, true);
			// v scale
			if (valueMax_!=0) gc.drawText(""+valueMax_,0,0, true);
			if (valueMin_!=0) gc.drawText(""+valueMin_,0,height_-fm.getHeight(), true);
		}

		// label edges
		if (labels!=null){
			gc.setForeground(new Color(null,0,0,0));
			for(LabelSignalMessage lblSig:labels) {
				gc.setBackground(getLabelBgColor(lblSig.getLabelInfo()));
				int x1=getWaveX(lblSig.getTime(), width_);
				int h=gc.getFontMetrics().getHeight()+4;
				gc.drawLine(x1, 0, x1, height_);
				String lblStr=lblSig.getLabelInfo().getLabel();
				switch(lblSig.getType()) {
				case BEGIN:
					gc.drawText(lblStr, x1+2, height_-h+2, false);
					break;
				case END:
					int w=gc.stringExtent(lblStr).x;
					gc.drawText(lblStr, x1-2-w, height_-h+2, false);
					break;
				}
			}
		}
		return img;
	}

	private Color getLabelBgColor(LabelInfo labelInfo) {
		String lblstr = labelInfo.getLabel();
		Color c=labelColorMap_.get(lblstr);
		if (c!=null) return c;
		c=labelColors_[labelColorMap_.size()%labelColors_.length];
		labelColorMap_.put(lblstr, c);
		return c;
	}

	public static String getTimeString(long t) {
		return (TimeUnit.MILLISECONDS.convert(t, SignalMessage.TIME_UNIT)/1000.f)+"s";
	}

	private int getWaveX(long time, int canvasWidth) {
		int w=canvasWidth-3;
		return 1+(int)(w+w*(time-viewTimeMax_)/viewTimeWidth_);
	}

	private int getWaveY(double d, int canvasHeight) {
		int h=canvasHeight-3;
		return 1+(int)(h-h*(d-valueMin_)/(valueMax_ - valueMin_));
	}

	private void imageCapture() {
		Display display = new Display();
		Image image = new Image(display, width_, height_);
		image = onCanvasPaint(image);
		ImageData data = image.getImageData();
		ImageLoader loader = new ImageLoader();
		loader.data = new ImageData[]{data};
		OutputStream outs = getRuntimeContext().getFileStreamProvider().openOutputStream(filePath_);
		loader.save(outs, SWT.IMAGE_PNG);
		try {
			outs.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		display.dispose();
	}

	private MessageProcessor inputPort_=new MessageProcessor() {
		@Override
		public void processMessage(Object message) {
			if (message instanceof VectorSignalMessage) {
				VectorSignalMessage vecMsg=(VectorSignalMessage)message;
				processVectorSignalMessage(vecMsg);
			}else if (message instanceof LabelSignalMessage) {
				processLabelMessage((LabelSignalMessage)message);
			}else if (message instanceof ObjectSignalMessage) {
				ObjectSignalMessage osm=(ObjectSignalMessage)message;
				SignalMessage[] sigs = (SignalMessage[]) osm.getValue();
				if (sigs.length==2 && sigs[0] instanceof VectorSignalMessage && sigs[1] instanceof ObjectSignalMessage) {
					// (VectorSignalMessage, LabelSignalMessage[]) の複合メッセージ
					VectorSignalMessage vecMsg=(VectorSignalMessage)sigs[0];
					processVectorSignalMessage(vecMsg);
					//
					ObjectSignalMessage labelMsgs=(ObjectSignalMessage) sigs[1];
					SignalMessage[] lbls=(SignalMessage[]) labelMsgs.getValue();
					for(SignalMessage lbl:lbls) {
						if (lbl instanceof LabelSignalMessage) {
							processLabelMessage((LabelSignalMessage)lbl);
						}else if (lbl instanceof NullSignalMessage) {
							// NOP
						}else{
						}
					}
				}

			}else if (message==SignalMessage.BEGIN) {
			}else if (message==SignalMessage.END) {
				imageCapture();
			}
		}


		private void processLabelMessage(LabelSignalMessage lbl) {
			labelData_.add(lbl);
		}

		private void processVectorSignalMessage(VectorSignalMessage we) {
			waveData_.add(we);
		}
	};

	public MessageProcessor getInputPort() { return inputPort_; }

	public boolean isShowScale() {
		return showScale_;
	}

	public void setFilePath(String filePath){
		filePath_ = filePath;
	}

	public void setViewTimeMax(long viewTimeMax){
		viewTimeMax_ = viewTimeMax;
	}

	public void setViewTimeWidth(long viewTimeWidth){
		viewTimeWidth_ = viewTimeWidth;
	}
	
	public void setMaxValue(double maxValue){
		valueMax_ = maxValue;
	}

	public void setMinValue(double minValue){
		valueMin_ = minValue;
	}
	
}
