/*******************************************************************************
 * Copyright (c) 2004, 2010 Tasktop Technologies and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Eike Stepper- initial API and implementation
 *     Tasktop Technologies - improvements
 *******************************************************************************/

package org.eclipse.mylyn.internal.team.ui.templates;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.mylyn.commons.core.StatusHandler;
import org.eclipse.mylyn.internal.team.ui.FocusedTeamUiPlugin;
import org.eclipse.mylyn.tasks.core.ITask;
import org.eclipse.mylyn.team.ui.AbstractCommitTemplateVariable;

/**
 * @author Eike Stepper
 * @author Mik Kersten
 */
public class CommitTemplateManager {

	private static final Pattern ARGUMENT_PATTERN = Pattern.compile("(.+)" + "\\(\\s*\"" //$NON-NLS-1$ //$NON-NLS-2$
			+ "(.+(?:\"\\s*,\\s*\".+)*)" + "\"\\s*\\)}" + "(.*)", Pattern.DOTALL); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$

	private static final String ATTR_CLASS = "class"; //$NON-NLS-1$

	private static final String ATTR_DESCRIPTION = "description"; //$NON-NLS-1$

	private static final String ATTR_RECOGNIZED_KEYWORD = "recognizedKeyword"; //$NON-NLS-1$

	private static final String ELEM_TEMPLATE_HANDLER = "templateVariable"; //$NON-NLS-1$

	private static final String EXT_POINT_TEMPLATE_HANDLERS = "commitTemplates"; //$NON-NLS-1$

	private static final String[] EMPTY_STRING_ARRAY = new String[0];

	public String generateComment(ITask task, String template) {
		return processKeywords(task, template);
	}

	public String getTaskIdFromCommentOrLabel(String comment) {
		try {
			String template = FocusedTeamUiPlugin.getDefault()
					.getPreferenceStore()
					.getString(FocusedTeamUiPlugin.COMMIT_TEMPLATE);
			int templateNewline = template.indexOf('\n');
			String templateFirstLineIndex = template;
			if (templateNewline != -1) {
				templateFirstLineIndex = template.substring(0, templateNewline - 1);
			}

			String regex = getTaskIdRegEx(templateFirstLineIndex);

			int commentNewlineIndex = comment.indexOf('\n');
			String commentFirstLine = comment;
			if (commentNewlineIndex != -1) {
				commentFirstLine = comment.substring(0, commentNewlineIndex);
			}

			Pattern pattern = Pattern.compile(regex);
			Matcher matcher = pattern.matcher(commentFirstLine);

			if (matcher.find()) {
				return matcher.group(1);
			}
		} catch (Exception e) {
			StatusHandler.log(new Status(IStatus.ERROR, FocusedTeamUiPlugin.ID_PLUGIN,
					"Problem while parsing task id from comment", e)); //$NON-NLS-1$
		}

		return null;
	}

	public String getTaskIdRegEx(String template) {
		final String META_CHARS = " $()*+.< [\\]^{|}"; //$NON-NLS-1$
		final String TASK_ID_PLACEHOLDER = "\uffff"; //$NON-NLS-1$
		final String KEYWORD_PLACEHOLDER = "\ufffe"; //$NON-NLS-1$

		template = template.replaceFirst("\\$\\{task\\.id\\}", TASK_ID_PLACEHOLDER); //$NON-NLS-1$
		template = template.replaceFirst("\\$\\{task\\.key\\}", TASK_ID_PLACEHOLDER); //$NON-NLS-1$
		template = replaceKeywords(template, KEYWORD_PLACEHOLDER);
		template = quoteChars(template, META_CHARS);
		template = template.replaceFirst(TASK_ID_PLACEHOLDER, "(\\\\d+)"); //$NON-NLS-1$
		template = template.replaceAll(KEYWORD_PLACEHOLDER, ".*"); //$NON-NLS-1$
		return template;
	}

	private String replaceKeywords(String str, String placeholder) {
		String[] recognizedKeywords = getRecognizedKeywords();
		for (String keyword : recognizedKeywords) {
			str = str.replaceAll("\\$\\{" + keyword + "\\}", placeholder); //$NON-NLS-1$ //$NON-NLS-2$
		}
		return str;
	}

	private String quoteChars(String str, String charsToQuote) {
		StringBuilder builder = new StringBuilder(str.length() * 2);
		for (int i = 0; i < str.length(); i++) {
			char c = str.charAt(i);
			if (charsToQuote.indexOf(c) != -1) {
				builder.append('\\');
			}
			builder.append(c);
		}
		return builder.toString();
	}

	public String[] getRecognizedKeywords() {
		final ArrayList<String> result = new ArrayList<String>();
		new ExtensionProcessor() {
			@Override
			protected Object processContribution(IConfigurationElement element, String keyword, String description,
					String className) throws Exception {
				result.add(keyword);
				return null;
			}
		}.run();

		return result.toArray(new String[result.size()]);
	}

	public String getHandlerDescription(final String keyword) {
		return (String) new ExtensionProcessor() {
			@Override
			protected Object processContribution(IConfigurationElement element, String foundKeyword,
					String description, String className) throws Exception {
				return keyword.equals(foundKeyword) ? description : null;
			}
		}.run();
	}

	public AbstractCommitTemplateVariable createHandler(final String keyword) {
		return (AbstractCommitTemplateVariable) new ExtensionProcessor() {
			@Override
			protected Object processContribution(IConfigurationElement element, String foundKeyword,
					String description, String className) throws Exception {
				if (keyword.equals(foundKeyword)) {
					AbstractCommitTemplateVariable handler = (AbstractCommitTemplateVariable) element.createExecutableExtension(ATTR_CLASS);
					if (handler != null) {
						handler.setDescription(description);
						handler.setRecognizedKeyword(foundKeyword);
					}
//					else {
//						String recognizedKeyword = handler.getRecognizedKeyword();
//						if (recognizedKeyword == null || !recognizedKeyword.equals(foundKeyword)) {
//							throw new IllegalArgumentException("Keyword markup does not match handler implementation");
//						}
//					}

					return handler;
				}

				return null;
			}
		}.run();
	}

	private String processKeywords(ITask task, String template) {
		String[] segments = template.split("\\$\\{"); //$NON-NLS-1$
		Stack<String> evaluated = new Stack<String>();
		evaluated.add(segments[0]);

		for (int i = 1; i < segments.length; i++) {
			String segment = segments[i];
			String value = null;
			String trailingCharacters;
			Matcher argumentMatcher = ARGUMENT_PATTERN.matcher(segment);
			if (argumentMatcher.matches()) {
				String keyword = argumentMatcher.group(1);
				String[] args = argumentMatcher.group(2).split("\"\\s*,\\s*\""); //$NON-NLS-1$
				value = processKeyword(task, keyword, args);
				trailingCharacters = argumentMatcher.group(3);
			} else {
				int brace = segment.indexOf('}');
				if (brace > 0) {
					String keyword = segment.substring(0, brace);
					value = processKeyword(task, keyword, EMPTY_STRING_ARRAY);
				}
				trailingCharacters = segment.substring(brace + 1);
			}

			if (value != null) {
				evaluated.add(value);
				evaluated.add(trailingCharacters);
			} else if (!evaluated.isEmpty()) {
				evaluated.add(trailingCharacters);
			}
//			else {
//				buffer.append("${");
//				buffer.append(segment);
//			}
		}
		StringBuffer buffer = new StringBuffer();
		for (String string : evaluated) {
			buffer.append(string);
		}

		// remove duplicate whitespace
		String commitTemplate = buffer.toString();
		return commitTemplate.replaceAll("[ ]+", " "); //$NON-NLS-1$ //$NON-NLS-2$
	}

	private String processKeyword(ITask task, String keyword, String[] args) {
		try {
			AbstractCommitTemplateVariable handler = createHandler(keyword);
			if (handler != null) {
				handler.setArguments(args);
				return handler.getValue(task);
			}
		} catch (Exception e) {
			StatusHandler.log(new Status(IStatus.ERROR, FocusedTeamUiPlugin.ID_PLUGIN,
					"Problem while dispatching to template handler for: " + keyword, e)); //$NON-NLS-1$
		}

		return null;
	}

	/**
	 * @author Eike Stepper
	 */
	private static class ExtensionProcessor {
		public Object run() {
			IExtensionPoint extPoint = Platform.getExtensionRegistry().getExtensionPoint(FocusedTeamUiPlugin.ID_PLUGIN,
					EXT_POINT_TEMPLATE_HANDLERS);
			IExtension[] extensions = extPoint.getExtensions();
			for (IExtension extension : extensions) {
				IConfigurationElement[] elements = extension.getConfigurationElements();
				for (IConfigurationElement element : elements) {
					if (ELEM_TEMPLATE_HANDLER.equals(element.getName())) {
						try {
							Object result = processContribution(element);
							if (result != null) {
								return result;
							}
						} catch (Exception e) {
							String msg = MessageFormat.format(
									Messages.CommitTemplateManager_Error_while_processing_template_handler_contribution_X_from_plugin_X,
									element.getAttribute(ATTR_CLASS), element.getContributor().getName());
							StatusHandler.log(new Status(IStatus.ERROR, FocusedTeamUiPlugin.ID_PLUGIN, msg, e));
						}
					}
				}
			}

			return null;
		}

		protected Object processContribution(IConfigurationElement element) throws Exception {
			String keyword = element.getAttribute(ATTR_RECOGNIZED_KEYWORD);
			String description = element.getAttribute(ATTR_DESCRIPTION);
			String className = element.getAttribute(ATTR_CLASS);
			return processContribution(element, keyword, description, className);
		}

		protected Object processContribution(IConfigurationElement element, String keyword, String description,
				String className) throws Exception {
			return null;
		}
	}
}
