/*******************************************************************************
 * Copyright (c) 2011 Petri Tuononen 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:
 * Petri Tuononen - Initial implementation
 *******************************************************************************/
package org.eclipse.cdt.managedbuilder.pkgconfig.properties;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
import org.eclipse.cdt.core.settings.model.ICProjectDescription;
import org.eclipse.cdt.core.settings.model.ICResourceDescription;
import org.eclipse.cdt.core.settings.model.ICStorageElement;
import org.eclipse.cdt.managedbuilder.core.IManagedBuildInfo;
import org.eclipse.cdt.managedbuilder.core.ITool;
import org.eclipse.cdt.managedbuilder.core.IToolChain;
import org.eclipse.cdt.managedbuilder.core.ManagedBuildManager;
import org.eclipse.cdt.managedbuilder.pkgconfig.Activator;
import org.eclipse.cdt.managedbuilder.pkgconfig.preferences.LibDirFieldEditor;
import org.eclipse.cdt.managedbuilder.pkgconfig.preferences.Messages;
import org.eclipse.cdt.managedbuilder.pkgconfig.preferences.PkgConfigPathListEditor;
import org.eclipse.cdt.managedbuilder.pkgconfig.preferences.PkgConfigSettingsDialog;
import org.eclipse.cdt.managedbuilder.pkgconfig.preferences.PreferenceStore;
import org.eclipse.cdt.managedbuilder.pkgconfig.settings.PkgConfigExternalSettingProvider;
import org.eclipse.cdt.managedbuilder.pkgconfig.util.Parser;
import org.eclipse.cdt.managedbuilder.pkgconfig.util.PathToToolOption;
import org.eclipse.cdt.managedbuilder.pkgconfig.util.PkgConfigUtil;
import org.eclipse.cdt.ui.newui.AbstractCPropertyTab;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTableViewer;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;

/**
 * Property tab to select packages and add pkg-config output of checked packages
 * to compiler and linker (MBS).
 * 
 */
public class PkgConfigPropertyTab extends AbstractCPropertyTab {

	PkgConfigPathListEditor configPathListEditor;
	LibDirFieldEditor libDirEditor;
	CheckboxTableViewer pkgCfgViewer;
	private Set<Object> previouslyChecked;
	private ArrayList<Object> newItems = new ArrayList<Object>();
	private static final int BUTTON_SELECT = 0;
	private static final int BUTTON_DESELECT = 1;
	private static final int BUTTON_ADVANCED = 2;
	private final String PACKAGES = "packages"; //$NON-NLS-1$
	private boolean reindexToggle = false;

	private SashForm sashForm;

	private static final String[] BUTTONS = new String[] { "Select", //$NON-NLS-1$
			"Deselect", //$NON-NLS-1$
			"Advanced..." //$NON-NLS-1$
	};

	private IToolChain selectedToolChain;

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.eclipse.cdt.ui.newui.AbstractCPropertyTab#createControls(org.eclipse
	 * .swt.widgets.Composite)
	 */
	@Override
	public void createControls(Composite parent) {
		super.createControls(parent);
		this.usercomp.setLayout(new GridLayout(1, false));

		this.sashForm = new SashForm(this.usercomp, SWT.NONE);
		this.sashForm.setBackground(this.sashForm.getDisplay().getSystemColor(
				SWT.COLOR_GRAY));
		this.sashForm.setLayoutData(new GridData(GridData.FILL_BOTH));

		GridLayout layout = new GridLayout(1, false);
		layout.marginHeight = 5;
		this.sashForm.setLayout(layout);

		final Composite c1 = new Composite(this.sashForm, SWT.NONE);
		GridLayout layout2 = new GridLayout(3, false);
		c1.setLayout(layout2);

		this.pkgCfgViewer = CheckboxTableViewer
				.newCheckList(c1, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL
						| SWT.FULL_SELECTION | SWT.BORDER);
		final Table tbl = this.pkgCfgViewer.getTable();
		tbl.setHeaderVisible(true);
		tbl.setLinesVisible(true);
		GridData gd = new GridData(GridData.FILL_HORIZONTAL);
		gd = new GridData(GridData.FILL_BOTH);
		gd.horizontalSpan = 2;
		tbl.setLayoutData(gd);

		createColumns(c1, this.pkgCfgViewer);
		this.pkgCfgViewer.setContentProvider(new ArrayContentProvider());
		this.selectedToolChain = getSelectedToolchain();
		updatePkgConfigBinPath();
		this.pkgCfgViewer.setInput(new DataModelProvider(this.page.getProject()
				.getName()).getEntries());

		this.pkgCfgViewer.addCheckStateListener(new PkgListener());

		this.pkgCfgViewer.addDoubleClickListener(new IDoubleClickListener() {
			@Override
			public void doubleClick(DoubleClickEvent event) {
				TableItem itm = tbl.getSelection()[0];
				if (itm.getChecked()) {
					itm.setChecked(false);
				} else {
					itm.setChecked(true);
				}
				handleCheckStateChange();
			}
		});

		// buttons
		Composite compositeButtons = new Composite(c1, SWT.NONE);
		initButtons(compositeButtons, BUTTONS);

		initializePackageStates();
		this.previouslyChecked = new HashSet<Object>(
				Arrays.asList(getCheckedItems()));
	}

	@Override
	public void handleTabEvent(int kind, Object data) {

		if (this.selectedToolChain != null
				&& this.selectedToolChain.getId() != getSelectedToolchain()
						.getId()) {
			this.selectedToolChain = getSelectedToolchain();
			updatePkgConfigBinPath();
			if (PkgConfigPropertyTab.this.pkgCfgViewer != null) {
				PkgConfigPropertyTab.this.pkgCfgViewer
						.setInput(new DataModelProvider(
								PkgConfigPropertyTab.this.page.getProject()
										.getName()).getEntries());
				PkgConfigPropertyTab.this.pkgCfgViewer.refresh();
			}
		}
	}

	/**
	 * Get checked items.
	 * 
	 * @return
	 */
	private Object[] getCheckedItems() {
		return this.pkgCfgViewer.getCheckedElements();
	}

	/**
	 * Action for the check state change.
	 */
	void handleCheckStateChange() {
		Object[] checkedItems = getCheckedItems();

		// check if new items checked
		if (checkedItems.length > this.previouslyChecked.size()) {
			// add checked items to an array list
			for (Object o : checkedItems) {
				// if new item
				if (!this.previouslyChecked.contains(o)) {
					this.newItems.add(o);
				}
			}
			addPackageValues(this.newItems.toArray(), this.page.getProject());
			this.reindexToggle = true;
		}

		saveChecked();
		updateData(getResDesc());
		this.previouslyChecked = new HashSet<Object>(
				Arrays.asList(checkedItems));
		this.newItems.clear();
	}

	/**
	 * Add new flags that the packages need to Tools' Options. Only for other
	 * flags.
	 * 
	 * @param addedItems
	 *            Object[]
	 * @param proj
	 *            IProject
	 */
	private static void addPackageValues(Object[] addedItems, IProject proj) {
		for (Object item : addedItems) {
			// handle options
			String cflags = PkgConfigUtil.getCflags(item.toString(),
					proj.getName());
			String[] optionsArray = Parser.parseCflagOptions(cflags);
			if (optionsArray != null) {
				for (String option : optionsArray) {
					PathToToolOption.addOtherFlag(option, proj);
				}
			}
		}
		ManagedBuildManager.saveBuildInfo(proj, true);
	}

	/**
	 * Initializes the check state of the packages from the storage.
	 */
	private void initializePackageStates() {
		ICConfigurationDescription desc = getResDesc().getConfiguration();
		ICStorageElement strgElem = null;
		try {
			strgElem = desc.getStorage(this.PACKAGES, true);
			TableItem[] items = this.pkgCfgViewer.getTable().getItems();
			String value = null;
			for (TableItem item : items) {
				/*
				 * The package names with + symbols were converted so that + ->
				 * plus in order to prevent an error when saving to
				 * ICStorageElement.
				 */
				if (item.getText().contains("+")) { //$NON-NLS-1$
					String newItemName = item.getText().replace("+", "plus"); //$NON-NLS-1$ //$NON-NLS-2$
					value = strgElem.getAttribute(newItemName);
				} else {
					value = strgElem.getAttribute(item.getText());
				}
				if (value != null) {
					if (value.equals("true")) { //$NON-NLS-1$
						item.setChecked(true);
					}
				}
			}
		} catch (CoreException e) {
			Activator.getDefault().log(e, "Initialization of packages failed."); //$NON-NLS-1$
		}
	}

	/**
	 * Saves checked state of the packages.
	 */
	private void saveChecked() {
		ICConfigurationDescription desc = getResDesc().getConfiguration();
		ICStorageElement strgElem = null;
		// get storage or create one if it doesn't exist
		try {
			strgElem = desc.getStorage(this.PACKAGES, true);
		} catch (CoreException e) {
			Activator.getDefault().log(e,
					"Getting packages from the storage failed."); //$NON-NLS-1$
		}
		TableItem[] items = this.pkgCfgViewer.getTable().getItems();
		for (TableItem item : items) {
			if (item != null) {
				String chkd;
				// form literal form of boolean state
				if (item.getChecked()) {
					chkd = "true"; //$NON-NLS-1$
				} else {
					chkd = "false"; //$NON-NLS-1$
				}
				/*
				 * add package name and the checkbox state to the storage
				 */
				try {
					String pkgName = item.getText();
					// need to convert + symbols to "plus"
					if (pkgName.contains("+")) { //$NON-NLS-1$
						String newPkgName = pkgName.replace("+", "plus"); //$NON-NLS-1$ //$NON-NLS-2$
						if (strgElem != null) {
							strgElem.setAttribute(newPkgName, chkd);
						}
					} else {
						if (strgElem != null) {
							strgElem.setAttribute(pkgName, chkd);
						}
					}
				} catch (Exception e) {
					Activator.getDefault().log(e,
							"Setting attribute to ICStorageElement failed."); //$NON-NLS-1$
					// Seems like ICStorageElement cannot store Strings with +
					/*
					 * INVALID_CHARACTER_ERR: An invalid or illegal XML
					 * character is specified.
					 */
				}
			}
		}
	}

	@Override
	protected void performApply(ICResourceDescription src,
			ICResourceDescription dst) {
		updateData(getResDesc());
	}

	@Override
	protected void performDefaults() {
		// uncheck every checkbox
		this.pkgCfgViewer.setCheckedElements(new Object[] {});

		// remove values from Tools Options
		handleCheckStateChange();
	}

	@Override
	protected void performOK() {
		// freshen index if new packages have been selected
		if (this.reindexToggle) {
			rebuiltIndex();
		}
		this.reindexToggle = false;
	}

	@Override
	protected void updateButtons() {
		// nothing here
	}

	@Override
	protected void updateData(ICResourceDescription cfg) {
		final ICConfigurationDescription confDesc = cfg.getConfiguration();
		ICProjectDescription projDesc = confDesc.getProjectDescription();

		Job j = new Job("Update Pkg-config exernal settings provider") { //$NON-NLS-1$
			@Override
			protected IStatus run(IProgressMonitor monitor) {
				// a set holding external setting providers
				Set<String> externalSettingsProviders = new LinkedHashSet<String>(
						Arrays.asList(confDesc.getExternalSettingsProviderIds()));

				// remove pkg-config external setting provider
				externalSettingsProviders
						.remove(PkgConfigExternalSettingProvider.ID);
				confDesc.setExternalSettingsProviderIds(externalSettingsProviders
						.toArray(new String[externalSettingsProviders.size()]));

				// add pkg-config external setting provider
				externalSettingsProviders
						.add(PkgConfigExternalSettingProvider.ID);
				confDesc.setExternalSettingsProviderIds(externalSettingsProviders
						.toArray(new String[externalSettingsProviders.size()]));

				// update external setting providers
				confDesc.updateExternalSettingsProviders(new String[] { PkgConfigExternalSettingProvider.ID });
				return Status.OK_STATUS;
			}
		};
		j.setPriority(Job.INTERACTIVE);
		j.schedule();

		try {
			CoreModel.getDefault().setProjectDescription(
					this.page.getProject(), projDesc);
		} catch (CoreException e) {
			Activator.getDefault().log(e,
					"Setting/updating the project description failed."); //$NON-NLS-1$
		}
	}

	/**
	 * Check state listener for the table viewer.
	 * 
	 */
	public class PkgListener implements ICheckStateListener {

		@Override
		public void checkStateChanged(CheckStateChangedEvent e) {
			handleCheckStateChange();
		}
	}

	/**
	 * Creates table columns, headers and sets the size of the columns.
	 * 
	 * @param parent
	 * @param viewer
	 */
	private void createColumns(
			@SuppressWarnings("unused") final Composite parent,
			@SuppressWarnings("unused") final TableViewer viewer) {
		String[] titles = { "Packages", "Description" }; //$NON-NLS-1$ //$NON-NLS-2$
		int[] bounds = { 200, 450 };

		// first column is for the package
		TableViewerColumn col = createTableViewerColumn(titles[0], bounds[0]);
		col.setLabelProvider(new ColumnLabelProvider() {
			@Override
			public String getText(Object element) {
				DataModel dm = (DataModel) element;
				return dm.getPackage();
			}
		});

		// second column is for the description
		col = createTableViewerColumn(titles[1], bounds[1]);
		col.setLabelProvider(new ColumnLabelProvider() {
			@Override
			public String getText(Object element) {
				DataModel dm = (DataModel) element;
				return dm.getDescription();
			}
		});
	}

	/**
	 * Creates a column for the table viewer.
	 * 
	 * @param title
	 * @param bound
	 * @return
	 */
	private TableViewerColumn createTableViewerColumn(String title, int bound) {

		final TableViewerColumn viewerColumn = new TableViewerColumn(
				this.pkgCfgViewer, SWT.NONE);
		final TableColumn column = viewerColumn.getColumn();

		column.setText(title);
		column.setWidth(bound);
		column.setResizable(true);

		return viewerColumn;
	}

	/**
	 * Get selected item(s).
	 * 
	 * @return
	 */
	private TableItem[] getSelected() {
		TableItem[] selected = this.pkgCfgViewer.getTable().getSelection();
		return selected;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.cdt.ui.newui.AbstractCPropertyTab#buttonPressed(int)
	 */
	@Override
	public void buttonPressed(int n) {
		switch (n) {
		case BUTTON_SELECT:
			selectedButtonPressed();
			break;
		case BUTTON_DESELECT:
			deselectedButtonPressed();
			break;
		case BUTTON_ADVANCED:
			advancedButtonPressed();
			break;
		default:
			break;
		}
		updateButtons();
	}

	/**
	 * Action for the Select button.
	 */
	private void selectedButtonPressed() {
		TableItem[] selected = getSelected();
		for (TableItem itm : selected) {
			itm.setChecked(true);
		}
		handleCheckStateChange();
	}

	/**
	 * Action for the Deselect button.
	 */
	private void deselectedButtonPressed() {
		TableItem[] selected = getSelected();
		for (TableItem itm : selected) {
			itm.setChecked(false);
		}
		handleCheckStateChange();
	}

	/**
	 * Action for the Select button.
	 */
	private void advancedButtonPressed() {
		// Create new dialog
		PkgConfigSettingsDialog dialog = new PkgConfigSettingsDialog(
				this.usercomp.getShell(), Messages.PkgConfigPropertyTab_0,
				this.page.getProject());
		dialog.open();
		if (PkgConfigPropertyTab.this.pkgCfgViewer != null) {
			// Update pkg-config libraries for the project
			updatePkgConfigBinPath();
			PkgConfigPropertyTab.this.pkgCfgViewer
					.setInput(new DataModelProvider(
							PkgConfigPropertyTab.this.page.getProject()
									.getName()).getEntries());
		}
	}

	/**
	 * Update the pkg-config path according to the toolchain if the default
	 * toolchain executable is selected by the user in the pkg-config advanced
	 * preferences.
	 */
	private void updatePkgConfigBinPath() {
		// Search if a toolchain tool defines the pkg-config tool bin
		// path
		String pkgConfigPath = ""; //$NON-NLS-1$
		if (this.page != null
				&& PreferenceStore.isPkgConfigExecutableDefault(this.page
						.getProject().getName())) {
			for (ITool tool : this.selectedToolChain
					.getToolsBySuperClassId("org.eclipse.cdt.managedbuilder.pkgconfig.tool")) { //$NON-NLS-1$
				pkgConfigPath = tool.getToolCommand();
			}

			PreferenceStore.setPkgConfigBinPath(pkgConfigPath, this.page
					.getProject().getName());
		}
	}

	private IToolChain getSelectedToolchain() {
		if (this.page != null) {
			IManagedBuildInfo info = ManagedBuildManager.getBuildInfo(this.page
					.getProject());
			if (info != null) {
				return info.getDefaultConfiguration().getToolChain();

			}
		}
		return null;
	}

	/**
	 * Rebuilts the index of the selected project in the workspace.
	 */
	private void rebuiltIndex() {
		ICProject cproject = CoreModel.getDefault().getCModel()
				.getCProject(this.page.getProject().getName());
		CCorePlugin.getIndexManager().reindex(cproject);
	}

}