package jp.hasc.hasctool.ui.views;

import java.io.StringReader;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import jp.hasc.hasctool.core.messaging.MessageProcessor;
import jp.hasc.hasctool.core.runtime.RuntimeContext;
import jp.hasc.hasctool.core.runtime.filter.file.CSVToLabelFilter;
import jp.hasc.hasctool.core.runtime.filter.file.CSVToVectorFilter;
import jp.hasc.hasctool.core.runtime.filter.file.label.LabelFileParser;
import jp.hasc.hasctool.core.runtime.filter.file.label.LabelLine;
import jp.hasc.hasctool.core.runtime.filter.file.label.TargetFileReader;
import jp.hasc.hasctool.core.runtime.source.LineReaderSource;
import jp.hasc.hasctool.ui.commands.ResourceStreamProvider;
import jp.hasc.hasctool.ui.util.UIUtil;

import org.eclipse.jface.text.ITextOperationTarget;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.ui.IFileEditorInput;

/**
 * Labelファイルのエディタです
 * @author iwasaki
 */
public class LabelEditor extends AbstractWaveEditor {
	/** logger for this class */
	private final static org.apache.commons.logging.Log LOG = org.apache.commons.logging.LogFactory
			.getLog(LabelEditor.class);
	
	private static final long LONG_EPS = 1000; // 1ms
	
	@Override
	protected void onUpdateWaveEditorViewContents(String editorText) throws Exception {
		long pos=waveViewWidget_.getViewTimeMax();
		waveViewWidget_.preventUpdates(true);
		waveViewWidget_.clearData(WaveViewWidget.DATA_BITS_ALL);
		int oldClearingDataWhenBeginMessage=waveViewWidget_.getClearingDataWhenBeginMessage();
		waveViewWidget_.setClearingDataWhenBeginMessage(0);
		
		//
		RuntimeContext context=new RuntimeContext();
		context.setFileStreamProvider(createResourceStreamProvider(context));
		
		// setup blocks
		LineReaderSource lr=new LineReaderSource();
		lr.setReader(new StringReader(editorText));
		lr.setup(context);
		//
		CSVToLabelFilter csvToLabel=new CSVToLabelFilter();
		csvToLabel.setup(context);
		//
		TargetFileReader targetFileReader=new TargetFileReader();
		targetFileReader.setStartReader(false);
		targetFileReader.setup(context);
		//
		CSVToVectorFilter csvToVector=new CSVToVectorFilter();
		csvToVector.setup(context);
		
		// setup connections
		lr.getOutputPort().connect(csvToLabel.getInputPort());
		csvToLabel.getOutputPort().connect(waveViewWidget_.getInputPort());
		//
		lr.getOutputPort().connect(targetFileReader.getInputPort());
		targetFileReader.getOutputPort().connect(csvToVector.getInputPort());
		csvToVector.getOutputPort().connect(waveViewWidget_.getInputPort());
		
		// UIスレッドで直接実行
		try{
			lr.runDirect();
			if (targetFileReader.getReader()!=null) {
				targetFileReader.getReader().runDirect();
			}
		}catch(Throwable th) {
			UIUtil.showMessageDialog(getSite().getShell(), "[Error]\n"+th.toString());
			LOG.warn("Throwable",th);
		}    
		//
		waveViewWidget_.setClearingDataWhenBeginMessage(oldClearingDataWhenBeginMessage);
		waveViewWidget_.preventUpdates(false);
		waveViewWidget_.setViewTimeMax(pos);
	}

	private ResourceStreamProvider createResourceStreamProvider(
			RuntimeContext context) {
		return new ResourceStreamProvider(((IFileEditorInput)getEditorInput()).getFile().getParent(), context);
	}
	
	// Audio
	public static final Pattern PAT_AUDIO_FILE_HEADER = Pattern.compile("#audiofile: *([^;]+); *offsettime: *(-?[0-9\\.]+)");
	private String audioFileName_ = null;
	private double audioOffsetTime_ = 0;
	private WaveViewAudioPlayer audioPlayer_ = null;
	
	private void parseAudioHeader() {
		String editorText=getEditorText();
		//
		RuntimeContext context=new RuntimeContext();
		context.setFileStreamProvider(createResourceStreamProvider(context));
		
		// setup blocks
		LineReaderSource lr=new LineReaderSource();
		lr.setReader(new StringReader(editorText));
		lr.setup(context);
		//
		TargetFileReader targetFileReader=new TargetFileReader();
		targetFileReader.setStartReader(false);
		targetFileReader.setup(context);
		//
		MessageProcessor headerParser = new MessageProcessor() {
			@Override
			public void processMessage(Object message) throws InterruptedException {
				if (message instanceof String) {
					String s=(String)message;
					if (s.startsWith("#")) {
						// Audio
						if (audioFileName_==null) {
							Matcher m = PAT_AUDIO_FILE_HEADER.matcher(s);
							if (m.matches()) {
								audioFileName_ = m.group(1);
								audioOffsetTime_ = m.group(2) != null ? Double.parseDouble(m.group(2)) : 0;
							}
						}
					}
				}
			}
		};
		
		// setup connections
		lr.getOutputPort().connect(targetFileReader.getInputPort());
		lr.getOutputPort().connect(headerParser);
		targetFileReader.getOutputPort().connect(headerParser);
		
		// UIスレッドで直接実行
		audioFileName_=null;
		try{
			lr.runDirect();
			if (targetFileReader.getReader()!=null) {
				targetFileReader.getReader().runDirect();
			}
		}catch(Throwable th) {
			UIUtil.showMessageDialog(getSite().getShell(), "[Error]\n"+th.toString());
			LOG.warn("Throwable",th);
		}    
		
	}

	@Override
	protected void createWaveEditorPage() {
		super.createWaveEditorPage();
		setPageText(waveEditorPageIndex_, "Wave&&Labels");
		//
		final WaveViewWidget wv = getWaveViewWidget();
		ToolBar toolBar = wv.getToolBar();
		{
			ToolItem ti=new ToolItem(toolBar, SWT.NONE);
			ti.setText("?t");
			ti.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					// WaveViewで選んだラベルをテキストエディタ上で表示
					if (wv.getTimeMarkerBegin()==null) {
						UIUtil.showMessageDialog(getSite().getShell(), "Please select a time or a time range.");
						return;
					}
					long t0=wv.getTimeMarkerBegin();
					long t1=wv.getTimeMarkerEnd()==null?t0:wv.getTimeMarkerEnd();

					//
					SourceViewer tv=(SourceViewer) textEditor_.getAdapter(ITextOperationTarget.class);
					StyledText tw = tv.getTextWidget();
					try{
						int[] pos=searchCharPosesInTimeRange(tw,t0,t1);
						tw.setSelection(pos[0],pos[1]);
					}catch(Throwable th) {
						UIUtil.showMessageDialog(getSite().getShell(), "[Error]\n"+th.toString());
						LOG.warn("Throwable",th);
					}
					setActivePage(textEditorPageIndex_);
				}
			});
		}
		{
			ToolItem ti=new ToolItem(toolBar, SWT.NONE);
			ti.setText("+L");
			ti.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					String text;
					long t0;
					if (wv.getTimeMarkerBegin()!=null && wv.getTimeMarkerEnd()==null){
						t0=wv.getTimeMarkerBegin();
						text=WaveViewWidget.timeToSecond(t0)+","+",";
					} else if (wv.getTimeMarkerBegin()!=null && wv.getTimeMarkerEnd()!=null){
						t0=wv.getTimeMarkerBegin();
						long t1=wv.getTimeMarkerEnd();
						text=WaveViewWidget.timeToSecond(t0)+","+WaveViewWidget.timeToSecond(t1)+",";
					} else {
						UIUtil.showMessageDialog(getSite().getShell(), "Please select a time range.");
						return;
					}
					
					//
					SourceViewer tv=(SourceViewer) textEditor_.getAdapter(ITextOperationTarget.class);
					StyledText tw = tv.getTextWidget();
					int insertPos=tw.getContent().getCharCount();
					try{
						insertPos=getInsertPos(tw, t0);
					}catch(Throwable th) {
						LOG.warn("Throwable",th);
					}
					//
					String lineDelimiter = tw.getContent().getLineDelimiter();
					tw.setSelection(insertPos);
					if (insertPos>0) {
						String lastS=tw.getText(insertPos-1,insertPos-1);
						if (!(lastS.equals("\n") || lastS.equals("\r"))) {
							tw.insert(lineDelimiter);
							tw.setSelection(++insertPos);
						}
					}
					tw.insert(text+lineDelimiter);
					tw.setSelection(insertPos+text.length());
					//
					setActivePage(textEditorPageIndex_);
				}
			});
		}
		
		// Audio Button
		{
			ToolItem ti=new ToolItem(toolBar, SWT.NONE);
			ti.setText("Au");
			ti.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					if (audioPlayer_!=null && !audioPlayer_.isClosed()) {
						audioPlayer_.close();
					}else{
						parseAudioHeader();
						if (audioFileName_==null) {
							UIUtil.showMessageDialog(waveViewWidget_.getShell(), "No audiofile information.");
						}
						audioPlayer_=new WaveViewAudioPlayer(
								createResourceStreamProvider(new RuntimeContext()).openInputStream(audioFileName_),
								audioOffsetTime_, waveViewWidget_);
						audioPlayer_.play();
					}
				}
			});
		}
	}
	
	private int[] searchCharPosesInTimeRange(StyledText tw, long t0, long t1) throws InterruptedException {
		RuntimeContext context=new RuntimeContext();
		
		// setup blocks
		final CharPosLineReaderSource lr=new CharPosLineReaderSource();
		lr.setSourceText(tw.getText());
		lr.setup(context);
		LabelFileParser parser=new LabelFileParser();
		parser.setup(context);
		
		// connect
		lr.getOutputPort().connect(parser.getInputPort());
		final ArrayList<LabelLine> labels = new ArrayList<LabelLine>();
		final ArrayList<int[]> labelPoses = new ArrayList<int[]>();
		parser.getOutputPort().connect(new MessageProcessor() {
			@Override
			public void processMessage(Object message) throws InterruptedException {
				if (message instanceof LabelLine) {
					labels.add((LabelLine) message);
					labelPoses.add(new int[]{lr.getPosCurrentLineBegins(), lr.getPosCurrentLineEnds()});
				}
			}
		});
		
		// execute
		lr.runDirect();
		
		// phase 2
		boolean found=false, prevfound=false, alert=false;
		int res0=tw.getCharCount(),res1=0;
		for(int i=0;i<labels.size();++i) {
			LabelLine label=labels.get(i);
			int[] pos=labelPoses.get(i);
			if (t0-LONG_EPS<=label.timeBegin && label.timeEnd<=t1+LONG_EPS) {
				res0=Math.min(res0,pos[0]);
				res1=Math.max(res1,pos[1]);
				if (found && !prevfound) {
					alert=true;
				}
				//
				found=true;
				prevfound=true;
			}else{
				//
				prevfound=false;
			}
		}
		if (alert) {
			UIUtil.showMessageDialog(getSite().getShell(), "[Warning]\nSome selected lines are out of the time range");
		}
		if (!found) {
			/*
			UIUtil.showMessageDialog(getSite().getShell(), "No labels in the time range");
			return new int[]{0,0};
			*/
			// 見つからなかったら、挿入位置に移動
			int cp=getInsertPos(tw, t0);
			return new int[]{cp,cp};
		}
		return new int[]{res0,res1};
	}
	
	public int getInsertPos(StyledText tw, final long t0) throws InterruptedException {
		final int[] res=new int[]{-1};
		
		// setup blocks
		RuntimeContext context=new RuntimeContext();
		final CharPosLineReaderSource lr=new CharPosLineReaderSource();
		lr.setSourceText(tw.getText());
		lr.setup(context);
		LabelFileParser parser=new LabelFileParser();
		parser.setup(context);
		
		// connect
		lr.getOutputPort().connect(parser.getInputPort());
		parser.getOutputPort().connect(new MessageProcessor() {
			@Override
			public void processMessage(Object message) throws InterruptedException {
				if (message instanceof LabelLine) {
					LabelLine line=((LabelLine) message);
					if (line.timeBegin>t0 && res[0]<0) res[0]=lr.getPosCurrentLineBegins();
				}
			}
		});
		
		// execute
		lr.runDirect();
		
		//
		if (res[0]<0) res[0]=tw.getContent().getCharCount();
		return res[0];
	}
}
