/*******************************************************************************
 * Copyright (c) 2004, 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 Rational Software - Initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.managedbuilder.ui.properties;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Vector;

import org.eclipse.cdt.managedbuilder.core.BuildException;
import org.eclipse.cdt.managedbuilder.core.IOption;
import org.eclipse.cdt.managedbuilder.core.IResourceInfo;
import org.eclipse.cdt.managedbuilder.core.ITool;
import org.eclipse.cdt.managedbuilder.internal.core.MultiResourceInfo;
import org.eclipse.cdt.ui.newui.AbstractCPropertyTab;
import org.eclipse.cdt.ui.newui.MultiLineTextFieldEditor;
import org.eclipse.cdt.ui.newui.UIMessages;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.preference.FieldEditor;
import org.eclipse.jface.preference.StringFieldEditor;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;

public class BuildToolSettingUI extends AbstractToolSettingUI {
	
	//  Label class for a preference page.
	class LabelFieldEditor extends FieldEditor {
		private String fTitle;
		private Label fTitleLabel;

		public LabelFieldEditor( Composite parent, String title ) {
			fTitle = title;
			this.createControl( parent );
		}

		protected void adjustForNumColumns( int numColumns ) {
			((GridData)fTitleLabel.getLayoutData()).horizontalSpan = 2;
		}

		protected void doFillIntoGrid( Composite parent, int numColumns ) {
			fTitleLabel = new Label( parent, SWT.WRAP );
			fTitleLabel.setText( fTitle );
			GridData gd = new GridData();
			gd.verticalAlignment = SWT.TOP;
			gd.grabExcessHorizontalSpace = false;
			gd.horizontalSpan = 2;
			fTitleLabel.setLayoutData( gd );
		}

		public int getNumberOfControls() {	return 1; }
		/**
		 * The label field editor is only used to present a text label on a preference page.
		 */
		protected void doLoad() {}
		protected void doLoadDefault() {}
		protected void doStore() {}
	}

	// Data members
	
	// all build options field editor label
	private static final String ALL_OPTIONS = UIMessages.getString("BuildToolSettingsPage.alloptions"); //$NON-NLS-1$
	// Field editor label for tool command
	private static final String COMMAND = "BuildToolSettingsPage.tool.command"; //$NON-NLS-1$
	// Advanced settings label
	private static final String ADVANCED_GROUP = "BuildToolSettingsPage.tool.advancedSettings"; //$NON-NLS-1$
	// Field editor label for tool command line pattern
	private static final String COMMAND_LINE_PATTERN = "BuildToolSettingsPage.tool.commandLinePattern"; //$NON-NLS-1$

	private static final String DEFAULT_SEPERATOR = ";"; //$NON-NLS-1$
	// Whitespace character
	private static final String WHITESPACE = " "; //$NON-NLS-1$
	// Empty String
	//private static final String EMPTY_STRING = ""; //$NON-NLS-1$
	// field editor that displays all the build options for a particular tool
	private MultiLineTextFieldEditor allOptionFieldEditor;
	//tool command field
	private StringFieldEditor commandStringField;
	//tool command-line-pattern field
	private StringFieldEditor commandLinePatternField;
	// A list of safe options to put unrecognized values in
	private Vector<String> defaultOptionNames;
	// Map that holds all string options and its values
	private HashMap<String, String> stringOptionsMap;

	private ITool fTool;
	// Map that holds all user object options and its values
	private HashMap<IOption, String> userObjsMap;
	
	public BuildToolSettingUI(AbstractCBuildPropertyTab page,
			IResourceInfo info, ITool _tool) {
		// Cache the configuration and tool this page is for
		//TODO: 
		super(info);
		this.fTool = _tool;
		buildPropPage = page;
		stringOptionsMap = new HashMap<String, String>();
		userObjsMap = new HashMap<IOption, String>();
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.jface.preference.IPreferencePage#computeSize()
	 */
	public Point computeSize() {
		return super.computeSize();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.jface.preference.FieldEditorPreferencePage#createFieldEditors()
	 */
	protected void createFieldEditors() {
		
		// Load up the preference store
		super.createFieldEditors();
		// Add a string editor to edit the tool command
		Composite parent = getFieldEditorParent();
		FontMetrics fm = AbstractCPropertyTab.getFontMetrics(parent);
		commandStringField = new StringFieldEditor(fTool.getId(),
				UIMessages.getString(COMMAND),
				parent);
		commandStringField.setEmptyStringAllowed(false);
		GridData gd = ((GridData)commandStringField.getTextControl(parent).getLayoutData());
		gd.grabExcessHorizontalSpace = true;
		gd.minimumWidth = Dialog.convertWidthInCharsToPixels(fm, 3);
		addField(commandStringField);
		// Add a field editor that displays overall build options
		Composite par = getFieldEditorParent();
		allOptionFieldEditor = new MultiLineTextFieldEditor(ToolSettingsPrefStore.ALL_OPTIONS_ID,
				ALL_OPTIONS, par);
		allOptionFieldEditor.getTextControl(par).setEditable(false);
//		gd = ((GridData)allOptionFieldEditor.getTextControl().getLayoutData());
		gd.grabExcessHorizontalSpace = true;
		gd.minimumWidth = Dialog.convertWidthInCharsToPixels(fm, 20);
		addField(allOptionFieldEditor);
		
		addField( createLabelEditor( getFieldEditorParent(), WHITESPACE ) );
		addField( createLabelEditor( getFieldEditorParent(), UIMessages.getString(ADVANCED_GROUP) ) );
		
		// Add a string editor to edit the tool command line pattern
		parent = getFieldEditorParent();
		commandLinePatternField = new StringFieldEditor(fTool.getId() + ToolSettingsPrefStore.COMMAND_LINE_SUFFIX,
				UIMessages.getString(COMMAND_LINE_PATTERN),
				parent);
		gd = ((GridData)commandLinePatternField.getTextControl(parent).getLayoutData());
		gd.grabExcessHorizontalSpace = true;
		gd.widthHint =  Dialog.convertWidthInCharsToPixels(fm,30);
		gd.minimumWidth = Dialog.convertWidthInCharsToPixels(fm, 20);
		addField(commandLinePatternField);
	}		

	protected FieldEditor createLabelEditor( Composite parent, String title ) {
		return new LabelFieldEditor( parent, title );
	}

	/**
	 * @return
	 */
	private Vector<String> getDefaultOptionNames() {
		if (defaultOptionNames == null) {
			defaultOptionNames = new Vector<String>();
			defaultOptionNames.add("Other flags"); //$NON-NLS-1$
			defaultOptionNames.add("Linker flags"); //$NON-NLS-1$
			defaultOptionNames.add("Archiver flags"); //$NON-NLS-1$
			defaultOptionNames.add("Assembler flags"); //$NON-NLS-1$
		}
		return defaultOptionNames;
	}

	/**
	 * The raw option string can contain path information that has spaces 
	 * in them. For example, 
	 * <p> <pre>
	 * -I"path\with a\couple of spaces" -O2 -g -fPIC
	 * </pre><p>
	 * would yeild
	 * <p><pre>-I"path\with | a\couple | of | spaces" | -O2 | -g | -fPIC</pre>
	 * <p>
	 * As you can see, simply splitting at the whitespaces will yeild a result 
	 * containing garbage, so the logic of this method must consider whether a 
	 * token contains the &quot; character. If so, then add it and all of the 
	 * subsequent tokens until the enclosing &quot; is found. 
	 *  
	 * @param rawOptionString
	 * @return Vector containing all options
	 */
	private Vector<String> getOptionVector(String rawOptionString){
		Vector<String> tokens = new Vector<String>(Arrays.asList(rawOptionString.split("\\s")));	//$NON-NLS-1$
		Vector<String> output = new Vector<String>(tokens.size());

		Iterator<String> iter = tokens.iterator();
		while(iter.hasNext()){
			String token = (String)iter.next();
			int firstIndex = token.indexOf("\"");	//$NON-NLS-1$
			int lastIndex = token.lastIndexOf("\"");	//$NON-NLS-1$
			if (firstIndex != -1 && firstIndex == lastIndex) {
				// Keep looking
				while (iter.hasNext()) {
					String nextToken = (String) iter.next();
					token += WHITESPACE + nextToken;
					if (nextToken.indexOf("\"") != -1) break;	//$NON-NLS-1$
				}
			}
			output.add(token);
		}
		
		return output;
	}
	
	/**
	 * Look for ${VALUE} in the command string
	 */
	//private String evaluateCommand( String command, String values ) {
	//	return (((Tool)clonedTool).evaluateCommand(command, values));
	//}

	/**
	 * Answers <code>true</code> if the receiver manages settings for the
	 * argument
	 * 
	 * @param tool
	 * @return
	 */
	public boolean isFor(Object tool, Object unused) {
		if (tool != null && tool instanceof ITool && unused == null) {
			return tool.equals(fTool);
		}
		return false;
	}
	
	/**
	 * This method parses the string that is entered in the all build option
	 * field editor and stores the options to the corresponding option fields.
	 */
	public void parseAllOptions() {
		// Get the all build options string from all options field
		String alloptions = getToolSettingsPrefStore().getString(ToolSettingsPrefStore.ALL_OPTIONS_ID);
		// list that holds the options for the option type other than
		// boolean,string and enumerated
		List<String> optionsList = new ArrayList<String>();
		// additional options buffer
		StringBuffer addnOptions = new StringBuffer();
		// split all build options string
		Vector<String> optionsArr = getOptionVector(alloptions);
		for (String optionValue : optionsArr) {
			boolean optionValueExist = false;
			// get the options for this tool
			IOption[] options = fTool.getOptions();
			for (IOption opt : options) {
				//String name = opt.getId();
				// check whether the option value is "STRING" type
				for (String s : stringOptionsMap.values()) {
					if (s.indexOf(optionValue) != -1)
						optionValueExist = true;
				}
				// check whether the option value is "OBJECTS" type
				for (String s : userObjsMap.values()) {
					if (s.indexOf(optionValue) != -1)
						optionValueExist = true;
				}
				// if the value does not exist in string option or user objects
				// option
				if (!optionValueExist) {
					try {
						// check whether the option value is already exist
						// and also change the preference store based on
						// the option value
						switch (opt.getValueType()) {
							case IOption.BOOLEAN :
								String boolCommand;
								boolCommand = opt.getCommand();
								if (boolCommand != null && boolCommand.equals(optionValue)) {
											setOption(opt, true);
									optionValueExist = true;
								}
								boolCommand = opt.getCommandFalse();
								if (boolCommand != null && boolCommand.equals(optionValue)) {
											setOption(opt, false);
									optionValueExist = true;
								}
								break;
							case IOption.ENUMERATED :
								String enumeration = ""; //$NON-NLS-1$
								String[] enumValues = opt.getApplicableValues();
								for (int i = 0; i < enumValues.length; i++) {
									if (opt.getEnumCommand(enumValues[i]).equals(
											optionValue)) {
										enumeration = enumValues[i];
										optionValueExist = true;
									}
								}
								if (!enumeration.equals("")) //$NON-NLS-1$
											setOption(opt, enumeration);
								break;
							case IOption.STRING_LIST :
							case IOption.INCLUDE_PATH :
							case IOption.PREPROCESSOR_SYMBOLS :
							case IOption.LIBRARIES :
							case IOption.INCLUDE_FILES:
							case IOption.LIBRARY_PATHS:
							case IOption.LIBRARY_FILES:
							case IOption.MACRO_FILES:
							case IOption.UNDEF_INCLUDE_PATH:
							case IOption.UNDEF_PREPROCESSOR_SYMBOLS:
							case IOption.UNDEF_INCLUDE_FILES:
							case IOption.UNDEF_LIBRARY_PATHS:
							case IOption.UNDEF_LIBRARY_FILES:
							case IOption.UNDEF_MACRO_FILES:
								if (opt.getCommand() != null
										&& optionValue.startsWith(opt
												.getCommand())) {
									optionsList.add(optionValue);
									optionValueExist = true;
								}
								break;
							default :
								break;
						}
					} catch (BuildException e) {}
				}
			}
			// If the parsed string does not match with any previous option
			// values then consider this option as a additional build option
			if (!optionValueExist) {
				addnOptions.append(optionValue + ITool.WHITE_SPACE);
			}
		}
		// check whether some of the "STRING" option value or "OBJECTS" type
		// option value removed
		// by the user from all build option field
		Set<String> set = stringOptionsMap.keySet();
		for (int i = 0; i < set.size(); i++) {
			Iterator<String> iterator = set.iterator();
			while (iterator.hasNext()) {
				Object key = iterator.next();
				String val = (String) stringOptionsMap.get(key);
				if (alloptions.indexOf(val) == -1) {
					StringBuffer buf = new StringBuffer();
					String[] vals = val.split(WHITESPACE);
					for (int t = 0; t < vals.length; t++) {
						if (alloptions.indexOf(vals[t]) != -1)
							buf.append(vals[t] + ITool.WHITE_SPACE);
					}
					setOption(((IOption) key),
							buf.toString().trim());
				}
			}
		}
		// "OBJECTS" type
		Set<IOption> objSet = userObjsMap.keySet();
		for (int s = 0; s < objSet.size(); s++) {
			for (IOption op : objSet) {
				String val = (String) userObjsMap.get(op);
				ArrayList<String> list = new ArrayList<String>();
				for (String v : parseString(val)) {
					if (alloptions.indexOf(v) != -1)
						list.add(v);
				}
				String listArr[] = new String[list.size()];
				list.toArray(listArr);
				setOption(op, listArr);
			}
		}
		// Now update the preference store with parsed options
		// Get the options for this tool
		IOption[] options = fTool.getOptions();
		for (int k = 0; k < options.length; ++k) {
			IOption opt = options[k];
			//String name = opt.getId();
			//String listStr = ""; //$NON-NLS-1$
			//String[] listVal = null;
			try {
				switch (opt.getValueType()) {
					case IOption.BOOLEAN :
						ArrayList<String> optsList = new ArrayList<String>(optionsArr);
						if (opt.getCommand() != null 
								&& opt.getCommand().length() > 0  
								&& !optsList.contains(opt.getCommand()))
							setOption(opt, false);
						if (opt.getCommandFalse() != null 
								&& opt.getCommandFalse().length() > 0  
								&& !optsList.contains(opt.getCommandFalse()))
							setOption(opt, true);
						break;
					case IOption.STRING :
						// TODO create a lst of valid default string options for the tool
						if (getDefaultOptionNames().contains(opt.getName())) {
							String newOptions = opt.getStringValue();
							if (addnOptions.length() > 0) {
								newOptions = newOptions + ITool.WHITE_SPACE
										+ addnOptions.toString().trim();
							}
							setOption(opt, newOptions);
						}
						break;
					case IOption.STRING_LIST :
					case IOption.INCLUDE_PATH :
					case IOption.PREPROCESSOR_SYMBOLS :
					case IOption.LIBRARIES :
						ArrayList<String> newList = new ArrayList<String>();
						for (String s : optionsList) {
							if (opt.getCommand() != null
									&& s.startsWith(opt.getCommand())) {
								newList.add(s.substring(opt.getCommand().length()));
							}
						}
						String[] strlist = new String[newList.size()];
						newList.toArray(strlist);
						newList.clear();
						setOption(opt, strlist);
						break;
					default :
						break;
				}
			} catch (BuildException e) {}
		}
	}

	public static String[] parseString(String stringList) {
		if (stringList == null || stringList.length() == 0) {
			return new String[0];
		} else {
			return stringList.split(DEFAULT_SEPERATOR);
		}
	}

	protected void setOption(IOption option, boolean value){
		try{
			fInfo.setOption(fTool,option,value);
		} catch (BuildException e){}
	}
	
	protected void setOption(IOption option, String value){
		try{ 
			fInfo.setOption(fTool,option,value);
		} catch (BuildException e){}
	}

	protected void setOption(IOption option, String value[]){
		try{
			fInfo.setOption(fTool,option,value);
		} catch (BuildException e){	}
	}

	/* (non-Javadoc)
	 * @see org.eclipse.jface.preference.FieldEditorPreferencePage#performOk()
	 */
	public boolean performOk() {
		return super.performOk();
	}
	
	/**
	 * Update the field editor that displays all the build options
	 */
	public void updateFields() {
		allOptionFieldEditor.load();
	}
	
	public void setValues(){
		commandStringField.load();
		commandLinePatternField.load();
		updateFields();
	}
	
	public void propertyChange(PropertyChangeEvent event) {
		// allow superclass to handle as well
		super.propertyChange(event);
		
		if (fInfo instanceof MultiResourceInfo) {
			MultiResourceInfo mri = (MultiResourceInfo)fInfo;
			if(event.getSource() == commandStringField){
				mri.setToolsCommand(fTool, commandStringField.getStringValue());
				updateFields();
			}
			else if(event.getSource() == commandLinePatternField){
				mri.setCommandLinePattern(fTool, commandLinePatternField.getStringValue());
			}
		} else {
			if(event.getSource() == commandStringField){
				fTool.setToolCommand(commandStringField.getStringValue());
				updateFields();
			}
			else if(event.getSource() == commandLinePatternField){
				fTool.setCommandLinePattern(commandLinePatternField.getStringValue());
			}
		}
	}
}
