/*******************************************************************************
 * Copyright (c) 2006, 2008 Steffen Pingel 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:
 *     Steffen Pingel - initial API and implementation
 *******************************************************************************/

package org.eclipse.mylyn.internal.trac.ui.wizard;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.mylyn.commons.core.StatusHandler;
import org.eclipse.mylyn.internal.trac.core.client.ITracClient;
import org.eclipse.mylyn.internal.trac.core.model.TracSearch;
import org.eclipse.mylyn.internal.trac.core.model.TracSearchFilter;
import org.eclipse.mylyn.internal.trac.core.model.TracSearchFilter.CompareOperator;
import org.eclipse.mylyn.internal.trac.core.util.TracUtil;
import org.eclipse.mylyn.internal.trac.ui.TracUiPlugin;
import org.eclipse.mylyn.tasks.core.IRepositoryQuery;
import org.eclipse.mylyn.tasks.core.TaskRepository;
import org.eclipse.mylyn.tasks.ui.wizards.AbstractRepositoryQueryPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;

/**
 * @author Steffen Pingel
 */
public class TracFilterQueryPage extends AbstractRepositoryQueryPage {

	private static final String TITLE = Messages.TracFilterQueryPage_New_Trac_Query;

	private static final String DESCRIPTION = Messages.TracFilterQueryPage_Add_search_filters_to_define_query;

	private static final String TITLE_QUERY_TITLE = Messages.TracFilterQueryPage_Query_Title;

	private Text titleText;

	private Composite scrollComposite;

	/* Maintain order of criterions in order to be able to restore this later. */
	private final Set<SearchField> visibleSearchFields = new LinkedHashSet<SearchField>();

	private List<SearchField> searchFields;

	public TracFilterQueryPage(TaskRepository repository, IRepositoryQuery query) {
		super(TITLE, repository, query);
		setTitle(TITLE);
		setDescription(DESCRIPTION);
	}

	@Override
	public void applyTo(IRepositoryQuery query) {
		query.setUrl(getQueryUrl(getTaskRepository().getRepositoryUrl()));
		query.setSummary(getQueryTitle());
	}

	public void createControl(Composite parent) {
		Composite composite = new Composite(parent, SWT.NONE);
		composite.setLayoutData(new GridData());
		composite.setLayout(new GridLayout(1, false));

		createTitleGroup(composite);

		ScrolledComposite scrolledComposite = new ScrolledComposite(composite, SWT.V_SCROLL | SWT.BORDER);
		GridData gd = new GridData(GridData.FILL, GridData.FILL, true, true);
		scrolledComposite.setExpandHorizontal(true);
		scrolledComposite.setLayoutData(gd);

		scrollComposite = new Composite(scrolledComposite, SWT.None);
		scrolledComposite.setContent(scrollComposite);
		scrollComposite.setLayout(new GridLayout(4, false));

		createAddFilterGroup(composite);

		if (getQuery() != null) {
			titleText.setText(getQuery().getSummary());
			TracSearch search = TracUtil.toTracSearch(getQuery());
			if (search != null) {
				restoreWidgetValues(search);
			}
		}

		Dialog.applyDialogFont(composite);
		setControl(composite);
	}

	@Override
	public String getQueryTitle() {
		return (titleText != null) ? titleText.getText() : null;
	}

	private void restoreWidgetValues(TracSearch search) {
		List<TracSearchFilter> filters = search.getFilters();
		for (TracSearchFilter filter : filters) {
			SearchField field = getSearchField(filter.getFieldName());
			if (field != null) {
				showSearchField(field, filter);
			} else {
				StatusHandler.log(new Status(IStatus.WARNING, TracUiPlugin.ID_PLUGIN,
						"Ignoring invalid search filter: " + filter)); //$NON-NLS-1$
			}
		}
	}

	private SearchField getSearchField(String fieldName) {
		for (SearchField searchField : searchFields) {
			if (searchField.getFieldName().equals(fieldName)) {
				return searchField;
			}
		}
		return null;
	}

	private void createAddFilterGroup(Composite parent) {
		GridLayout layout;
		GridData gd;

		Composite composite = new Composite(parent, SWT.NONE);
		layout = new GridLayout(2, false);
		composite.setLayout(layout);
		composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
		gd = new GridData(GridData.BEGINNING | GridData.FILL_HORIZONTAL | GridData.GRAB_HORIZONTAL);
		composite.setLayoutData(gd);

		Label label = new Label(composite, SWT.LEFT);
		label.setText(Messages.TracFilterQueryPage_Select_to_add_filter);

		// condition
		final Combo filterCombo = new Combo(composite, SWT.SINGLE | SWT.BORDER | SWT.READ_ONLY);

		searchFields = new ArrayList<SearchField>();
		searchFields.add(new TextSearchField("summary", Messages.TracFilterQueryPage_Summary)); //$NON-NLS-1$
		searchFields.add(new TextSearchField("reporter", Messages.TracFilterQueryPage_Reporter)); //$NON-NLS-1$
		searchFields.add(new TextSearchField("owner", Messages.TracFilterQueryPage_Owner)); //$NON-NLS-1$
		searchFields.add(new TextSearchField("type", Messages.TracFilterQueryPage_Type)); //$NON-NLS-1$
		searchFields.add(new TextSearchField("status", Messages.TracFilterQueryPage_Status)); //$NON-NLS-1$
		searchFields.add(new TextSearchField("priority", Messages.TracFilterQueryPage_Priority)); //$NON-NLS-1$
		searchFields.add(new TextSearchField("milestone", Messages.TracFilterQueryPage_Milestone)); //$NON-NLS-1$
		searchFields.add(new TextSearchField("component", Messages.TracFilterQueryPage_Component)); //$NON-NLS-1$
		searchFields.add(new TextSearchField("version", Messages.TracFilterQueryPage_Version)); //$NON-NLS-1$
		searchFields.add(new TextSearchField("resoution", Messages.TracFilterQueryPage_Resolution)); //$NON-NLS-1$
		searchFields.add(new TextSearchField("keywords", Messages.TracFilterQueryPage_Keywords)); //$NON-NLS-1$
		searchFields.add(new TextSearchField("cc", Messages.TracFilterQueryPage_CC)); //$NON-NLS-1$

		filterCombo.add(""); //$NON-NLS-1$
		for (SearchField field : searchFields) {
			filterCombo.add(field.getDisplayName());
		}

		filterCombo.addModifyListener(new ModifyListener() {
			public void modifyText(ModifyEvent e) {
				if (filterCombo.getText().length() > 0) {
					SearchField field = searchFields.get(filterCombo.getSelectionIndex() - 1);
					showSearchField(field, null);
					filterCombo.setText(""); //$NON-NLS-1$
				}
			}
		});
	}

	private void createTitleGroup(Composite composite) {
		Group group = new Group(composite, SWT.NONE);
		group.setText(TITLE_QUERY_TITLE);
		group.setLayout(new GridLayout(1, false));
		GridData gd = new GridData(GridData.FILL_HORIZONTAL);
		group.setLayoutData(gd);

		titleText = new Text(group, SWT.BORDER);
		gd = new GridData(GridData.FILL_HORIZONTAL | GridData.GRAB_HORIZONTAL);
		titleText.setLayoutData(gd);

		titleText.addKeyListener(new KeyListener() {
			public void keyPressed(KeyEvent e) {
				// ignore
			}

			public void keyReleased(KeyEvent e) {
				getContainer().updateButtons();
			}
		});
	}

	@Override
	public boolean isPageComplete() {
		if (titleText != null && titleText.getText().length() > 0) {
			return true;
		}
		return false;
	}

	private void showSearchField(SearchField field, TracSearchFilter filter) {
		assert filter == null || !visibleSearchFields.contains(field);

		if (!visibleSearchFields.contains(field)) {
			field.createControls(scrollComposite, filter);
			visibleSearchFields.add(field);
		} else {
			field.addControl(scrollComposite);
		}
		updateScrollPane();
	}

	public String getQueryUrl(String repsitoryUrl) {
		TracSearch search = new TracSearch();
		for (SearchField field : visibleSearchFields) {
			search.addFilter(field.getFilter());
		}

		StringBuilder sb = new StringBuilder();
		sb.append(repsitoryUrl);
		sb.append(ITracClient.QUERY_URL);
		sb.append(search.toUrl());
		return sb.toString();
	}

	private void hideSearchField(SearchField field) {
		visibleSearchFields.remove(field);
	}

	private void updateScrollPane() {
		scrollComposite.setSize(scrollComposite.computeSize(SWT.DEFAULT, SWT.DEFAULT));
		scrollComposite.layout();
	}

	private abstract class SearchField {

		protected String fieldName;

		private final String displayName;

		public SearchField(String fieldName, String displayName) {
			this.fieldName = fieldName;
			this.displayName = displayName;
		}

		public abstract void createControls(Composite parent, TracSearchFilter filter);

		public abstract void addControl(Composite parent);

		public String getFieldName() {
			return fieldName;
		}

		public String getDisplayName() {
			return displayName;
		}

		public abstract TracSearchFilter getFilter();

	}

	private class TextSearchField extends SearchField {

		private final CompareOperator[] compareOperators = { CompareOperator.CONTAINS, CompareOperator.CONTAINS_NOT,
				CompareOperator.BEGINS_WITH, CompareOperator.ENDS_WITH, CompareOperator.IS, CompareOperator.IS_NOT, };

		private List<TextCriterion> criterions;

		public TextSearchField(String fieldName, String displayName) {
			super(fieldName, displayName);
		}

		@Override
		public void createControls(Composite parent, TracSearchFilter filter) {
			if (filter != null) {
				TextCriterion first = addCriterion(parent);
				first.setCondition(filter.getOperator());
				List<String> values = filter.getValues();
				if (!values.isEmpty()) {
					first.setSearchText(values.get(0));
					for (int i = 1; i < values.size(); i++) {
						TextCriterion criterion = addCriterion(parent);
						criterion.setSearchText(values.get(1));
					}
				}
			} else {
				addCriterion(parent);
			}

		}

		@Override
		public void addControl(Composite parent) {
			addCriterion(parent);
		}

		public TextCriterion addCriterion(Composite parent) {
			TextCriterion criterion = new TextCriterion();
			if (criterions == null) {
				criterions = new ArrayList<TextCriterion>();
				criterion.createControl(parent);
			} else {
				criterion.createControl(parent, criterions.get(criterions.size() - 1));
			}
			criterions.add(criterion);
			return criterion;
		}

		@Override
		public TracSearchFilter getFilter() {
			TracSearchFilter newFilter = new TracSearchFilter(getFieldName());
			newFilter.setOperator(criterions.get(0).getCondition());
			for (TextCriterion criterion : criterions) {
				newFilter.addValue(criterion.getSearchText());
			}
			return newFilter;
		}

		public void removeCriterion(TextCriterion criterion) {
			int i = criterions.indexOf(criterion);
			if (i == -1) {
				throw new RuntimeException();
			}
			if (i == 0) {
				// the first criterion is special since it contains the compare
				// operator combo
				if (criterions.size() > 1) {
					// copy the value from the second criterion to the first
					TextCriterion sourceCriterion = criterions.get(1);
					criterion.searchText.setText(sourceCriterion.searchText.getText());
					removeCriterionByIndex(1);
				} else {
					// no more criterions, remove all controls
					removeCriterionByIndex(0);
					hideSearchField(this);
				}
			} else {
				removeCriterionByIndex(i);
			}
		}

		private void removeCriterionByIndex(int i) {
			criterions.get(i).remove();
			criterions.remove(i);
			updateScrollPane();
		}

		private class TextCriterion {

			private Combo conditionCombo;

			private Text searchText;

			private Label label;

			private Button removeButton;

			public void createControl(Composite parent) {
				label = new Label(parent, SWT.LEFT);
				label.setText(getDisplayName() + ": "); //$NON-NLS-1$

				conditionCombo = new Combo(parent, SWT.SINGLE | SWT.BORDER | SWT.READ_ONLY);
				for (CompareOperator op : compareOperators) {
					conditionCombo.add(op.toString());
				}
				conditionCombo.setText(compareOperators[0].toString());

				createSearchTextAndRemoveButton(parent);
			}

			public void createControl(Composite parent, TextCriterion top) {
				label = new Label(parent, SWT.RIGHT);
				GridData gd = new GridData();
				gd.horizontalAlignment = SWT.END;
				gd.horizontalSpan = 2;
				label.setLayoutData(gd);
				label.setText(Messages.TracFilterQueryPage_or);

				createSearchTextAndRemoveButton(parent);

				label.moveBelow(top.removeButton);
				searchText.moveBelow(label);
				removeButton.moveBelow(searchText);
			}

			private void createSearchTextAndRemoveButton(Composite parent) {
				searchText = new Text(parent, SWT.BORDER);
				GridData gd = new GridData(GridData.GRAB_HORIZONTAL | GridData.FILL_HORIZONTAL);
				searchText.setLayoutData(gd);

				removeButton = new Button(parent, SWT.PUSH);
				removeButton.setText("-"); //$NON-NLS-1$
				removeButton.addSelectionListener(new SelectionAdapter() {
					@Override
					public void widgetSelected(org.eclipse.swt.events.SelectionEvent e) {
						TextSearchField.this.removeCriterion(TextCriterion.this);
					}
				});
			}

			public void remove() {
				label.dispose();
				if (conditionCombo != null) {
					conditionCombo.dispose();
				}
				searchText.dispose();
				removeButton.dispose();
			}

			public CompareOperator getCondition() {
				return (conditionCombo != null) ? compareOperators[conditionCombo.getSelectionIndex()] : null;
			}

			public String getSearchText() {
				return searchText.getText();
			}

			public boolean setCondition(CompareOperator operator) {
				if (conditionCombo != null) {
					int i = conditionCombo.indexOf(operator.toString());
					if (i != -1) {
						conditionCombo.select(i);
						return true;
					}
				}
				return false;
			}

			public void setSearchText(String text) {
				searchText.setText(text);
			}

		}
	}

}
