/*******************************************************************************
 * Copyright (c) 2013 Google, Inc 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:
 * 	   Sergey Prigogin (Google) - initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.internal.ui.preferences;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.core.resources.IProject;
import org.eclipse.jface.layout.PixelConverter;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StackLayout;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.ui.preferences.IWorkbenchPreferenceContainer;

import org.eclipse.cdt.internal.ui.dialogs.IStatusChangeListener;
import org.eclipse.cdt.internal.ui.dialogs.StatusInfo;
import org.eclipse.cdt.internal.ui.refactoring.includes.IncludeGroupStyle;
import org.eclipse.cdt.internal.ui.refactoring.includes.IncludeGroupStyle.IncludeKind;
import org.eclipse.cdt.internal.ui.wizards.dialogfields.DialogField;
import org.eclipse.cdt.internal.ui.wizards.dialogfields.IDialogFieldListener;
import org.eclipse.cdt.internal.ui.wizards.dialogfields.ITreeListAdapter;
import org.eclipse.cdt.internal.ui.wizards.dialogfields.TreeListDialogField;

/**
 * The preference block for configuring styles of different categories of include statements.
 */
public class IncludeCategoriesBlock extends OptionsConfigurationBlock {
	private final List<IncludeGroupStyle> styles;
	private final Map<IncludeKind, Category> categories = new HashMap<IncludeKind, Category>();
	private TreeListDialogField<Category> categoryTree;
	private PixelConverter pixelConverter;
	private StackLayout editorAreaStack;
	private Category selectedCategory;

	public IncludeCategoriesBlock(IStatusChangeListener context, IProject project,
			IWorkbenchPreferenceContainer container, List<IncludeGroupStyle> styles) {
		super(context, project, new Key[0], container);
		this.styles = styles;
		createCategories();
	}

	private void createCategories() {
		createCategory(IncludeKind.RELATED);
		createCategory(IncludeKind.PARTNER);
		createCategory(IncludeKind.IN_SAME_FOLDER);
		createCategory(IncludeKind.IN_SUBFOLDER);
		createCategory(IncludeKind.SYSTEM);
		createCategory(IncludeKind.SYSTEM_WITH_EXTENSION);
		createCategory(IncludeKind.SYSTEM_WITHOUT_EXTENSION);
		createCategory(IncludeKind.OTHER);
		createCategory(IncludeKind.IN_SAME_PROJECT);
		createCategory(IncludeKind.IN_OTHER_PROJECT);
		createCategory(IncludeKind.EXTERNAL);
	}

	private Category createCategory(IncludeKind includeKind) {
		Category parentCategory = categories.get(includeKind.parent);
		Category category = new Category(includeKind, parentCategory);
		categories.put(category.getIncludeKind(), category);
		return category;
	}

	public void postSetSelection(Object element) {
		categoryTree.postSetSelection(new StructuredSelection(element));
	}

	@Override
	protected Control createContents(Composite parent) {
		pixelConverter =  new PixelConverter(parent);

		setShell(parent.getShell());

		Composite composite =  new Composite(parent, SWT.NONE);
		composite.setFont(parent.getFont());

		GridLayout layout = new GridLayout();
		layout.marginHeight = 0;
		layout.marginWidth = 0;
		composite.setLayout(layout);

		IncludeStyleAdapter adapter = new IncludeStyleAdapter();
		categoryTree = new TreeListDialogField<Category>(adapter, null, new IncludeStyleLabelProvider());
		categoryTree.setDialogFieldListener(adapter);
		categoryTree.setLabelText(PreferencesMessages.IncludeCategoriesBlock_header_categories);
		categoryTree.setViewerComparator(adapter);

		for (Category category : categories.values()) {
			if (category.parent == null)
				categoryTree.addElement(category);
		}

		Label label = categoryTree.getLabelControl(composite);
		label.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, false, false));

		Control tree = categoryTree.getTreeControl(composite);
		GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true);
		gd.widthHint = pixelConverter.convertWidthInCharsToPixels(50);
		gd.heightHint = pixelConverter.convertHeightInCharsToPixels(2);
		tree.setLayoutData(gd);

		createCategoryEditors(composite);

		categoryTree.setTreeExpansionLevel(2);
		categoryTree.selectFirstElement();

		updateControls();
		return composite;
	}

	private void createCategoryEditors(Composite parent) {
		Composite editorArea =  new Composite(parent, SWT.NONE);
		editorArea.setLayoutData(new GridData(SWT.FILL, SWT.BOTTOM, true, false));
		editorArea.setFont(parent.getFont());
		editorAreaStack = new StackLayout();
		editorArea.setLayout(editorAreaStack);
		Map<IncludeKind, IncludeGroupStyle> stylesByKind = new HashMap<IncludeKind, IncludeGroupStyle>();
		for (IncludeGroupStyle style : styles) {
			if (style.getIncludeKind() != IncludeKind.MATCHING_PATTERN)
				stylesByKind.put(style.getIncludeKind(), style);
		}

		for (Category category : categories.values()) {
			IncludeGroupStyleBlock block = new IncludeGroupStyleBlock(fContext, fProject, fContainer,
					category.getDescription());
			IncludeGroupStyle style = stylesByKind.get(category.getIncludeKind());
			block.setStyle(style);
			Control composite = block.createContents(editorArea);
			category.setEditor(block, composite);
		}
	}

	@Override
	protected void updateControls() {
		super.updateControls();
		// Refresh
		categoryTree.refresh();
		updateConfigurationBlock(categoryTree.getSelectedElements());
		for (Category category : categories.values()) {
			category.getEditor().updateControls();
		}
	}

	private void updateConfigurationBlock(List<Object> selection) {
		if (selection.size() == 0)
			return;
		selectedCategory = (Category) selection.get(0);
		editorAreaStack.topControl = selectedCategory.getEditorArea();
		editorAreaStack.topControl.getParent().layout();
	}

	@Override
	protected void validateSettings(Key changedKey, String oldValue, String newValue) {
		fContext.statusChanged(new StatusInfo());
	}

    /**
     * Represents a category of settings.
     */
	private final static class Category {
		public final Category parent;
		public final int index;  // Index in the siblings list
		private final List<Category> children;
		private final IncludeKind includeKind;
		private Control editorArea;
		private IncludeGroupStyleBlock editor;

		Category(IncludeKind includeKind, Category parent) {
			this.includeKind = includeKind;
			this.parent = parent;
			children = new ArrayList<Category>();
			index = parent != null ? parent.addChild(this) : 0;
		}

		private int addChild(Category category) {
			children.add(category);
			return children.size() - 1;
		}

		Category[] getChildren() {
			return children.toArray(new Category[children.size()]);
		}

		boolean hasChildren() {
			return !children.isEmpty();
		}

		@Override
		public String toString() {
			return includeKind.name;
		}

		IncludeKind getIncludeKind() {
			return includeKind;
		}

		IncludeGroupStyleBlock getEditor() {
			return editor;
		}

		Control getEditorArea() {
			return editorArea;
		}

		void setEditor(IncludeGroupStyleBlock editor, Control editorArea) {
			this.editor = editor;
			this.editorArea = editorArea;
		}

		String getName() {
			return includeKind.name;
		}

		String getDescription() {
			return includeKind.description;
		}
	}

	private class IncludeStyleAdapter extends ViewerComparator
			implements ITreeListAdapter<Category>, IDialogFieldListener {
		@Override
		public void selectionChanged(TreeListDialogField<Category> field) {
			updateConfigurationBlock(field.getSelectedElements());
		}

		@Override
		public void customButtonPressed(TreeListDialogField<Category> field, int index) {
		}

		@Override
		public void doubleClicked(TreeListDialogField<Category> field) {
		}

		@Override
		public Category[] getChildren(TreeListDialogField<Category> field, Object element) {
			return ((Category) element).getChildren();
		}

		@Override
		public Category getParent(TreeListDialogField<Category> field, Object element) {
			return ((Category) element).parent;
		}

		@Override
		public boolean hasChildren(TreeListDialogField<Category> field, Object element) {
			return ((Category) element).hasChildren();
		}

		@Override
		public void dialogFieldChanged(DialogField field) {
		}

		@Override
		public void keyPressed(TreeListDialogField<Category> field, KeyEvent event) {
		}

		@Override
		public int category(Object element) {
			return ((Category) element).index;
		}
	}

	private static class IncludeStyleLabelProvider extends LabelProvider {
		@Override
		public Image getImage(Object element) {
			return null;
		}

		@Override
		public String getText(Object element) {
			return ((Category) element).getName();
		}
	}
}
