package cucumber.eclipse.editor.editors;

import gherkin.I18n;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.handlers.HandlerUtil;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.texteditor.ITextEditor;

import cucumber.eclipse.steps.integration.IStepDefinitions;
import cucumber.eclipse.steps.integration.Step;

public class PopupMenuFindStepActionDelegate extends AbstractHandler {

	private final static String EXTENSION_POINT_STEPDEFINITIONS_ID = "cucumber.eclipse.steps.integration";
	private Pattern variablePattern = Pattern.compile("<([^>]+)>");

	private List<IStepDefinitions> getStepDefinitions() {
		List<IStepDefinitions> stepDefs = new ArrayList<IStepDefinitions>();
		IConfigurationElement[] config = Platform
				.getExtensionRegistry()
				.getConfigurationElementsFor(EXTENSION_POINT_STEPDEFINITIONS_ID);
		try {
			for (IConfigurationElement ce : config) {
				Object obj = ce.createExecutableExtension("class");
				if (obj instanceof IStepDefinitions) {
					stepDefs.add((IStepDefinitions) obj);
				}
			}
		} catch (CoreException e) {
		}
		return stepDefs;
	}

	@Override
	public Object execute(ExecutionEvent event) throws ExecutionException {
		
		IEditorPart editorPart = HandlerUtil.getActiveEditorChecked(event);
		IEditorInput input = editorPart.getEditorInput();

		// Editor contents needs to be associated with an eclipse project
		// for this to work, if not then simply do nothing.
		if (!(input instanceof IFileEditorInput)) {
			return null;
		}
		IFile featurefile = ((IFileEditorInput) input).getFile();

		Set<Step> steps = new HashSet<Step>();
		for (IStepDefinitions stepDef : getStepDefinitions()) {
			steps.addAll(stepDef.getSteps(featurefile));
		}
		
		String selectedLine = getSelectedLine(editorPart);
		String language = getDocumentLanguage(editorPart);

		Step matchedStep = matchSteps(language, steps, selectedLine);
		try {
			if (matchedStep != null) openEditor(matchedStep);
		} catch (CoreException e) {
			e.printStackTrace();
		}
		return null;
	}

	Step matchSteps(String languageCode, Set<Step> steps, String currentLine) {
		Pattern cukePattern = getLanguageKeyWordMatcher(languageCode);
		
		if(cukePattern == null)
			return null;
		
		Matcher matcher = cukePattern.matcher(currentLine);
		if (matcher.matches()) {
			String cukeStep = matcher.group(1);
		
			// FIXME: Replace variables with 0 for now to allow them to match steps
			// Should really read the whole scenario outline and sub in the first scenario
			Matcher variableMatcher = variablePattern.matcher(cukeStep);
			cukeStep = variableMatcher.replaceAll("0");
			
			for(Step step: steps) {
				if (step.matches(cukeStep)) {
					return step;
				}
			}
			
		} 
		return null;
	}
	
	private void openEditor(Step step) throws CoreException {
		   IResource file = step.getSource();
		   IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();

		   HashMap<String, Integer> map = new HashMap<String, Integer>();
		   map.put(IMarker.LINE_NUMBER, step.getLineNumber());
		   IMarker marker = file.createMarker(IMarker.TEXT);
		   marker.setAttributes(map);
		   IDE.openEditor(page, marker);
		   marker.delete();
		   
	}
	
	private String getDocumentLanguage(IEditorPart editorPart) {
		String lang = null;
		try {
			ITextEditor editor = (ITextEditor) editorPart;
			IDocumentProvider docProvider = editor.getDocumentProvider();
			IDocument doc = docProvider.getDocument(editorPart.getEditorInput());
			
			IRegion lineInfo = doc.getLineInformation(0);
			int length = lineInfo.getLength();
			int offset = lineInfo.getOffset();
			String line = doc.get(offset, length);
			
			if(line.contains("language")) {
				int indexOf = line.indexOf(":");
				lang = line.substring((indexOf + 1)).trim();
			}
		} catch(BadLocationException e) {
			e.printStackTrace();
		}
		
		return lang;
	}
	
	private String getSelectedLine(IEditorPart editorPart) {
		
		ITextEditor editor = (ITextEditor) editorPart;
				
		TextSelection selecton = (TextSelection) editor.getSelectionProvider().getSelection();
		int line = selecton.getStartLine();	
		
		IDocumentProvider docProvider = editor.getDocumentProvider();
		IDocument doc = docProvider.getDocument(editorPart.getEditorInput());
		try {
			String stepLine = doc.get(doc.getLineOffset(line), doc.getLineLength(line)).trim();
			return stepLine;
		} catch (BadLocationException e) {
			return "";
		}
	}
	
	private Pattern getLanguageKeyWordMatcher(String languageCode) {
		try {
			I18n i18n = new I18n(languageCode.toLowerCase());
			
			StringBuilder sb = new StringBuilder();
			sb.append("(?:");
			String delim = "";
		
			for(String keyWord : i18n.getCodeKeywords()) {
				sb.append(delim).append(keyWord);
				delim = "|";
			}
		
			return Pattern.compile((sb.append(") (.*)$").toString()));
		} catch(NullPointerException e) {
			e.printStackTrace();
			return null;
		} catch(PatternSyntaxException e) {
			e.printStackTrace();
			return null;
		}
	}
}

@@ -5540,16 +5540,136 @@
 ry %7B%0A%09%09%09
+if (languageCode == null) %7B%0A%09%09%09%09languageCode = %22en%22;%0A%09%09%09%7D else %7B%0A%09%09%09%09languageCode = languageCode.toLowerCase();%0A%09%09%09%7D%0A%09%09%09
 I18n i18
@@ -5693,30 +5693,16 @@
 uageCode
-.toLowerCase()
 );%0A%09%09%09%0A%09

fix NPE when languageCode is null and just default to EN