package jp.hasc.hasctool.ui.views;

import java.util.Iterator;
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.SignalMessage;
import jp.hasc.hasctool.core.data.VectorSignalMessage;
import jp.hasc.hasctool.core.data.VectorSignalMessages;
import jp.hasc.hasctool.core.messaging.MessageProcessor;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Slider;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.part.ViewPart;

/**
 *　ある時刻でのベクトル要素を表示するビューです
 * @author iwasaki, hiro
 */
public class VectorView extends ViewPart {

	private static final int REFRESH_DELAY_MS = 1000 / 30;
	public static final String ID = "jp.hasc.hasctool.ui.views.VectorView";
	private Canvas canvas_;
	private Slider slider_;
	private long sliderUnitTime_;
	private Label topLabel_;
	private Display display_;
	private boolean showScale_=true;
	private ToolBar toolBar_;
	
	@Override
	public void createPartControl(Composite parent) {
		display_=parent.getDisplay();
		parent.setLayout(new FormLayout());
		
		toolBar_ = new ToolBar(parent, SWT.VERTICAL);
		{
			FormData fd = new FormData();
			fd.top=new FormAttachment(0, 1);
			fd.right=new FormAttachment(100, -1);
			fd.bottom=new FormAttachment(100, -1);
			toolBar_.setLayoutData(fd);
		}
		{
			ToolItem ti=new ToolItem(toolBar_, SWT.NONE);
			ti.setText("+v");
			ti.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					setValueHeight(getValueHeight()/2);
					setValueMin(getValueMin()/2);
					requestRedraw();
				}
			});
		}
		{
			ToolItem ti=new ToolItem(toolBar_, SWT.NONE);
			ti.setText("-v");
			ti.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					setValueHeight(getValueHeight()*2);
					setValueMin(getValueMin()*2);
					requestRedraw();
				}
			});
		}
		
		topLabel_ = new Label(parent, SWT.WRAP);
		{
			FormData fd = new FormData();
			fd.top=new FormAttachment(0, 1);
			fd.left=new FormAttachment(0, 1);
			fd.right=new FormAttachment(toolBar_, -1);
			topLabel_.setLayoutData(fd);
		}
		
		slider_ = new Slider(parent, SWT.NONE);
		{
			FormData fd = new FormData();
			fd.left=new FormAttachment(0, 1);
			fd.bottom=new FormAttachment(100, -1);
			fd.right=new FormAttachment(toolBar_, -1);
			sliderUnitTime_ = SignalMessage.TIME_UNIT.convert(1, TimeUnit.MILLISECONDS);
			slider_.setLayoutData(fd);
			slider_.setMinimum(0);
			slider_.setIncrement(10);
			slider_.addSelectionListener(new SelectionListener() {
				
				@Override
				public void widgetSelected(SelectionEvent e) {
					synchronized (waveData_) {
						if (!waveData_.isEmpty()) {
							int pos=slider_.getSelection()+slider_.getThumb();
							viewTime_=pos*sliderUnitTime_+waveDataTimeMin_;
						}
					}
					//requestRedraw();
					doRedraw();
				}
				
				@Override
				public void widgetDefaultSelected(SelectionEvent e) {
				}
			});
		}
		
		canvas_ = new Canvas(parent, SWT.BORDER | SWT.DOUBLE_BUFFERED);
		{
			FormData fd = new FormData();
			fd.top=new FormAttachment(topLabel_, 1);
			fd.left=new FormAttachment(0, 1);
			fd.bottom=new FormAttachment(slider_, -1);
			fd.right=new FormAttachment(toolBar_, -1);
			canvas_.setLayoutData(fd);
			canvas_.addPaintListener(new PaintListener() {
				@Override
				public void paintControl(PaintEvent e) {
					onCanvasPaint(e);
				}
			});
		}
		
		clearWaveData();
	}
	
	private int signalDataCapacity_ = 65536;
	private NavigableSet<VectorSignalMessage> waveData_=new TreeSet<VectorSignalMessage>(new DefaultVectorSignalComparator());
	private long viewTime_=0;
	private long waveDataTimeMin_=0, waveDataTimeMax_=0;
	private double valueMin_=-2;
	private double valueHeight_=-valueMin_*2;
	
	//private double valueMin_=0;
	//private double valueHeight_=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)};
	
	/** draw vector */
	private void onCanvasPaint(PaintEvent e) {
		requestedRedraw_=false;
		//
		GC gc = e.gc;
		Point csize = canvas_.getSize();
		int cw=csize.x-canvas_.getBorderWidth(), ch=csize.y-canvas_.getBorderWidth();
		// bg
		gc.setBackground(new Color(null,255,255,255));
		gc.fillRectangle(0,0,cw,ch);
		// zero line
		gc.setForeground(new Color(null,0,0,0));
		int zeroy=getWaveY(0.0,ch);
		gc.drawLine(0,zeroy,cw,zeroy);

		// vector
		VectorSignalMessage last=null;
		do {
			synchronized(waveData_) {
				if (waveData_.isEmpty()) break;
				//
				last=waveData_.ceiling(VectorSignalMessages.createEmpty(viewTime_+1));
				if (last==null) last=waveData_.last();
				//
					int prevX=0,prevY=0;
					Color color = waveColores_[0];
					gc.setForeground(color);
					gc.setBackground(color);
					VectorSignalMessage elem=last;
					int vectorSize=elem.getVectorSize();
					for(int idx=0;idx<vectorSize;++idx) {
					{
						int x=getWaveX(idx, vectorSize, cw);
						int y=getWaveY(elem.getVectorElement(idx), ch);
						if (idx!=0) {
							gc.drawLine(prevX, prevY, x, y);
						}
						gc.fillRectangle(x-1, y-1, 3,3);
						prevX=x; prevY=y;
					}
				}
			}
		}while(false);
		
		// scale
		if (isShowScale()) {
			gc.setForeground(new Color(null,0,0,0));
			FontMetrics fm = gc.getFontMetrics();
			// t scale
			if (last!=null) {
				String s="t="+WaveViewWidget.getTimeString(last.getTime());
				gc.drawText(s, cw-gc.stringExtent(s).x-4, 0,true);
			}
			// v scale
			double valueMax = valueMin_+valueHeight_;
			if (valueMax!=0) gc.drawText(""+valueMax,0,0,true);
			if (valueMin_!=0) gc.drawText(""+valueMin_,0,ch-fm.getHeight(),true);
			gc.setForeground(new Color(null,0,0,0));
			gc.setBackground(new Color(null,255,255,255));
		}
	}

	private int getWaveX(int idx, int vectorSize, int canvasWidth) {
		int w=canvasWidth-20;
		if (vectorSize>1) {
			return 10+(int)(w*idx/(vectorSize-1));
		}else{
			return 10+w/2;
		}
	}

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

	@Override
	public void setFocus() {
	}
	
	private boolean requestedUpdateTimeRangeAndRedraw=false;
	
	/** should be called from SWT thread */
	private void doRequestedUpdateTimeRange() {
		synchronized(waveData_) {
			if (!waveData_.isEmpty()) {
				
				/*
				// delete unnecessary data
				VectorSignalMessage viewFirst=waveData_.lower(VectorSignalMessage.create(viewTimeMax_-viewTimeWidth_, NULL_VECTOR_VALUE));
				if (viewFirst!=null) {
					for(Iterator<VectorSignalMessage> it=waveData_.iterator();it.hasNext();) {
						VectorSignalMessage we=it.next();
						if (we==viewFirst) break;
						it.remove();
					}
				}
				*/
				
				// trim waveData within capacity
				int trimmingNum=waveData_.size()-signalDataCapacity_;
				if (trimmingNum>0) {
					for(Iterator<VectorSignalMessage> it=waveData_.iterator(); it.hasNext() && trimmingNum>0; --trimmingNum) {
						it.next();
						it.remove();
					}
				}
				
				boolean followSlider=true; // viewTimeMax_==waveDataTimeMax_; 一度動かすと右端に戻せない場合が
				
				// range
				waveDataTimeMin_=waveData_.first().getTime();
				waveDataTimeMax_=waveData_.last().getTime();
				
				// auto scroll
				if (followSlider) viewTime_=waveDataTimeMax_;
			}
		}
		
		// update slider
		if (!slider_.isDisposed()) {
				slider_.setMaximum((int)((waveDataTimeMax_-waveDataTimeMin_)/sliderUnitTime_));
				slider_.setThumb(0); //(int)(viewTimeWidth_/sliderUnitTime_));
				slider_.setSelection((int)((viewTime_-waveDataTimeMin_)/sliderUnitTime_)-slider_.getThumb());
		}
	}
	
	private void requestUpdateTimeRangeAndRedraw() {
		if (requestedUpdateTimeRangeAndRedraw) return;
		requestedUpdateTimeRangeAndRedraw=true;
		asyncTimerExec(REFRESH_DELAY_MS, new Runnable() {
			@Override
			public void run() {
				requestedUpdateTimeRangeAndRedraw=false;
				doRequestedUpdateTimeRange();
				doRedraw();
			}
		});
	}
	
	/** should be called from SWT thread */
	private void doRedraw() {
		if (!canvas_.isDisposed()) canvas_.redraw();
	}
	
	private boolean requestedRedraw_=false;
	
	public void requestRedraw() {
		if (requestedRedraw_) return;
		requestedRedraw_=true;
		asyncTimerExec(REFRESH_DELAY_MS, new Runnable() {
			@Override
			public void run() {
				doRedraw();
			}
		});
	}
	
	private void asyncTimerExec(final int milliseconds, final Runnable runnable) {
		display_.asyncExec(new Runnable() {
			@Override
			public void run() {
				display_.timerExec(milliseconds, runnable);
			}
		});
	}
	
	private MessageProcessor inputPort_=new MessageProcessor() {
		@Override
		public void processMessage(Object message) {
			if (message instanceof VectorSignalMessage) {
				VectorSignalMessage we=(VectorSignalMessage)message;
				synchronized(waveData_) {
					waveData_.add(we);
					requestUpdateTimeRangeAndRedraw();
					//requestRedraw();
				}
			}else if (message==SignalMessage.BEGIN) {
				clearWaveData();
			}else if (message==SignalMessage.END) {
				// NOP
			}
		}
	};
	
	private void clearWaveData() {
		synchronized(waveData_) {
			waveData_.clear();
			waveDataTimeMin_=0;
			viewTime_=waveDataTimeMax_=0;
			requestUpdateTimeRangeAndRedraw();
		}
	}
	
	public MessageProcessor getInputPort() { return inputPort_; }
	
	private void onInit(IViewSite site) {
	}
	
	@Override
	public void init(IViewSite site, IMemento memento) throws PartInitException {
		super.init(site, memento);
		onInit(site);
	}

	@Override
	public void init(IViewSite site) throws PartInitException {
		super.init(site);
		onInit(site);
	}

	public int getSignalDataCapacity() {
		return signalDataCapacity_;
	}

	public void setSignalDataCapacity(int signalDataCapacity) {
		signalDataCapacity_ = signalDataCapacity;
	}

	public double getValueMin() {
		return valueMin_;
	}

	public void setValueMin(double valueMin) {
		valueMin_ = valueMin;
	}

	public double getValueHeight() {
		return valueHeight_;
	}

	public void setValueHeight(double valueHeight) {
		valueHeight_ = valueHeight;
	}

	public boolean isShowScale() {
		return showScale_;
	}

	public void setShowScale(boolean showScale) {
		showScale_ = showScale;
	}
	
	public void setTitleName(String s) {
		topLabel_.setText(s);
	}
}
