package jp.hasc.hasctool.ui.views;

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NavigableSet;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

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.util.CoreUtil;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
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.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.Label;
import org.eclipse.swt.widgets.Link;
import org.eclipse.swt.widgets.Slider;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;

/**
 * WaveViewのWidgetです
 * @author iwasaki
 */
public class WaveViewWidget extends Composite {
	/**
	 * @author iwasaki
	 */
	private final class CopyLinkSelectionListener implements SelectionListener {
		@Override
		public void widgetSelected(SelectionEvent e) {
			String text=e.text;
			LOG.debug("toClipboard: "+text);
			CoreUtil.copyToClipboard(getShell().getDisplay(), text);          
		}

		@Override
		public void widgetDefaultSelected(SelectionEvent e) {
		}
	}  

	/** logger for this class */
	private final static org.apache.commons.logging.Log LOG = org.apache.commons.logging.LogFactory
	.getLog(WaveViewWidget.class);

	private static final int REFRESH_DELAY_MS = 1000 / 30;

	private Canvas canvas_;
	private Slider slider_;
	private long sliderUnitTime_;
	private Label topLabel_;
	private Link timeLabel_;
	private ToolBar toolBar_;

	private boolean showScale_=true;
	
	// +t/-tボタンで拡大縮小する時の中心位置
	private int timeZoomCenterType_=TIME_ZOOM_CENTER_TYPE_RIGHT;
	private static final int TIME_ZOOM_CENTER_TYPE_RIGHT = 0;
	private static final int TIME_ZOOM_CENTER_TYPE_LEFT = 1;
	private static final int TIME_ZOOM_CENTER_TYPE_CENTER = 2;

	public WaveViewWidget(Composite parent, int style) {
		super(parent,style);
		this.setLayout(new FormLayout());

		toolBar_ = new ToolBar(this, 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("+t");
			ti.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					long pos=getViewTimeMax(), len=getViewTimeWidth();
					setViewTimeWidth(len/2);
					switch(timeZoomCenterType_) {
					case TIME_ZOOM_CENTER_TYPE_RIGHT:
						setViewTimeMax(pos);
						break;
					case TIME_ZOOM_CENTER_TYPE_LEFT:
						setViewTimeMax(pos-len/2);
						break;
					case TIME_ZOOM_CENTER_TYPE_CENTER:
						setViewTimeMax(pos-len/4);
						break;
					}
					doRequestUpdateTimeRangeWithoutFollowSlider();
					doRedraw();
				}
			});
		}
		{
			ToolItem ti=new ToolItem(toolBar_, SWT.NONE);
			ti.setText("-t");
			ti.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					long pos=getViewTimeMax(), len=getViewTimeWidth();
					setViewTimeWidth(len*2);
					switch(timeZoomCenterType_) {
					case TIME_ZOOM_CENTER_TYPE_RIGHT:
						setViewTimeMax(pos);
						break;
					case TIME_ZOOM_CENTER_TYPE_LEFT:
						setViewTimeMax(pos+len);
						break;
					case TIME_ZOOM_CENTER_TYPE_CENTER:
						setViewTimeMax(pos+len/2);
						break;
					}
					doRequestUpdateTimeRangeWithoutFollowSlider();
					doRedraw();
				}
			});
		}
		{
			ToolItem ti=new ToolItem(toolBar_, SWT.NONE);
			ti.setText("[t]");
			ti.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					if (timeMarkerBegin_!=null && timeMarkerEnd_!=null) {
						// 範囲選択中は、そこを拡大
						setViewTimeWidth(timeMarkerEnd_-timeMarkerBegin_);
						setViewTimeMax(timeMarkerEnd_);
						// 範囲選択解除
						timeMarkerEnd_=null; timeMarkerBegin_=null;
						updateTimeLabel();
					}else{
						setViewTimeWidth(waveDataTimeMax_-waveDataTimeMin_);
						setViewTimeMax(waveDataTimeMax_);
					}
					doRequestUpdateTimeRangeWithoutFollowSlider();
					doRedraw();
				}
			});
		}
		{
			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();
				}
			});
		}
		{
			ToolItem ti=new ToolItem(toolBar_, SWT.NONE);
			ti.setText("rt");
			ti.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					if (viewTimeOffset_==0) {
						viewTimeOffset_=waveDataTimeMin_;
					}else{
						viewTimeOffset_=0;
					}
					updateTimeLabel();
					requestRedraw();
				}
			});
		}

		//
		topLabel_ = new Label(this, SWT.WRAP);
		{
			FormData fd = new FormData();
			fd.top=new FormAttachment(0, 1);
			fd.left=new FormAttachment(0, 1);
			fd.right=new FormAttachment(70, -1);
			topLabel_.setLayoutData(fd);
			/*
			String sid = getViewSite().getSecondaryId();
			topLabel_.setText(sid!=null?sid:"");
			 */
		}

		timeLabel_ = new Link(this, SWT.WRAP);
		{
			FormData fd = new FormData();
			fd.top=new FormAttachment(0, 1);
			fd.left=new FormAttachment(topLabel_, 1);
			fd.right=new FormAttachment(toolBar_, -1);
			timeLabel_.setText("time:");
			timeLabel_.setLayoutData(fd);
			timeLabel_.addSelectionListener(new CopyLinkSelectionListener());
		}


		/*
		valueLabel_ = new Link(this, SWT.WRAP);
		{
			FormData fd = new FormData();
			fd.top=new FormAttachment(0, 1);
			fd.left=new FormAttachment(timeLabel_, 1);
			fd.right=new FormAttachment(100, -1);
			valueLabel_.setText("<a>value</a>");
			valueLabel_.setLayoutData(fd);
			valueLabel_.addSelectionListener(new CopyLinkSelectionListener());
		}
		 */

		slider_ = new Slider(this, 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_) {
						synchronized (labelData_) {
							int pos=slider_.getSelection()+slider_.getThumb();
							viewTimeMax_=pos*sliderUnitTime_+waveDataTimeMin_;
						}
					}
					//requestRedraw();
					doRedraw();
				}

				@Override
				public void widgetDefaultSelected(SelectionEvent e) {
				}
			});
		}

		canvas_ = new Canvas(this, 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);
				}
			});

			// time picker
			canvas_.addMouseListener(new MouseListener() {
				int downX_,downY_; 

				@Override
				public void mouseUp(MouseEvent e) {
					if (e.button==1) {
						if (Math.abs(downX_-e.x)>2 || Math.abs(downY_-e.y)>2) {
							// range
							timeMarkerEnd_=getTimeAtCanvasX(e.x,getCanvasWidth());
							if (timeMarkerEnd_<timeMarkerBegin_) {
								// swap
								Long tmp=timeMarkerBegin_; timeMarkerBegin_=timeMarkerEnd_; timeMarkerEnd_=tmp;
							}
							updateTimeLabel();
							requestRedraw();
						}
					}
				}

				@Override
				public void mouseDown(MouseEvent e) {
					if (e.button==1) {
						downX_=e.x;
						downY_=e.y;
						timeMarkerBegin_=getTimeAtCanvasX(downX_,getCanvasWidth());
						// point
						timeMarkerEnd_=null;
						updateTimeLabel();
						requestRedraw();
					}else{
						timeMarkerBegin_=timeMarkerEnd_=null;
						updateTimeLabel();
						requestRedraw();
					}
				}

				@Override
				public void mouseDoubleClick(MouseEvent e) {
				}
			});
		}

		/*
		if (false) {
			// not implemented yet
			MenuManager menuMgr=new MenuManager("#PopupMenu");
			menuMgr.setRemoveAllWhenShown(true);
			menuMgr.addMenuListener(new IMenuListener() {
				@Override
				public void menuAboutToShow(IMenuManager manager) {
					Action action = new Action() {
						@Override
						public void run() {
							// 
						}
					};
					action.setText("time to clipboard");
					manager.add(action);
				}
			});
			Menu menu=menuMgr.createContextMenu(canvas_);
			canvas_.setMenu(menu);
			// 
			//getSite().registerContextMenu(menuMgr, canvas_);
		}
		 */

		//
		clearData(DATA_BITS_ALL);
	}

	protected void updateTimeLabel() {
		if (timeMarkerEnd_!=null && timeMarkerBegin_!=null) {
			timeLabel_.setText("time:<a>"+
					((viewTimeOffset_!=0)?"r ":"")+
					timeToSecond(timeMarkerBegin_-viewTimeOffset_)+","+timeToSecond(timeMarkerEnd_-viewTimeOffset_)+"</a>");
		}else if (timeMarkerBegin_!=null) {
			timeLabel_.setText("time:<a>"+
					((viewTimeOffset_!=0)?"r ":"")+
					timeToSecond(timeMarkerBegin_-viewTimeOffset_)+"</a>");
		}else{
			timeLabel_.setText("time:");
		}
	}

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

	private Long timeMarkerBegin_=null, timeMarkerEnd_=null;
	private int signalDataCapacity_ = -1; // -1なら無制限
	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 long waveDataTimeMin_=0, waveDataTimeMax_=0;
	private double valueMin_=-2;
	private double valueHeight_=-valueMin_*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)};
	
	public static class AlphaColor {
		private int alpha_;
		private Color color_;
		public AlphaColor(int a,int r,int g,int b) {
			alpha_=a;
			color_=new Color(null,r,g,b);
		}
		public int getAlpha() {
			return alpha_;
		}
		public Color getColor() {
			return color_;
		}
		
		/**
		 * 半透明の色（this）を、背景色backgroundと合成して、不透明化した色を返します
		 */
		public Color getOpaqueColor(Color background) {
			Color ret;
			int a=alpha_;
			if (a==255) {
				ret = color_;
			}else{
				int r=color_.getRed();
				int g=color_.getGreen();
				int b=color_.getBlue();
				int r0=background.getRed();
				int g0=background.getGreen();
				int b0=background.getBlue();
				ret = new Color(null,r0*(255-a)/255+r*a/255,g0*(255-a)/255+g*a/255,b0*(255-a)/255+b*a/255);
			}
			return ret;
		}
	}
	
	public static final AlphaColor ALPHA_COLOR_WHITE = new AlphaColor(255,255,255,255);
	private static final AlphaColor ALPHA_COLOR_TIME_MARKER = new AlphaColor(180,170,170,170);
	
	/* 不透明のカラーセット
	private AlphaColor[] labelColors_=new AlphaColor[]{
			new AlphaColor(255,255,255,128), new AlphaColor(255,200,255,255), new AlphaColor(255,255,200,255),
			new AlphaColor(255,255,200,128), new AlphaColor(255,200,200,255), new AlphaColor(255,200,255,128), 
			new AlphaColor(255,64,255,255), new AlphaColor(255,255,64,255) };
	*/
	
	///* 半透明のカラーセット
	private AlphaColor[] labelColors_=new AlphaColor[]{
			new AlphaColor(128,255,255,0), new AlphaColor(128,128,255,255), new AlphaColor(128,255,128,255),
			new AlphaColor(128,255,128,0), new AlphaColor(128,128,128,255), new AlphaColor(128,128,255,0), 
			new AlphaColor(128,0,255,255), new AlphaColor(128,255,0,255) };
	//*/
	
	private int labelColorsIndex_ = 0;
	
	private Map<String,AlphaColor> labelColorMap_=new HashMap<String,AlphaColor>();

	private int getCanvasWidth() {
		return canvas_.getSize().x-canvas_.getBorderWidth()*2;
	}
	private int getCanvasHeight() {
		return canvas_.getSize().y-canvas_.getBorderWidth()*2;
	}
	
	private void setBackgroundAlphaColor(GC gc, AlphaColor ac) {
		gc.setBackground(ac.getColor());
		gc.setAlpha(ac.getAlpha());
	}

	public static final Color COLOR_BLACK=new Color(null,0,0,0);
	
	/** draw view */
	private void onCanvasPaint(PaintEvent e) {
		requestedRedraw_=false;
		//
		GC gc = e.gc;
		int cw=getCanvasWidth(), ch=getCanvasHeight();

		// bg
		setBackgroundAlphaColor(gc, ALPHA_COLOR_WHITE);
		gc.fillRectangle(0,0,cw,ch);

		// 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) {
				setBackgroundAlphaColor(gc, 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(), cw);
					if (beginSig!=null) {
						beginSigs.remove(lblSig.getLabelInfo());
						x0=getWaveX(beginSig.getTime(), cw);
					}
					if (x1>x0) {
						// 別時刻
						gc.fillRectangle(x0, 0, x1-x0, ch);
					}else if (x0==x1){
						// 同時刻
						gc.fillRectangle(x1-3, 0, 7, ch);
					}
					break;
				}
			}
			for(LabelSignalMessage beginSig : beginSigs.values()) {
				setBackgroundAlphaColor(gc, getLabelBgColor(beginSig.getLabelInfo()));
				int x0=getWaveX(beginSig.getTime(), cw);
				gc.fillRectangle(x0, 0, cw-x0, ch);
			}
			beginSigs.clear();
		}    

		// timeMarker BG
		setBackgroundAlphaColor(gc, ALPHA_COLOR_TIME_MARKER);
		gc.setAlpha(180);
		if (timeMarkerBegin_!=null && timeMarkerEnd_!=null) {
			int x0=getWaveX(timeMarkerBegin_, cw);
			int x1=getWaveX(timeMarkerEnd_, cw);
			gc.fillRectangle(x0, 0, x1-x0, ch);
		}
		setBackgroundAlphaColor(gc, ALPHA_COLOR_WHITE);

		// wave lines
		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(), cw);
						int y=getWaveY(elem.getVectorElement(idx), ch);
						if (prevElem!=null) {
							if( prevX == x)continue; // for quick Draw! // must get max val..
							gc.drawLine(prevX, prevY, x, y);
						}
						if( prevX < x-2){ // if there is a distance between dot, then draw.
							gc.fillRectangle(x-1, y-1, 3,3); // stop draw box
						}
						prevElem=elem;
						prevX=x; prevY=y;
					}
				}
			}
		}while(false);

		// zero line
		gc.setForeground(COLOR_BLACK);
		int zeroy=getWaveY(0.0,ch);
		gc.drawLine(0,zeroy,cw,zeroy);

		// scale
		if (isShowScale()) {
			gc.setForeground(COLOR_BLACK);
			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, cw-gc.stringExtent(s).x-4, zeroty, 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);
		}

		// label edges
		if (labels!=null){
			gc.setForeground(COLOR_BLACK);
			HashMap<LabelInfo,LabelSignalMessage> beginSigs=new HashMap<LabelInfo, LabelSignalMessage>();
			for(LabelSignalMessage lblSig:labels) {
				switch(lblSig.getType()) {
				case BEGIN:
					beginSigs.put(lblSig.getLabelInfo(), lblSig);
					break;
				case END:
					LabelSignalMessage beginSig = beginSigs.get(lblSig.getLabelInfo());
					drawLabelEdge(gc, cw, ch, lblSig, beginSig, lblSig);
					break;
				}
			}
			for(LabelSignalMessage beginSig : beginSigs.values()) {
				drawLabelEdge(gc, cw, ch, beginSig, beginSig, null);
			}
			beginSigs.clear();
		}
		setBackgroundAlphaColor(gc, ALPHA_COLOR_WHITE);

		// timeMarker edge
		Color colorTimeMarkerEdge = new Color(null,64,64,128);
		gc.setForeground(colorTimeMarkerEdge);
		if (timeMarkerBegin_!=null) {
			int x=getWaveX(timeMarkerBegin_, cw);
			gc.drawLine(x, 0, x, ch);
		}
		if (timeMarkerEnd_!=null) {
			int x=getWaveX(timeMarkerEnd_, cw);
			gc.drawLine(x, 0, x, ch);
		}		

	}

	private void drawLabelEdge(GC gc, int cw, int ch,
			LabelSignalMessage lblSig, LabelSignalMessage beginSig, LabelSignalMessage endSig) {
		gc.setForeground(COLOR_BLACK);
		//
		AlphaColor labelColor=getLabelBgColor(lblSig.getLabelInfo());
		// 白と半透明のラベル色を合成した色を、不透明色で塗る
		boolean transparent=false;
		gc.setAlpha(255);
		gc.setBackground(labelColor.getOpaqueColor(ALPHA_COLOR_WHITE.getColor()));
		//
		String lblStr=lblSig.getLabelInfo().getLabel();
		int h=gc.getFontMetrics().getHeight()+4;
		
		if (beginSig!=null && endSig!=null && beginSig.getTime()==endSig.getTime()) {
			// 同時刻
			int x1=getWaveX(beginSig.getTime(), cw);
			gc.setForeground(COLOR_BLACK);
			//gc.fillRectangle(x1-3, 0, 7, ch);
			gc.drawLine(x1, 0, x1, ch);
			gc.drawText(lblStr, x1+2+3, ch-h+2, transparent);
		}else{
			// 別時刻
			gc.setForeground(COLOR_BLACK);
			if (beginSig!=null) {
				int x1=getWaveX(beginSig.getTime(), cw);
				gc.drawLine(x1, 0, x1, ch);
				gc.drawText(lblStr, x1+2, ch-h+2, transparent);
			}
			if (endSig!=null) {
				int x1=getWaveX(endSig.getTime(), cw);
				gc.drawLine(x1, 0, x1, ch);
				int w=gc.stringExtent(lblStr).x;
				gc.drawText(lblStr, x1-2-w, ch-h+2, transparent);
			}
		}
	}

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

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

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

	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_)/valueHeight_);
	}

	/*
	@Override
	public void setFocus() {
	}
	 */

	private boolean requestedUpdateTimeRangeAndRedraw=false;

	/** should be called from SWT thread */
	private void doRequestedUpdateTimeRange() {
		boolean empty=true;
		synchronized(waveData_) {
			if (!waveData_.isEmpty()) {

				// trim waveData within capacity
				if (signalDataCapacity_>=0) {
					int trimmingNum=waveData_.size()-signalDataCapacity_;
					if (trimmingNum>0) {
						for(Iterator<VectorSignalMessage> it=waveData_.iterator(); it.hasNext() && trimmingNum>0; --trimmingNum) {
							it.next();
							it.remove();
						}
					}
				}

				// range
				waveDataTimeMin_=waveData_.first().getTime();
				waveDataTimeMax_=waveData_.last().getTime();
				empty=false;
			}
		}
		synchronized (labelData_) {
			if (!labelData_.isEmpty()) {
				// todo: trim LabelData？

				// 表示範囲を、ラベルの範囲に拡張
				long labelTimeMin=labelData_.first().getTime();
				long labelTimeMax=labelData_.last().getTime();
				if (empty || waveDataTimeMin_>labelTimeMin) waveDataTimeMin_=labelTimeMin;
				if (empty || waveDataTimeMax_<labelTimeMax) waveDataTimeMax_=labelTimeMax;
				empty=false;
			}
		}
		if (empty) {
			waveDataTimeMin_=0;
			waveDataTimeMax_=0;
		}

		// auto scroll
		if (followSlider_) {
			viewTimeMax_=waveDataTimeMax_;
		}

		// update slider
		if (!slider_.isDisposed()) {
			slider_.setMaximum((int)((waveDataTimeMax_-waveDataTimeMin_)/sliderUnitTime_));
			slider_.setThumb((int)(viewTimeWidth_/sliderUnitTime_));
		}
		setViewTimeMax(viewTimeMax_);
	}

	public void preventUpdates(boolean b) {
		if (b) {
			requestedUpdateTimeRangeAndRedraw=true;
			requestedRedraw_=true;
		}else{
			requestedUpdateTimeRangeAndRedraw=false;
			doRequestedUpdateTimeRange();
			doRedraw();
		}
	}

	public void requestUpdateTimeRangeAndRedraw() {
		if (requestedUpdateTimeRangeAndRedraw) return;
		requestedUpdateTimeRangeAndRedraw=true;
		requestedRedraw_=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;

	private boolean followSlider_ = true;

	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) {
		getDisplay().asyncExec(new Runnable() {
			@Override
			public void run() {
				getDisplay().timerExec(milliseconds, runnable);
			}
		});
	}

	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{
							LOG.warn("Unknown signal:"+lbl);
						}
					}
				}

			}else if (message==SignalMessage.BEGIN) {
				clearData(clearingDataWhenBeginMessage_);
			}else if (message==SignalMessage.END) {
				LOG.debug("got END message.");
			}else if (message instanceof String) {
				//LOG.debug("string message: "+message);
				String s=(String) message;
				if (s.startsWith(HEADER_PREFIX_LABELS)) {
					processLabelsHeader(s.substring(HEADER_PREFIX_LABELS.length()));
				}
			}
		}
		
		/**
		 * labelファイルの、"#labels:"の行を処理します
		 */
		private void processLabelsHeader(String param) {
			labelColorMap_.clear();
			labelColorsIndex_=0;
			//
			String[] args = param.split(",");
			for(String arg:args) {
				Matcher m = patLabelsHeaderArg__.matcher(arg.trim());
				if (m.matches()) {
					String name=m.group(1).trim();
					String colStr=m.group(2);
					if (colStr!=null) {
						int a=255;
						if (colStr.length()==8) {
							a=CoreUtil.hexToInt(colStr.substring(0,2));
							colStr=colStr.substring(2);
						}
						AlphaColor ac=new AlphaColor(a, 
								CoreUtil.hexToInt(colStr.substring(0,2)), 
								CoreUtil.hexToInt(colStr.substring(2,4)), 
								CoreUtil.hexToInt(colStr.substring(4,6)));
						labelColorMap_.put(name, ac);
					}else{
						labelColorMap_.put(name, getNextLabelColor());
					}
				}else{
					LOG.warn("Unknown label format: "+arg);
				}
			}
		}
		
		private void processLabelMessage(LabelSignalMessage lbl) {
			//LOG.debug("WaveView: labelSig "+lbl);
			synchronized(labelData_) {
				labelData_.add(lbl);
				requestUpdateTimeRangeAndRedraw();
				//requestRedraw();
			}
		}

		private void processVectorSignalMessage(VectorSignalMessage we) {
			synchronized(waveData_) {
				waveData_.add(we);
				requestUpdateTimeRangeAndRedraw();
				//requestRedraw();
			}
		}
	};
	
	public static final String HEADER_PREFIX_LABELS = "#labels:";
	private static Pattern patLabelsHeaderArg__ = Pattern.compile("(.+?)(?:\\(color=([0-9a-fA-F]{6}|[0-9a-fA-F]{8})\\))?");

	void setVectorData(Collection<VectorSignalMessage> waveData) {
		synchronized(waveData_) {
			waveData_.clear();
			waveData_.addAll(waveData);
			requestUpdateTimeRangeAndRedraw();
		}
	}

	void setLabelData(Collection<LabelSignalMessage> labelData) {
		synchronized(labelData_) {
			labelData_.clear();
			labelData_.addAll(labelData);
			requestUpdateTimeRangeAndRedraw();
		}
	}

	public MessageProcessor getInputPort() { return inputPort_; }

	public static final int DATA_BIT_LABEL=2;
	public static final int DATA_BIT_WAVE=1;
	public static final int DATA_BITS_ALL=DATA_BIT_LABEL|DATA_BIT_WAVE;

	private int clearingDataWhenBeginMessage_=DATA_BITS_ALL;

	public void clearData(int dataToClear) {
		if ((dataToClear&DATA_BIT_LABEL)!=0) {
			synchronized (labelData_) {
				labelData_.clear();
			}
		}
		if ((dataToClear&DATA_BIT_WAVE)!=0) {
			synchronized(waveData_) {
				waveData_.clear();
				waveDataTimeMin_=0;
				waveDataTimeMax_=0;
				//viewTimeMax_=0;
				requestUpdateTimeRangeAndRedraw();
			}
		}
	}


	/*
	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 long getViewTimeWidth() {
		return viewTimeWidth_;
	}

	public void setViewTimeWidth(long viewTimeWidth) {
		viewTimeWidth_ = viewTimeWidth;
	}

	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 showGraduations) {
		showScale_ = showGraduations;
	}

	public void setTitleName(String s) {
		topLabel_.setText(s);
	}

	public boolean isFollowSlider() {
		return followSlider_;
	}

	public void setFollowSlider(boolean followSlider) {
		followSlider_ = followSlider;
	}

	public long getViewTimeMax() {
		return viewTimeMax_;
	}
	
	private void setViewTimeMaxSub_clipLeft() {
		if (viewTimeMax_<waveDataTimeMin_+viewTimeWidth_) viewTimeMax_=waveDataTimeMin_+viewTimeWidth_;
	}
	
	private void setViewTimeMaxSub_clipRight() {
		if (viewTimeMax_>waveDataTimeMax_) viewTimeMax_=waveDataTimeMax_;
	}

	public void setViewTimeMax(long viewTimeMax) {
		viewTimeMax_ = viewTimeMax;
		
		// 波形の存在する領域が、Viewの範囲より小さい場合
		switch(timeZoomCenterType_) {
		case TIME_ZOOM_CENTER_TYPE_RIGHT:
			// 右よせ
			setViewTimeMaxSub_clipLeft();
			setViewTimeMaxSub_clipRight();
			break;
		case TIME_ZOOM_CENTER_TYPE_LEFT:
			// 左よせ
			setViewTimeMaxSub_clipRight();
			setViewTimeMaxSub_clipLeft();
			break;
		case TIME_ZOOM_CENTER_TYPE_CENTER:
			// nothing
			break;
		}
		
		//
		if (!slider_.isDisposed()) {
			slider_.setSelection((int)((viewTimeMax_-waveDataTimeMin_)/sliderUnitTime_)-slider_.getThumb());
		}
	}

	public long getViewTimeMin() {
		return viewTimeMax_ - viewTimeWidth_;
	}

	public void setViewTimeMin(long viewTimeMin) {
		setViewTimeMax(viewTimeMin+viewTimeWidth_);
	}

	public ToolBar getToolBar() {
		return toolBar_;
	}

	public Long getTimeMarkerBegin() {
		return timeMarkerBegin_;
	}

	public Long getTimeMarkerEnd() {
		return timeMarkerEnd_;
	}

	public int getClearingDataWhenBeginMessage() {
		return clearingDataWhenBeginMessage_;
	}

	public void setClearingDataWhenBeginMessage(int clearDataWhenBeginMessage) {
		clearingDataWhenBeginMessage_ = clearDataWhenBeginMessage;
	}

	public double getAudioCursorTime()
	{
		long at;
		if (timeMarkerEnd_!=null) {
			at = timeMarkerEnd_;
		}else if (timeMarkerBegin_!=null) {
			at = timeMarkerBegin_;
		}else{
			return 0;
		}
		long rt=at - waveDataTimeMin_;
		return TimeUnit.MICROSECONDS.convert(rt, SignalMessage.TIME_UNIT)/1000000.0;
	}

	public void setAudioCursorTime(double t)
	{
		long rt = SignalMessage.TIME_UNIT.convert((long)(t*1000000.0), TimeUnit.MICROSECONDS);
		long at=rt + waveDataTimeMin_;
		if (timeMarkerBegin_!=null && timeMarkerEnd_!=null && timeMarkerBegin_<at) {
			timeMarkerEnd_ = at;
		}else{
			timeMarkerBegin_ = at;
			timeMarkerEnd_=null;
		}
		setViewTimeMin(timeMarkerBegin_ - viewTimeWidth_/2);
		updateTimeLabel();
		requestRedraw();
	}

	private void doRequestUpdateTimeRangeWithoutFollowSlider() {
		boolean followSliderOld=followSlider_;
		followSlider_=false;
		doRequestedUpdateTimeRange();
		followSlider_=followSliderOld;
	}
}
