/*******************************************************************************
 * Copyright (c) 2010 SAP AG.
 * 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:
 *    Mathias Kinzler (SAP AG) - initial implementation
 *******************************************************************************/
package org.eclipse.egit.ui.internal.repository;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

import org.eclipse.egit.ui.Activator;
import org.eclipse.egit.ui.internal.UIIcons;
import org.eclipse.egit.ui.internal.UIText;
import org.eclipse.egit.ui.internal.preferences.ConfigurationEditorComponent;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.ActionContributionItem;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.TitleAreaDialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.SystemReader;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.views.properties.IPropertyDescriptor;
import org.eclipse.ui.views.properties.IPropertySource;
import org.eclipse.ui.views.properties.PropertyDescriptor;
import org.eclipse.ui.views.properties.PropertySheetPage;

/**
 * Properties for repository and user configuration (read-only).
 * <p>
 * Depending on which mode is selected, either the user configuration, the
 * repository configuration, or the effective configuration is shown.
 */
public class RepositoryPropertySource implements IPropertySource {

	static final String CHANGEMODEACTIONID = "ChangeMode"; //$NON-NLS-1$

	static final String SINGLEVALUEACTIONID = "SingleValueToggle"; //$NON-NLS-1$

	static final String EDITACTIONID = "Edit"; //$NON-NLS-1$

	private static final String SYSTEM_ID_PREFIX = "system"; //$NON-NLS-1$

	private static final String USER_ID_PREFIX = "user"; //$NON-NLS-1$

	private static final String REPO_ID_PREFIX = "repo"; //$NON-NLS-1$

	private static final String EFFECTIVE_ID_PREFIX = "effe"; //$NON-NLS-1$

	private static class EditAction extends Action {

		private RepositoryPropertySource source;

		public EditAction(String text, ImageDescriptor image,
				RepositoryPropertySource source) {
			super(text, image);
			this.source = source;
		}

		public EditAction setSource(RepositoryPropertySource source) {
			this.source = source;
			return this;
		}

		@Override
		public String getId() {
			return EDITACTIONID;
		}

		@Override
		public void run() {
			final StoredConfig config;

			DisplayMode mode = source.getCurrentMode();
			switch (mode) {
			case EFFECTIVE:
				return;
			case SYSTEM:
				config = source.systemConfig;
				break;
			case USER:
				config = source.userHomeConfig;
				break;
			case REPO:
				config = source.repositoryConfig;
				break;
			default:
				return;
			}

			new EditDialog(source.myPage.getSite().getShell(),
					(FileBasedConfig) config, mode.getText()).open();
			source.myPage.refresh();
		}

		@Override
		public int getStyle() {
			return IAction.AS_PUSH_BUTTON;
		}

	}

	private final PropertySheetPage myPage;

	private final FileBasedConfig systemConfig;

	private final FileBasedConfig userHomeConfig;

	private final StoredConfig repositoryConfig;

	private final StoredConfig effectiveConfig;

	private ActionContributionItem changeModeAction;

	private ActionContributionItem editAction;

	private ActionContributionItem singleValueToggleAction;

	/**
	 * @param repository
	 *            the repository
	 * @param page
	 *            the page showing the properties
	 */
	public RepositoryPropertySource(Repository repository,
			PropertySheetPage page) {
		myPage = page;

		effectiveConfig = repository.getConfig();
		systemConfig = SystemReader.getInstance().openSystemConfig(null, FS.DETECTED);
		userHomeConfig = SystemReader.getInstance().openUserConfig(null, FS.DETECTED);

		if (effectiveConfig instanceof FileBasedConfig) {
			File configFile = ((FileBasedConfig) effectiveConfig).getFile();
			repositoryConfig = new FileBasedConfig(configFile, repository
					.getFS());
		} else {
			repositoryConfig = effectiveConfig;
		}

		synchronized (myPage) {
			// check if the actions are already there, if not, create them
			IActionBars bars = myPage.getSite().getActionBars();

			changeModeAction = (ActionContributionItem) bars
					.getToolBarManager().find(CHANGEMODEACTIONID);
			singleValueToggleAction = (ActionContributionItem) bars
					.getToolBarManager().find(SINGLEVALUEACTIONID);

			editAction = ((ActionContributionItem) bars.getToolBarManager()
					.find(EDITACTIONID));
			if (editAction != null)
				((EditAction) editAction.getAction()).setSource(this);

			if (changeModeAction != null) {
				return;
			}

			changeModeAction = new ActionContributionItem(new Action(
					DisplayMode.REPO.getText(), IAction.AS_DROP_DOWN_MENU) {
				@Override
				public String getId() {
					return CHANGEMODEACTIONID;
				}

				@Override
				public void run() {
					MenuManager mgr = new MenuManager();
					ToolItem item = (ToolItem) changeModeAction.getWidget();
					ToolBar control = item.getParent();
					final Menu ctxMenu = mgr.createContextMenu(control);

					for (final DisplayMode aMode : DisplayMode.values()) {
						mgr.add(new Action(aMode.getText()) {
							@Override
							public void run() {
								changeModeAction.getAction().setText(
										aMode.getText());
								editAction.getAction().setEnabled(
										aMode != DisplayMode.EFFECTIVE);
								myPage.refresh();
							}

							@Override
							public boolean isEnabled() {
								return aMode != getCurrentMode();
							}

							@Override
							public boolean isChecked() {
								return aMode == getCurrentMode();
							}

							@Override
							public int getStyle() {
								return IAction.AS_CHECK_BOX;
							}
						});
					}

					ctxMenu.setVisible(true);
				}

				@Override
				public String getToolTipText() {
					return UIText.RepositoryPropertySource_SelectModeTooltip;
				}

				@Override
				public int getStyle() {
					return IAction.AS_DROP_DOWN_MENU;
				}

			});

			editAction = new ActionContributionItem(new EditAction(
					UIText.RepositoryPropertySource_EditConfigButton,
					UIIcons.EDITCONFIG, this));

			singleValueToggleAction = new ActionContributionItem(new Action(
					UIText.RepositoryPropertySource_SingleValueButton) {
				@Override
				public String getId() {
					return SINGLEVALUEACTIONID;
				}

				@Override
				public void run() {
					myPage.refresh();
				}

				@Override
				public int getStyle() {
					return IAction.AS_CHECK_BOX;
				}

				@Override
				public String getToolTipText() {
					return UIText.RepositoryPropertySource_SuppressMultipleValueTooltip;
				}
			});

			bars.getToolBarManager().add(new Separator());
			bars.getToolBarManager().add(changeModeAction);
			bars.getToolBarManager().add(singleValueToggleAction);
			bars.getToolBarManager().add(editAction);

			bars.getToolBarManager().update(false);
		}
	}

	private DisplayMode getCurrentMode() {
		String actionText = changeModeAction.getAction().getText();
		for (DisplayMode aMode : DisplayMode.values()) {
			if (aMode.getText().equals(actionText))
				return aMode;
		}
		return null;
	}

	private boolean getSingleValueMode() {
		if (singleValueToggleAction != null)
			return singleValueToggleAction.getAction().isChecked();
		return false;
	}

	private Object getValueFromConfig(Config config, String keyString) {
		StringTokenizer tok = new StringTokenizer(keyString, "."); //$NON-NLS-1$

		String section;
		String subsection;
		String name;

		String[] valueList = null;
		if (tok.countTokens() == 2) {
			section = tok.nextToken();
			subsection = null;
			name = tok.nextToken();
		} else if (tok.countTokens() == 3) {
			section = tok.nextToken();
			subsection = tok.nextToken();
			name = tok.nextToken();
		} else {
			return ""; //$NON-NLS-1$
		}

		if (getSingleValueMode())
			valueList = new String[] { config.getString(section, subsection,
					name) };
		else
			valueList = config.getStringList(section, subsection, name);

		if (valueList == null || valueList.length == 0)
			return null;

		if (valueList.length == 1) {
			return valueList[0];
		}

		StringBuilder sb = new StringBuilder();
		for (String value : valueList) {
			sb.append('[');
			sb.append(value);
			sb.append(']');
		}

		return sb.toString();
	}

	public Object getEditableValue() {
		return null;
	}

	public IPropertyDescriptor[] getPropertyDescriptors() {
		try {
			systemConfig.load();
			userHomeConfig.load();
			repositoryConfig.load();
			effectiveConfig.load();
		} catch (IOException e) {
			showExceptionMessage(e);
		} catch (ConfigInvalidException e) {
			showExceptionMessage(e);
		}

		List<IPropertyDescriptor> resultList = new ArrayList<IPropertyDescriptor>();

		StoredConfig config;
		String category;
		String prefix;
		boolean recursive = false;
		switch (getCurrentMode()) {
		case EFFECTIVE:
			prefix = EFFECTIVE_ID_PREFIX;
			category = UIText.RepositoryPropertySource_EffectiveConfigurationCategory;
			config = effectiveConfig;
			recursive = true;
			break;
		case REPO: {
			prefix = REPO_ID_PREFIX;
			String location = ""; //$NON-NLS-1$
			if (repositoryConfig instanceof FileBasedConfig) {
				location = ((FileBasedConfig) repositoryConfig).getFile()
						.getAbsolutePath();
			}
			category = NLS
					.bind(
							UIText.RepositoryPropertySource_RepositoryConfigurationCategory,
							location);
			config = repositoryConfig;
			break;
		}
		case USER: {
			prefix = USER_ID_PREFIX;
			String location = userHomeConfig.getFile().getAbsolutePath();
			category = NLS
					.bind(UIText.RepositoryPropertySource_GlobalConfigurationCategory,
							location);
			config = userHomeConfig;
			break;
		}
		case SYSTEM: {
			prefix = SYSTEM_ID_PREFIX;
			String location = systemConfig.getFile().getAbsolutePath();
			category = NLS
					.bind(UIText.RepositoryPropertySource_GlobalConfigurationCategory,
							location);
			config = systemConfig;
			break;
		}
		default:
			return new IPropertyDescriptor[0];
		}
		for (String key : config.getSections()) {
			for (String sectionItem : config.getNames(key, recursive)) {
				String sectionId = key + "." + sectionItem; //$NON-NLS-1$
				PropertyDescriptor desc = new PropertyDescriptor(prefix
						+ sectionId, sectionId);
				desc.setCategory(category);
				resultList.add(desc);
			}
			for (String sub : config.getSubsections(key)) {
				for (String sectionItem : config.getNames(key, sub, recursive)) {
					String sectionId = key + "." + sub + "." + sectionItem; //$NON-NLS-1$ //$NON-NLS-2$
					PropertyDescriptor desc = new PropertyDescriptor(prefix
							+ sectionId, sectionId);
					desc.setCategory(category);
					resultList.add(desc);
				}
			}
		}

		return resultList.toArray(new IPropertyDescriptor[0]);
	}

	public Object getPropertyValue(Object id) {
		String actId = ((String) id);
		Object value = null;
		if (actId.startsWith(SYSTEM_ID_PREFIX)) {
			value = getValueFromConfig(systemConfig, actId.substring(4));
		} else if (actId.startsWith(USER_ID_PREFIX)) {
			value = getValueFromConfig(userHomeConfig, actId.substring(4));
		} else if (actId.startsWith(REPO_ID_PREFIX)) {
			value = getValueFromConfig(repositoryConfig, actId.substring(4));
		} else if (actId.startsWith(EFFECTIVE_ID_PREFIX)) {
			value = getValueFromConfig(effectiveConfig, actId.substring(4));
		}
		if (value == null)
			// the text editor needs this to work
			return ""; //$NON-NLS-1$

		return value;
	}

	public boolean isPropertySet(Object id) {
		return false;
	}

	public void resetPropertyValue(Object id) {
		// no editing here
	}

	public void setPropertyValue(Object id, Object value) {
		// no editing here
	}

	private void showExceptionMessage(Exception e) {
		Activator.handleError(UIText.RepositoryPropertySource_ErrorHeader, e,
				true);
	}

	private enum DisplayMode {
		/* The effective configuration as obtained from the repository */
		EFFECTIVE(UIText.RepositoryPropertySource_EffectiveConfigurationAction),
		/* System wide configuration */
		SYSTEM(UIText.RepositoryPropertySource_SystemConfigurationMenu),
		/* The user specific configuration */
		USER(UIText.RepositoryPropertySource_GlobalConfigurationMenu),
		/* The repository specific configuration */
		REPO(UIText.RepositoryPropertySource_RepositoryConfigurationButton);

		/**
		 * @return the human-readable String for this mode
		 */
		String getText() {
			return this.text;
		}

		private final String text;

		private DisplayMode(String text) {
			this.text = text;
		}
	}

	/**
	 * Wraps the editor component into a dialog
	 */
	private final static class EditDialog extends TitleAreaDialog {
		private final FileBasedConfig myConfig;

		private final String myTitle;

		ConfigurationEditorComponent editor;

		public EditDialog(Shell shell, FileBasedConfig config, String title) {
			super(shell);
			myConfig = config;
			setShellStyle(getShellStyle() | SWT.SHELL_TRIM);
			myTitle = title;
			setHelpAvailable(false);
		}

		@Override
		protected Control createDialogArea(Composite parent) {
			Composite main = (Composite) super.createDialogArea(parent);
			GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(true,
					true).applyTo(main);
			editor = new ConfigurationEditorComponent(main, myConfig, true, false) {
				@Override
				protected void setErrorMessage(String message) {
					EditDialog.this.setErrorMessage(message);
				}

				@Override
				protected void setDirty(boolean dirty) {
					getButton(IDialogConstants.OK_ID).setEnabled(dirty);
				}
			};

			Control result = editor.createContents();
			applyDialogFont(main);
			return result;
		}

		@Override
		protected void configureShell(Shell newShell) {
			super.configureShell(newShell);
			newShell
					.setText(UIText.RepositoryPropertySource_EditConfigurationTitle);
			newShell.setSize(700, 600);
		}

		@Override
		public void create() {
			super.create();
			setTitle(myTitle);
			setMessage(UIText.RepositoryPropertySource_EditorMessage);
			getButton(IDialogConstants.OK_ID).setEnabled(false);
		}

		@Override
		protected void okPressed() {
			try {
				editor.save();
				super.okPressed();
			} catch (IOException e) {
				Activator.handleError(e.getMessage(), e, true);
			}
		}
	}
}
