/*******************************************************************************
 * Copyright (c) 2012 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:
 *     Tasktop Technologies - initial API and implementation
 *******************************************************************************/

package org.eclipse.mylyn.internal.tasks.index.ui;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Pattern;

import org.eclipse.jface.fieldassist.IContentProposal;
import org.eclipse.jface.fieldassist.IContentProposalProvider;
import org.eclipse.mylyn.internal.tasks.core.AbstractTask;
import org.eclipse.mylyn.internal.tasks.core.TaskList;
import org.eclipse.mylyn.internal.tasks.index.core.TaskListIndex;
import org.eclipse.mylyn.tasks.core.data.AbstractTaskSchema;
import org.eclipse.mylyn.tasks.core.data.AbstractTaskSchema.Field;
import org.eclipse.mylyn.tasks.core.data.TaskAttribute;
import org.eclipse.osgi.util.NLS;

/**
 * A content proposal provider for proposals on a task list index and task list.
 * 
 * @author David Green
 */
public class ContentProposalProvider implements IContentProposalProvider {

	private static final Pattern PATTERN_WHITESPACE = Pattern.compile("\\s"); //$NON-NLS-1$

	private static final Pattern PATTERN_SPECIAL_CHARACTERS = Pattern.compile("([+&\\|!\\(\\)\\{\\}\\[\\]^\"~\\*\\?:\\\\-])"); //$NON-NLS-1$

	private final AbstractIndexReference reference;

	private final TaskList taskList;

	public ContentProposalProvider(TaskList taskList, AbstractIndexReference reference) {
		this.taskList = taskList;
		this.reference = reference;
	}

	public static class ProposalContentState {

		public String beforePrefixContent;

		public String fieldPrefix = ""; //$NON-NLS-1$

		public String prefix = ""; //$NON-NLS-1$

		public String suffix = ""; //$NON-NLS-1$

		public ProposalContentState(String contents) {
			beforePrefixContent = contents;
		}

	}

	public IContentProposal[] getProposals(String contents, int position) {
		List<IContentProposal> proposals = new ArrayList<IContentProposal>(10);

		ProposalContentState contentState = computeProposalContentState(contents, position);

		// if we have a field prefix
		if (contentState.fieldPrefix.length() > 0) {
			AbstractTaskSchema.Field indexField = computeIndexField(contentState.fieldPrefix);

			// if it's a person field then suggest
			// people from the task list
			if (indexField != null && TaskAttribute.TYPE_PERSON.equals(indexField.getType())) {
				computePersonProposals(proposals, contentState);
			}

		} else {

			// suggest field name prefixes
			for (Field field : reference.index().getIndexedFields()) {

				// searching on URL is not useful
				if (field.equals(TaskListIndex.FIELD_IDENTIFIER)) {
					continue;
				}

				String indexKey = field.getIndexKey();
				if (indexKey.startsWith(contentState.prefix)) {
					String description;
					if (TaskListIndex.FIELD_CONTENT.equals(field)) {
						description = Messages.IndexSearchHandler_hint_content;
					} else if (TaskListIndex.FIELD_PERSON.equals(field)) {
						description = Messages.IndexSearchHandler_hint_person;
					} else {
						description = NLS.bind(Messages.IndexSearchHandler_hint_generic, field.getLabel());
					}
					proposals.add(new ContentProposal(contentState.beforePrefixContent + indexKey + ":", //$NON-NLS-1$
							contentState.suffix, indexKey, description));

					if (TaskAttribute.TYPE_DATE.equals(field.getType())
							|| TaskAttribute.TYPE_DATETIME.equals(field.getType())) {
						computeDateRangeProposals(proposals, contentState, field);
					}
				}
			}
		}

		return proposals.toArray(new IContentProposal[proposals.size()]);
	}

	public ProposalContentState computeProposalContentState(String contents, int position) {
		ProposalContentState contentState = new ProposalContentState(contents);

		if (position >= 0 && position <= contents.length()) {
			int i = position;
			while (i > 0 && !Character.isWhitespace(contents.charAt(i - 1)) && contents.charAt(i - 1) != ':') {
				--i;
			}
			if (i > 0 && contents.charAt(i - 1) == ':') {
				int fieldEnd = i - 1;
				int fieldStart = i - 1;
				while (fieldStart > 0 && Character.isLetter(contents.charAt(fieldStart - 1))) {
					--fieldStart;
				}
				contentState.fieldPrefix = contents.substring(fieldStart, fieldEnd);
			}

			contentState.prefix = contents.substring(i, position);
			contentState.beforePrefixContent = contents.substring(0, i);

			if (position < contents.length()) {
				i = position;
				while (i < contents.length()) {
					if (Character.isWhitespace(contents.charAt(i))) {
						break;
					}
					++i;
				}
				contentState.suffix = contents.substring(i);
			}
		}
		return contentState;
	}

	public Field computeIndexField(String fieldPrefix) {
		for (Field field : reference.index().getIndexedFields()) {
			if (field.getIndexKey().equals(fieldPrefix)) {
				return field;
			}
		}
		return null;
	}

	public void computeDateRangeProposals(List<IContentProposal> proposals, ProposalContentState contentState,
			Field field) {
		// for date fields give suggestion of date range search
		String description;
		final Date now = new Date();
		final Date dateSearchUpperBound;
		final Date dateSearchOneWeekLowerBound;
		{
			GregorianCalendar calendar = new GregorianCalendar();

			calendar.setTime(now);
			calendar.add(Calendar.DAY_OF_WEEK, 1); // one day in future due to GMT conversion in index
			dateSearchUpperBound = calendar.getTime();

			calendar.setTime(now);
			calendar.add(Calendar.DAY_OF_WEEK, -7);
			dateSearchOneWeekLowerBound = calendar.getTime();
		}

		description = NLS.bind(Messages.IndexSearchHandler_Generic_date_range_search_1_week, field.getLabel());

		String label = NLS.bind(Messages.IndexSearchHandler_Past_week_date_range_label, field.getIndexKey());

		String queryText = reference.index().computeQueryFieldDateRange(field, dateSearchOneWeekLowerBound,
				dateSearchUpperBound);

		if (queryText.startsWith(contentState.prefix)) {
			proposals.add(new ContentProposal(contentState.beforePrefixContent + queryText, contentState.suffix, label,
					description));
		}
	}

	public void computePersonProposals(List<IContentProposal> proposals, ProposalContentState contentState) {
		Set<String> addresses = new TreeSet<String>();

		Collection<AbstractTask> allTasks = taskList.getAllTasks();
		for (AbstractTask task : allTasks) {
			addAddresses(addresses, task);
		}

		for (String address : addresses) {
			if (address.startsWith(contentState.prefix)) {
				String proposalContent = address;
				proposalContent = PATTERN_SPECIAL_CHARACTERS.matcher(proposalContent).replaceAll("\\\\$1"); //$NON-NLS-1$
				if (PATTERN_WHITESPACE.matcher(proposalContent).find()) {
					proposalContent = "\"" + proposalContent + "\""; //$NON-NLS-1$//$NON-NLS-2$
				}
				proposals.add(new ContentProposal(contentState.beforePrefixContent + proposalContent,
						contentState.suffix, address, null));
			}
		}
	}

	private void addAddresses(Set<String> addresses, AbstractTask task) {
		String name = task.getOwner();
		if (name != null && name.trim().length() > 0) {
			addresses.add(name.trim());
		}
	}
}
