package jp.hasc.hasctool.ui.commands;

import java.io.InputStreamReader;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import jp.hasc.hasctool.core.blockdiagram.BlockDiagramExecutor;
import jp.hasc.hasctool.core.blockdiagram.model.BlockDiagram;
import jp.hasc.hasctool.core.runtime.RuntimeContext;
import jp.hasc.hasctool.core.util.CoreUtil;
import jp.hasc.hasctool.ui.util.UIUtil;

import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.jface.window.Window;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.dialogs.ResourceSelectionDialog;
import org.eclipse.ui.handlers.HandlerUtil;
import org.eclipse.ui.services.IEvaluationService;

import com.thoughtworks.xstream.XStream;

/**
 * @author iwasaki
 */
public class BlockDiagramExecutorManager {
	public static final Pattern PAT_INPUT_FILE = Pattern.compile("\\$\\(inputFile\\.(\\w+)\\)"); //$NON-NLS-1$
	public static final Pattern PAT_CURRENT_DATETIME = Pattern.compile("\\$\\(currentDateTime\\)"); //$NON-NLS-1$

	/** logger for this class */
	private final static org.apache.commons.logging.Log LOG = org.apache.commons.logging.LogFactory
			.getLog(BlockDiagramExecutorManager.class);
	
	private static ArrayList<BlockDiagramExecutorManager> instances__=new ArrayList<BlockDiagramExecutorManager>();
	
	public static BlockDiagramExecutorManager getInstance(ExecutionEvent event) {
		IWorkbenchWindow win = HandlerUtil.getActiveWorkbenchWindow(event);
		return getInstance(win);
	}

	public static BlockDiagramExecutorManager getInstance(IWorkbenchWindow win) {
		synchronized (instances__) {
			for(BlockDiagramExecutorManager m:instances__) {
				if (m.getWindow()==win) return m;
			}
			// create new one
			BlockDiagramExecutorManager m=new BlockDiagramExecutorManager(win);
			instances__.add(0,m);
			return m;
		}
	}
	
	private IWorkbenchWindow window_;
	private BlockDiagramExecutor executor_=null;  

	public IWorkbenchWindow getWindow() {
		return window_;
	}

	//
	public BlockDiagramExecutorManager(IWorkbenchWindow window) {
		window_ = window;
	}
	
	private boolean iterateInputFiles_;
	
	private ResourceSelectionDialog intputFileDialog_;
	private IAdaptable inputFileDialogRootElement_;

	private ArrayList<IFile> inputFiles_= new ArrayList<IFile>();
	
	private IFile fileToExecute_;
	private int currentInputFileIndex_;
	private IFile currentInputFile_;

	public void executeFile(IFile fileToExecute) {
		stopExecution();
		//
		LOG.debug("execute "+fileToExecute.getFullPath()); //$NON-NLS-1$
		fileToExecute_=fileToExecute;
		
		//
		try {
			// preprocess
			String contents=CoreUtil.readReaderAsString(new InputStreamReader(fileToExecute.getContents(),RuntimeContext.DEFAULT_CHARSET));
			
			iterateInputFiles_=PAT_INPUT_FILE.matcher(contents).find();
			if (iterateInputFiles_) {
				// open file dialog
				IAdaptable rootElemtnt = fileToExecute.getProject(); // fileToExecute.getWorkspace().getRoot();
				if (intputFileDialog_==null || inputFileDialogRootElement_!=rootElemtnt) {
					inputFileDialogRootElement_=rootElemtnt;
					intputFileDialog_ = new ResourceSelectionDialog(window_.getShell(), inputFileDialogRootElement_, Messages.BlockDiagramExecutorManager_SelectInputFiles);
				}
				if (!inputFiles_.isEmpty()) intputFileDialog_.setInitialSelections(inputFiles_.toArray());
				if (Window.OK!=intputFileDialog_.open()) return;
				Object[] result = intputFileDialog_.getResult();
				//LOG.debug("selected: "+Arrays.toString(result));
				inputFiles_.clear();
				for(Object obj : result) {
					if (obj instanceof IFile) {
						IFile f = (IFile) obj;
						String fp=f.getFullPath().toString();
						if (fp.contains("/.svn") || fp.contains("/.git") || fp.contains("/.csv")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
							LOG.debug("skip:"+fp); //$NON-NLS-1$
						}else{
							inputFiles_.add(f);
						}
					}
				}
				currentInputFileIndex_=0;
				if (inputFiles_.isEmpty()) {
					UIUtil.showMessageDialog(window_.getShell(), Messages.BlockDiagramExecutorManager_NoValidInputFiles);
					return;
				}
			}
			
			// execute
			executeSub(contents);
			
			//
		} catch (Exception ex) {
			stopExecution();
			disposeExecutor();
			CoreUtil.throwAsRuntimeException(ex);
		}
	}
	
	private void executeSub(final String contents) {
		final IFile f=fileToExecute_;
		
		if (iterateInputFiles_) {
			currentInputFile_=inputFiles_.get(currentInputFileIndex_);
			LOG.debug("input file: "+currentInputFile_.getFullPath()); //$NON-NLS-1$
		}
		
		// parse
		XStream xs = BlockDiagramExecutor.newXStream();
		BlockDiagram bd = (BlockDiagram)xs.fromXML(preprocess(contents));
		
		// setup context
		executor_=new BlockDiagramExecutor();
		final RuntimeContext runtimeContext = executor_.getRuntimeContext();
		runtimeContext.putObjectToRepogitory(IWorkbenchWindow.class, getWindow());
		runtimeContext.putObjectToRepogitory(BlockDiagramExecutor.KEY_BLOCK_DIAGRAM_FILE_NAME, f.getName());
		runtimeContext.setFileStreamProvider(new HybridStreamProvider(f.getParent(), runtimeContext));
		
		if (iterateInputFiles_) {
			runtimeContext.tasksTerminationListeners().add(new Runnable() {
				@Override
				public void run() {
					runtimeContext.tasksTerminationListeners().remove(this);
					//executor_.dispose();
					executor_=null;
					// UIスレッドで実行しないと、RuntimeWaveView#setup時に落ちる
					window_.getShell().getDisplay().asyncExec(new Runnable() {
						@Override
						public void run() {
							// next input file
							if (iterateInputFiles_ && ++currentInputFileIndex_<inputFiles_.size()) {
								executeSub(contents);
							}else{
								updateRunning();
								LOG.debug("done all input files."); //$NON-NLS-1$
							}
						}
					});
				}
			});
		}else{
			runtimeContext.tasksTerminationListeners().add(new Runnable() {
				@Override
				public void run() {
					runtimeContext.tasksTerminationListeners().remove(this);
					//executor_.dispose();
					disposeExecutor();
				}
			});
		}

		// execute
		executor_.execute(bd);
		updateRunning();
	}
	
	/**
	 * 変数キーワードの置換など
	 */
	private String preprocess(String contents) {
		return preprocess_currentDateTime(preprocess_inputFiles(contents));
	}
	
	public static final SimpleDateFormat FMT_DATETIME = new SimpleDateFormat("yyyyMMdd-HHmmss-SSS"); //$NON-NLS-1$
	
	public static String preprocess_currentDateTime(String contents) {
		Matcher m=PAT_CURRENT_DATETIME.matcher(contents);
		if (!m.find()) return contents;
		StringBuilder sb=new StringBuilder();
		int cursor=0;
		do{
			sb.append(contents,cursor,m.start());
			cursor=m.end();
			sb.append(FMT_DATETIME.format(new Date()));
		}while(m.find(cursor));
		sb.append(contents,cursor,contents.length());
		return sb.toString();
	}
	
	private String preprocess_inputFiles(String contents) {
		if (!iterateInputFiles_) return contents;
		
		StringBuilder sb=new StringBuilder();
		Matcher m=PAT_INPUT_FILE.matcher(contents);
		int cursor=0;
		while(m.find(cursor)) {
			sb.append(contents,cursor,m.start());
			cursor=m.end();
			String prop=m.group(1);
			if (prop.equals("path")) { //$NON-NLS-1$
				sb.append(modifyPath(currentInputFile_.getFullPath().toString()));
			}else if (prop.equals("parentPath")) { //$NON-NLS-1$
				sb.append(modifyPath(currentInputFile_.getParent().getFullPath().toString()));
			}else if (prop.equals("name")) { //$NON-NLS-1$
				sb.append(currentInputFile_.getName());
			}else if (prop.equals("nameWithoutExt")) { //$NON-NLS-1$
				sb.append(removeFileExt(currentInputFile_.getName()));
			}
		}
		sb.append(contents,cursor,contents.length());
		return sb.toString();
	}

	private String modifyPath(String path) {
		String prefix=fileToExecute_.getProject().getFullPath().toString();
		if (path.startsWith(prefix)) {
			path=ResourceStreamProvider.KEYWORD_PROJECT_ROOT+path.substring(prefix.length());
		}
		return path;
	}

	private String removeFileExt(String name) {
		int idx=name.lastIndexOf("."); //$NON-NLS-1$
		return (idx>=0) ? name.substring(0,idx) : name;
	}

	public void stopExecution() {
		iterateInputFiles_=false;
		if (executor_!=null) {
			executor_.getRuntimeContext().stopTasks();
		}
	}
	
	public boolean isRunning() {
		return executor_!=null;
	}
	
	private boolean prevRunning_=false;
	
	public void updateRunning() {
		boolean r = isRunning();
		if (prevRunning_!=r) {
			// change StopBD state
			IEvaluationService service = (IEvaluationService) window_.getService(IEvaluationService.class);
			service.requestEvaluation("jp.hasc.hasctool.ui.commands.StopBlockDiagramCommandEnabled");
		}
		prevRunning_=r;
	}

	private void disposeExecutor() {
		executor_=null;
		window_.getShell().getDisplay().asyncExec(new Runnable() {
			@Override
			public void run() {
				updateRunning();
			}
		});
	}
}
