/*******************************************************************************
 * Copyright (c) 2007, 2008 IBM Corporation 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:
 * IBM Corporation - Initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.internal.ui.language;

import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.jface.layout.TableColumnLayout;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.swt.SWT;
import org.eclipse.swt.accessibility.AccessibleAdapter;
import org.eclipse.swt.accessibility.AccessibleEvent;
import org.eclipse.swt.custom.CCombo;
import org.eclipse.swt.custom.TableEditor;
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.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Link;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.ui.dialogs.PropertyPage;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.language.ProjectLanguageConfiguration;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.model.ILanguage;
import org.eclipse.cdt.core.model.LanguageManager;
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
import org.eclipse.cdt.core.settings.model.ICProjectDescription;
import org.eclipse.cdt.ui.CUIPlugin;

import org.eclipse.cdt.internal.core.CContentTypes;
import org.eclipse.cdt.internal.core.Util;
import org.eclipse.cdt.internal.core.language.LanguageMapping;
import org.eclipse.cdt.internal.core.language.LanguageMappingResolver;

import org.eclipse.cdt.internal.ui.preferences.PreferencesMessages;
import org.eclipse.cdt.internal.ui.util.Messages;

public class FileLanguageMappingPropertyPage extends PropertyPage {

	private static final int MINIMUM_COLUMN_WIDTH = 150;
	private static final int LANGUAGE_COLUMN = 1;
	private static final int CONFIGURATION_COLUMN = 0;
	
	private static final int LANGUAGE_ID = 0;
	private static final int LANGUAGE_NAME = 1;
	
	private static final String ALL_CONFIGURATIONS = ""; //$NON-NLS-1$
	
	private IContentType fContentType;
	private Composite fContents;
	private Table fTable;
	private ILanguage[] fLanguages;
	private Map<String, ILanguage> fLanguageIds;
	private boolean fHasChanges;
	
	public FileLanguageMappingPropertyPage() {
		super();
		fLanguages = LanguageManager.getInstance().getRegisteredLanguages();
		fLanguageIds = LanguageVerifier.computeAvailableLanguages();
	}
	
	@Override
	protected Control createContents(Composite parent) {
		IFile file = getFile();
		IProject project = file.getProject();
		fContentType = CContentTypes.getContentType(project, file.getLocation().toString());
		
		fContents = new Composite(parent, SWT.NONE);
		fContents.setLayout(new GridLayout(2, false));

		Label contentTypeLabel = new Label(fContents, SWT.NONE);
		contentTypeLabel.setText(PreferencesMessages.FileLanguagesPropertyPage_contentTypeLabel);
		contentTypeLabel.setLayoutData(new GridData(SWT.TRAIL, SWT.CENTER, false, false));
		
		Label contentTypeDescriptionLabel = new Label(fContents, SWT.NONE);
		contentTypeDescriptionLabel.setText(fContentType.getName());
		contentTypeDescriptionLabel.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false));
		
		try {
			createMappingTable(fContents, file);
		} catch (CoreException e) {
			CUIPlugin.log(e);
		}
		
		Link link = new Link(fContents, SWT.NONE);
		link.setText(PreferencesMessages.FileLanguagesPropertyPage_description);
		link.addListener(SWT.Selection, new LanguageMappingLinkListener(parent.getShell(), project) {
			@Override
			protected void refresh() {
				try {
					refreshMappings();
				} catch (CoreException e) {
					CUIPlugin.log(e);
				}
			}
		});
		
		link.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false, 2, 1));
		
		fContents.pack();
		return fContents;
	}

	private void createMappingTable(Composite contents, final IFile file) throws CoreException {
		Composite tableParent = new Composite(contents, SWT.NONE);
		tableParent.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));

		fTable = new Table(tableParent, SWT.MULTI | SWT.BORDER | SWT.V_SCROLL | SWT.FULL_SELECTION);
		fTable.setHeaderVisible(true);
		fTable.setLinesVisible(true);
		fTable.getAccessible().addAccessibleListener(new AccessibleAdapter() {
			@Override
			public void getName(AccessibleEvent e) {
				e.result = PreferencesMessages.FileLanguagesPropertyPage_mappingTableTitle;
			}
		});
		fTable.setToolTipText(PreferencesMessages.FileLanguagesPropertyPage_mappingTableTitle);

		TableColumn contentTypeColumn = new TableColumn(fTable, SWT.LEAD);
		contentTypeColumn.setText(PreferencesMessages.FileLanguagesPropertyPage_configurationColumn);

		TableColumn languageColumn = new TableColumn(fTable, SWT.LEAD);
		languageColumn.setText(PreferencesMessages.ProjectLanguagesPropertyPage_languageColumn);

		TableColumnLayout layout = new TableColumnLayout();
		layout.setColumnData(contentTypeColumn, new ColumnWeightData(1, MINIMUM_COLUMN_WIDTH, true));
		layout.setColumnData(languageColumn, new ColumnWeightData(1, MINIMUM_COLUMN_WIDTH, true));
		tableParent.setLayout(layout);
		
		final TableEditor editor = new TableEditor(fTable);
		editor.grabHorizontal = true;
		editor.grabVertical = true;
		editor.setColumn(LANGUAGE_COLUMN);
		
		final IProject project = file.getProject();

		fTable.addListener(SWT.Selection, new Listener() {
			public void handleEvent(Event event) {
				Control oldEditor = editor.getEditor();
				if (oldEditor != null) {
					oldEditor.dispose();
				}
				
				TableItem item = (TableItem) event.item;
				if (item == null) {
					return;
				}

				LanguageTableData data = (LanguageTableData) item.getData();
				CCombo newEditor = new CCombo(fTable, SWT.READ_ONLY);
				populateLanguages(project, file, data.configuration, data.languageId, newEditor);
				
				newEditor.addListener(SWT.Selection, new Listener() {
					public void handleEvent(Event event) {
						CCombo combo = (CCombo) editor.getEditor();
						int index = combo.getSelectionIndex();
						if (index != -1) {
							TableItem item = editor.getItem();
							item.setText(LANGUAGE_COLUMN, combo.getText());
							
							String selectedLanguage = ((String[]) combo.getData())[index];
							LanguageTableData data = (LanguageTableData) item.getData();
							data.languageId = selectedLanguage;
							fHasChanges = true;
							
							try {
								refreshMappings();
							} catch (CoreException e) {
								CUIPlugin.log(e);
							}
						}
					}
				});
				
				newEditor.setFocus();
				editor.setEditor(newEditor, item, LANGUAGE_COLUMN);
			}
		});
		
		populateLanguageTable(fTable);
		refreshMappings();
	}

	private void populateLanguageTable(Table table) throws CoreException {
		IFile file = getFile();
		IProject project = file.getProject();
		ICProjectDescription description = CoreModel.getDefault().getProjectDescription(project);
		ICConfigurationDescription[] configurations = description.getConfigurations();
		
		TableItem defaultItem = new TableItem(table, SWT.NONE);
		defaultItem.setText(CONFIGURATION_COLUMN, PreferencesMessages.FileLanguagesPropertyPage_defaultMapping);
		
		ProjectLanguageConfiguration config = LanguageManager.getInstance().getLanguageConfiguration(project);
		
		Set<String> missingLanguages = LanguageVerifier.removeMissingLanguages(config, description, fLanguageIds);
		if (missingLanguages.size() > 0) {
			MessageBox messageBox = new MessageBox(getShell(), SWT.ICON_WARNING | SWT.OK);
			messageBox.setText(PreferencesMessages.LanguageMappings_missingLanguageTitle);
			String affectedLanguages = LanguageVerifier.computeAffectedLanguages(missingLanguages);
			messageBox.setMessage(Messages.format(PreferencesMessages.FileLanguagesPropertyPage_missingLanguage, affectedLanguages));
			messageBox.open();
		}
		
		String defaultLanguageId = config.getLanguageForFile(null, file);
		LanguageTableData defaultData = new LanguageTableData(null, defaultLanguageId );
		defaultItem.setData(defaultData);
		
		for (int i = 0; i < configurations.length; i++) {
			TableItem item = new TableItem(table, SWT.NONE);
			item.setText(CONFIGURATION_COLUMN, configurations[i].getName());
			String languageId = config.getLanguageForFile(configurations[i], file);
			
			if (languageId != null) {
				ILanguage language = fLanguageIds.get(languageId);
				String languageName =  language.getName();
				item.setText(LANGUAGE_COLUMN, languageName);
			}
			
			LanguageTableData data = new LanguageTableData(configurations[i], languageId);
			item.setData(data);
		}
	}

	private void populateLanguages(IProject project, IFile file, ICConfigurationDescription configuration, String selectedLanguage, CCombo combo) {
		try {
			String[][] languageInfo = getLanguages(project, file, configuration);
			combo.setItems(languageInfo[LANGUAGE_NAME]);
			combo.setData(languageInfo[LANGUAGE_ID]);
			
			findSelection(configuration, selectedLanguage, combo);
			fContents.layout();
		} catch (CoreException e) {
			CCorePlugin.log(e);
		}
	}
	
	private void refreshMappings() throws CoreException {
		IFile file = getFile();
		IProject project = file.getProject();
		
		LanguageManager manager = LanguageManager.getInstance();
		TableItem[] items = fTable.getItems();
		for (int i = 0; i < items.length; i++) {
			TableItem item = items[i];
			LanguageTableData data = (LanguageTableData) item.getData();
			if (data.languageId == null) {
				LanguageMapping mapping = computeInheritedMapping(project, file, data.configuration);
				item.setText(LANGUAGE_COLUMN, computeInheritedFrom(data.configuration, mapping));
			} else {
				ILanguage language = manager.getLanguage(data.languageId);
				item.setText(LANGUAGE_COLUMN, language.getName());
			}
		}
	}

	private void findSelection(ICConfigurationDescription configuration, String languageId, CCombo combo) throws CoreException {
//		if (languageId == null) {
//			TableItem[] items = fTable.getItems();
//			for (int i = 0; i < items.length; i++) {
//				LanguageTableData data = (LanguageTableData) items[i].getData();
//				if (configuration == null && data.configuration == null) {
//					languageId = data.languageId;
//					break;
//				} else if (configuration != null && data.configuration != null) {
//					languageId = data.languageId;
//					break;
//				}
//			}
//		}
		
		if (languageId == null) {
			// No mapping was defined so we'll choose the default.
			combo.select(0);
			return;
		}
		
		LanguageManager manager = LanguageManager.getInstance();
		ILanguage language = manager.getLanguage(languageId);
		String name = language.getName();
		
		for (int i = 1; i < combo.getItemCount(); i++) {
			if (name.equals(combo.getItem(i))) {
				combo.select(i);
				return;
			}
		}
		
		// Couldn't find the mapping so we'll choose the default.
		combo.select(0);
	}

	@Override
	public boolean performOk() {
		try {
			if (!fHasChanges) {
				return true;
			}
			
			IFile file = getFile();
			IProject project = file.getProject();
			LanguageManager manager = LanguageManager.getInstance();
			ProjectLanguageConfiguration config = manager.getLanguageConfiguration(project);
			
			Map<String, String> mappings = new TreeMap<String, String>();
			TableItem[] items = fTable.getItems();
			for (int i = 0; i < items.length; i++) {
				TableItem item = items[i];
				LanguageTableData data = (LanguageTableData) item.getData();
				if (data.languageId == null) {
					continue;
				}
				String configurationId;
				if (data.configuration == null) {
					configurationId = ALL_CONFIGURATIONS;
				} else {
					configurationId = data.configuration.getId();
				}
				mappings.put(configurationId, data.languageId);
			}
			config.setFileMappings(file, mappings);
			manager.storeLanguageMappingConfiguration(file);
			fHasChanges = false;
			return true;
		} catch (CoreException e) {
			CCorePlugin.log(e);
			return false;
		}
	}
	
	private IFile getFile() {
		return (IFile) getElement().getAdapter(IFile.class);
	}

	@Override
	protected void performDefaults() {
		super.performDefaults();
	}
	
	private String computeInheritedFrom(ICConfigurationDescription configuration, LanguageMapping mapping) throws CoreException {
		String inheritedFrom;
		ILanguage language;
		
		LanguageTableData data = (LanguageTableData) fTable.getItem(0).getData();
		if (configuration != null && data.languageId != null) {
			inheritedFrom = PreferencesMessages.FileLanguagesPropertyPage_inheritedFromFile;
			language = LanguageManager.getInstance().getLanguage(data.languageId);
		} else {
			language = mapping.language;
			switch (mapping.inheritedFrom) {
			case LanguageMappingResolver.DEFAULT_MAPPING:
				inheritedFrom = PreferencesMessages.FileLanguagesPropertyPage_inheritedFromSystem;
				break;
			case LanguageMappingResolver.PROJECT_MAPPING:
				inheritedFrom = PreferencesMessages.FileLanguagesPropertyPage_inheritedFromProject;
				break;
			case LanguageMappingResolver.WORKSPACE_MAPPING:
				inheritedFrom = PreferencesMessages.FileLanguagesPropertyPage_inheritedFromWorkspace;
				break;
			default:
				throw new CoreException(Util.createStatus(new IllegalArgumentException()));
			}
		}
		return Messages.format(inheritedFrom, language.getName());
	}
	
	private LanguageMapping computeInheritedMapping(IProject project, IFile file, ICConfigurationDescription configuration) throws CoreException {
		LanguageMapping mappings[] = LanguageMappingResolver.computeLanguage(project, file.getProjectRelativePath().toPortableString(), configuration, fContentType.getId(), true);
		LanguageMapping inheritedMapping = mappings[0];
		
		// Skip over the file mapping because we want to know what mapping the file
		// mapping overrides.
		if (inheritedMapping.inheritedFrom == LanguageMappingResolver.FILE_MAPPING ) {
			inheritedMapping = mappings[1];
		}
		
		return inheritedMapping;
	}
	
	private String[][] getLanguages(IProject project, IFile file, ICConfigurationDescription configuration) throws CoreException {
		String[][] descriptions = new String[2][fLanguages.length + 1];
		
		LanguageMapping inheritedMapping = computeInheritedMapping(project, file, configuration);
		
		int index = 0;
		descriptions[LANGUAGE_ID][index] = null;
		descriptions[LANGUAGE_NAME][index] = computeInheritedFrom(configuration, inheritedMapping);

		index++;
		for (int i = 0; i < fLanguages.length; i++) {
			descriptions[LANGUAGE_ID][index] = fLanguages[i].getId();
			descriptions[LANGUAGE_NAME][index] = fLanguages[i].getName();
			index++;
		}
		return descriptions;
	}
	
	private static class LanguageTableData {
		ICConfigurationDescription configuration;
		String languageId;
		
		LanguageTableData(ICConfigurationDescription configuration, String languageId) {
			this.configuration = configuration;
			this.languageId = languageId;
		}
	}
}

