/*
* Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). 
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:
*
*/
package com.nokia.s60tools.creator.editors;


import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PushbackInputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.util.Iterator;
import java.util.Set;
import java.util.Vector;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.wizard.ProgressMonitorPart;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.List;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IPathEditorInput;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.SaveAsDialog;
import org.eclipse.ui.editors.text.TextEditor;
import org.eclipse.ui.ide.FileStoreEditorInput;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.part.MultiPageEditorPart;

import com.nokia.s60tools.creator.CreatorActivator;
import com.nokia.s60tools.creator.CreatorHelpContextIDs;
import com.nokia.s60tools.creator.components.AbstractComponent;
import com.nokia.s60tools.creator.components.Components;
import com.nokia.s60tools.creator.components.contact.ContactSetVariables;
import com.nokia.s60tools.creator.core.CreatorEditorSettings;
import com.nokia.s60tools.creator.dialogs.AbstractDialog;
import com.nokia.s60tools.creator.dialogs.DialogLauncher;
import com.nokia.s60tools.creator.util.CreatorEditorConsole;
import com.nokia.s60tools.creator.xml.CreatorScriptNotValidException;
import com.nokia.s60tools.creator.xml.CreatorXML;
import com.nokia.s60tools.creator.xml.CreatorXMLParser;
import com.nokia.s60tools.hticonnection.services.HTIServiceFactory;
import com.nokia.s60tools.util.resource.FileUtils;

/**
 * Creator script editor - Editor area. Stores all components and handles adding, removing and editing a Script.
 */
public class CreatorScriptEditor extends MultiPageEditorPart implements IResourceChangeListener, IComponentProvider{

	private static final String ADD_TXT = " Add ";

	
	public static final int COMPONENT_LIST_WIDTH = 500;

	public static final int EDITOR_DEFAULT_WIDTH = 600;

	/**
	 * Fixed Width and height parameters to set UI components 
	 * precisely 
	 */
	private static final int COMPONENT_LIST_ITEMS_HEIGHT_HINT = 8;

	/** The text editor used in page 0. */
	private TextEditor editor;
	
	/**
	 * All components
	 */
	private Components components;
	

	
	//Editor window widgets

	private List componentsList;
	
	private Combo componentCombo;

	private Button removeBtn;

	private Button editBtn;

	private boolean isDirty;

	private Color white;

	private boolean isEditLaunched = false;

	private Button addComponentButton;

	private Button addContactSetButton;
	
	private Button runInDeviceButton = null;	
	
	
	
	
	
	/**
	 * Creates a multi-page editor example.
	 */
	public CreatorScriptEditor() {
		super();
		ResourcesPlugin.getWorkspace().addResourceChangeListener(this);
	}

	private void setContextSensitiveHelpIDs(){
		 PlatformUI.getWorkbench().getHelpSystem().setHelp(componentCombo, 
		    		CreatorHelpContextIDs.CREATOR_HELP_ADD_COMPONENT);
		 PlatformUI.getWorkbench().getHelpSystem().setHelp(addComponentButton, 
		    		CreatorHelpContextIDs.CREATOR_HELP_ADD_COMPONENT);		
		 
		 PlatformUI.getWorkbench().getHelpSystem().setHelp(editBtn, 
		    		CreatorHelpContextIDs.CREATOR_HELP_MODIFY_COMPONENT);		
		 PlatformUI.getWorkbench().getHelpSystem().setHelp(removeBtn, 
		    		CreatorHelpContextIDs.CREATOR_HELP_MODIFY_COMPONENT);		
		 PlatformUI.getWorkbench().getHelpSystem().setHelp(componentsList, 
		    		CreatorHelpContextIDs.CREATOR_HELP_MODIFY_COMPONENT);		

		 if(runInDeviceButton != null){
			 PlatformUI.getWorkbench().getHelpSystem().setHelp(runInDeviceButton, 
			    		CreatorHelpContextIDs.RUN_IN_DEVICE_PAGE);					 
		 }
		 
	}

	/**
	 * Create Creator Editor page.
	 * 
	 * Creating all Widgets and setting data from this.xml to them.
	 * Creating listeners for selected Widgets. 
	 * 
	 */
	private void createCreatorScriptEditorPage() {


		final Composite composite = new Composite(getContainer(), SWT.SIMPLE);
		final Shell shell = composite.getShell();

		GridLayout gridLayout = new GridLayout();//2, false
		composite.setLayout(gridLayout);

		GridData gridData = new GridData();
 		gridData.horizontalAlignment = GridData.FILL;
 		gridData.grabExcessHorizontalSpace = true;
		composite.setLayoutData(gridData);
		
		RGB rgbWhite = new RGB(255, 255, 255);
		white = new Color(null, rgbWhite);
		composite.setBackground(white);

		//Create part of UI where is components to add, contact set adding button and run in device button
		createButtonsPart(composite, shell);		
		
		
		//COMPONENTS
		Group componentsGroup = new Group(composite, SWT.SHADOW_NONE);
		componentsGroup.setText("Components added:");
		GridLayout componentsGrid = new GridLayout(2, false);
		componentsGroup.setBackground(white);
		GridData componentsGridData = new GridData(EDITOR_DEFAULT_WIDTH, SWT.DEFAULT/*GridData.HORIZONTAL_ALIGN_FILL*/);
		componentsGridData.horizontalAlignment = GridData.FILL;
		componentsGridData.grabExcessHorizontalSpace = true;	
		componentsGridData.verticalAlignment = GridData.FILL;
		componentsGridData.grabExcessVerticalSpace = true;

		componentsGroup.setLayout(componentsGrid);
		componentsGroup.setLayoutData(componentsGridData);
		
		
		final int listBoxStyleBits = SWT.MULTI | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL;
		componentsList = new List(componentsGroup,listBoxStyleBits);
		//If font not found or something else unexpected occur, don't fail whole program
		try {
			componentsList.setFont(getUnicodeFont());
		} catch (Exception e) {
			e.printStackTrace();
			CreatorEditorConsole.getInstance().println("Could not set list font to Unicode, reason: " +e.getMessage(), CreatorEditorConsole.MSG_ERROR);
		}
		GridData listData = new GridData(COMPONENT_LIST_WIDTH, SWT.DEFAULT);
		listData.horizontalAlignment = GridData.FILL;
		listData.grabExcessHorizontalSpace = true;
		listData.verticalAlignment = GridData.FILL;
		listData.grabExcessVerticalSpace = true;

		int listHeight = componentsList.getItemHeight() * COMPONENT_LIST_ITEMS_HEIGHT_HINT;
		Rectangle trim = componentsList.computeTrim(0, 0, 0, listHeight);
		listData.heightHint = trim.height;		
		componentsList.setLayoutData(listData);
		
		componentsList.addSelectionListener(getEnableButtonsListener());	
		
		
		//
		//Setting components data to selection box
		//
		
		
		redrawComponentList();

		Composite btnComp = new Composite(componentsGroup, SWT.SIMPLE);
		btnComp.setLayout(new GridLayout(1, false));
		GridData btnGrid = new GridData();
		btnGrid.verticalAlignment = SWT.BEGINNING;
		btnGrid.horizontalAlignment = SWT.BEGINNING;
		btnComp.setLayoutData(btnGrid);//GridData.FILL_BOTH	
		btnComp.setBackground(white);
		
		removeBtn = new Button(btnComp,SWT.PUSH);
		removeBtn.setText("Remove");
		removeBtn.setEnabled(false);
		
		editBtn = new Button(btnComp,SWT.PUSH);
		editBtn.setText("   Edit   ");
		editBtn.setEnabled(false);//By Default edit is not available, but when a component is selected, its enabled 
		
		//Open Edit Dialog when Edit button has been pushed
		editBtn.addSelectionListener(getEditButtonListener(this, shell));			
		
		//Adding action to remove Button
		removeBtn.addSelectionListener(getRemoveComponentListener());			
		
		
		
		int pageIndex = addPage(composite);
		composite.pack();
		setPageText(pageIndex, "Creator Script Editor");
	}

	private void createButtonsPart(final Composite composite, final Shell shell) {
		//ADD COMPONENT GROUP				
		Group addComponentGroup = new Group(composite, SWT.SHADOW_NONE);
		addComponentGroup.setText("Add a component to script:");
		GridLayout componentGrid = new GridLayout(7, false);
		addComponentGroup.setBackground(white);
		GridData componentGridData = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
		componentGridData.horizontalAlignment = GridData.FILL;
		componentGridData.grabExcessHorizontalSpace = true;		
		addComponentGroup.setLayout(componentGrid);
		addComponentGroup.setLayoutData(componentGridData);

		//SELECT COMPONENT LABEL
		Label compoenentLabel = new Label(addComponentGroup, SWT.HORIZONTAL);
		compoenentLabel.setText("Select component to add:");
		compoenentLabel.setLayoutData(new GridData());
		compoenentLabel.setBackground(white);

		//Components to select
		componentCombo = new Combo(addComponentGroup, SWT.READ_ONLY | SWT.DROP_DOWN);
		componentCombo.setVisibleItemCount(CreatorEditorSettings.getInstance().getComponents().length);
		componentCombo.setItems(CreatorEditorSettings.getInstance().getComponents());
		componentCombo.setLayoutData(new GridData());
		
		
		addComponentButton = new Button(addComponentGroup, SWT.PUSH);
		addComponentButton.setText(ADD_TXT);		

		//add button listener
		addComponentButton.addSelectionListener(getAddComponentButtonListener(shell, this));		

		
		//Create own button for contact-set:s
		Label nullText = new Label(addComponentGroup, SWT.HORIZONTAL);
		nullText.setBackground(white);
		nullText.setText("       ");//Just for making some space between buttons.
		addContactSetButton = new Button(addComponentGroup, SWT.PUSH);
		addContactSetButton.setText(ContactSetVariables.ADD_CONTACT_SET_TXT);		

		//add button listener
		addContactSetButton.addSelectionListener(getAddNewContactSetComponentListener(shell, this));

		
		//create button for run in device
		Label nullText2 = new Label(addComponentGroup, SWT.HORIZONTAL);
		nullText2.setBackground(white);
		nullText2.setText("       ");//Just for making some space between buttons.
		//Checking if HTI is supported at all, if not, not creating button at all.
		if(isHTIAvailable()){
			runInDeviceButton = new Button(addComponentGroup, SWT.PUSH);
			runInDeviceButton.setText("Run in device via HTI");
			runInDeviceButton.addSelectionListener(new RunInDeviceSelectionListener(components.getFileName(), components.getFilePath()));
		}
	}
	
	
	/**
	 * Checking if HTI connection package is available.
	 * @return <code>true</code> if HTI is available, <code>false</code> otherwise.
	 */
	private boolean isHTIAvailable() {

		try {
			//If HTI is not available, Class HTIServiceFactory does not found and NoClassDefFoundError is thrown.
			//There are different deliverables available and with all, HTI is not bundled with Carbide. 
			HTIServiceFactory
				.createFTPService(CreatorEditorConsole.getInstance());
		} catch (NoClassDefFoundError e) {
			CreatorEditorConsole.getInstance().println("HTI Connection is not available, Run in Device via HTI functionality is disabled.");
			return false;
		}			
		return true;
	}

	/* (non-Javadoc)
	 * @see com.nokia.s60tools.creator.editors.IComponentProvider#getAddNewContactSetComponentListener(org.eclipse.swt.widgets.Shell, com.nokia.s60tools.creator.editors.IComponentProvider)
	 */
	public SelectionListener getAddNewContactSetComponentListener(final Shell shell,
			final IComponentProvider provider) {
		
		return new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {

				//tell listeners that a component was added to list	
				IAddComponentListener listener = null;
				if(event.widget != null){
					Object o = event.widget.getData();
					if(o instanceof IAddComponentListener){
						//if listener was provided, passing it through to compont adding
						listener = (IAddComponentListener)o;
					}
				}
				
				isEditLaunched = false;

				// Get a dialog by selection type
				final AbstractDialog aComponentAddDialog = DialogLauncher
						.getDialog(CreatorEditorSettings.TYPE_CONTACT_SET, shell, provider);
				// Open dialog
				openAddNewComponentDialog(aComponentAddDialog, listener);				
			}
		};
	}

	/**
	 * Get listener for Add button
	 * @param shell
	 * @return Listener
	 */
	private SelectionAdapter getAddComponentButtonListener(final Shell shell, final IComponentProvider provider) {
		return new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				
				isEditLaunched = false;

				// Get a dialog by selection type
				final AbstractDialog aComponentAddDialog = DialogLauncher
						.getDialog(componentCombo.getText(), shell, provider);
				openAddNewComponentDialog(aComponentAddDialog, null);
			}

		};
	}
	/**
	 * Open a dialog
	 * @param aComponentAddDialog
	 */
	private void openAddNewComponentDialog(
			final AbstractDialog aComponentAddDialog, IAddComponentListener listener) {
		// Open dialog
		aComponentAddDialog.open();
		if (aComponentAddDialog.getReturnCode() == IDialogConstants.OK_ID) {
			// After dialog closed, get component(s) created
			AbstractComponent comp = aComponentAddDialog.getComponent();
			addComponent(comp, listener); 

		}// else, cancel is pushed
	}

	
	/**
	 * Get Listener for enabling buttons or not
	 * @return Listener
	 */
	private SelectionAdapter getEnableButtonsListener() {
		return new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
	
				boolean buttonsEnabled = componentsList.getSelectionIndex() != -1 ? true : false ;
				removeBtn.setEnabled(buttonsEnabled);

				//Edit only enabled when one row is selected
				if(buttonsEnabled && componentsList.getSelection().length == 1){
					editBtn.setEnabled(buttonsEnabled);
				}else{
					editBtn.setEnabled(false);
				}

			}
		};
	}


	/**
	 * Get Listener for Edit button
	 * @param shell
	 * @return Listener
	 */
	private SelectionAdapter getEditButtonListener(final IComponentProvider provider, final Shell shell) {
		return new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				
				isEditLaunched = true;

				//Get a dialog by selection type
				int compIndex = componentsList.getSelectionIndex();
				AbstractComponent comp = provider.getEditable();

				final AbstractDialog aComponentEditDialog = DialogLauncher.getDialog(provider, shell) ;//new AddContactDialog(shell);
				//Open dialog
				aComponentEditDialog.open();
				if (aComponentEditDialog.getReturnCode() == IDialogConstants.OK_ID) {
					//After dialog closed, get component(s) created
					//AbstractComponent editedComp= aComponentEditDialog.getComponent();
					AbstractComponent editedComp= aComponentEditDialog.getComponent();
					
					//Add component to list and check if its valid
					if(editedComp.isValid()){
						getComponents().updateComponent(comp, editedComp);
						componentsList.setItem(compIndex, editedComp.toString());
						setDirty(true);
					}
					else{
						//set error message
						showErrorDialog("Component is not valid", "Component edited is not valid", "Please edit valid values to component.");
					}
				}//else, cancel is pushed
			}
		};
	}


	/**
	 * Handle component(s) removal
	 * 
	 * @return Listener
	 */
	private SelectionAdapter getRemoveComponentListener() {
		return new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {

				int[] indexes = componentsList.getSelectionIndices();
				Vector<AbstractComponent> componentsToBeRemoved = new Vector<AbstractComponent>(indexes.length);
				
				//First just collecting components to be removed, because when Contact-set is removed a 
				//components referenced to that will be changed, and seeking components left requires
				//component list update to be found after that
				for (int i = 0; i < indexes.length; i++) {
					AbstractComponent comp = getComponentBySelection(indexes[i]);
					componentsToBeRemoved.add(comp);
				}				

				//actually removing components and references to component to be removed
				for (Iterator<AbstractComponent> iterator = componentsToBeRemoved.iterator(); iterator.hasNext();) {
					AbstractComponent comp = (AbstractComponent) iterator.next();
					//remove references to that component
					getComponents().removeReferencesToComponent(comp);		
					//remove component
					getComponents().remove(comp);					
				}
				
				componentsList.remove(indexes);
				
				//Update component list, because many components may changed				
				redrawComponentList();				
				setDirty(true);
			}

		};
	}
	
	private void addComponentToList(AbstractComponent aComponent, List componentsList) {
		componentsList.add(  aComponent.toString());
	}
	
	private void redrawComponentList() {
		componentsList.removeAll();
		//Get all component types (Contacts, Connection methods, messages...)
		Set<String> allComponentTypes = getComponents().getComponentTypes();
		//Looping through all component types one by one
		for (Iterator<String> allCompKeysIt = allComponentTypes.iterator(); allCompKeysIt.hasNext();) {			
			String compType = (String) allCompKeysIt.next();
			Vector<AbstractComponent> allComponentsByType = getComponents().getComponents(compType);
	
			//Looping through all components in one component type
			for (Iterator<AbstractComponent> iterator = allComponentsByType.iterator(); iterator.hasNext();) {
				AbstractComponent aComponent = (AbstractComponent) iterator.next();			
				addComponentToList(aComponent, componentsList);
			}			
		}
	}

	
	/**
	 * Creates the pages of the multi-page editor.
	 */
	protected void createPages() {
		
		try {
			createCreatorScriptEditorPage();
			setContextSensitiveHelpIDs();			
		} catch (Exception e) {
			CreatorEditorConsole.getInstance().println("Unexpected error occurs: " + e, CreatorEditorConsole.MSG_ERROR);			
			e.printStackTrace();			
		}

	}
	/**
	 * The <code>MultiPageEditorPart</code> implementation of this 
	 * <code>IWorkbenchPart</code> method disposes all nested editors.
	 * Subclasses may extend.
	 */
	public void dispose() {
		ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
		super.dispose();
	}
	/**
	 * Saves the multi-page editor's document.
	 */
	public void doSave(IProgressMonitor monitor) {
		//getEditor(0).doSave(monitor);
				
		InputStream stream = null;
		try {
			IEditorInput editorInput = getEditorInput();
			//Type is IFileEditorInput allways when file is opened from project
			//If file opened outside project, editor type is IPathEditorInput
			if (editorInput instanceof IFileEditorInput) {
				IFile file;
				//Just making sure that stream is closed before renaming file
				try {
					stream = openUTF8ContentStream(getComponents().toXMLString());

					IFileEditorInput iFile = (IFileEditorInput) editorInput;
					file = iFile.getFile();

					if (file.exists()) {
						file.setContents(stream, true, true, monitor);
						file.setCharset(FileUtils.ENCODING_TYPE_UTF_8, monitor);	
					} else {
						file.create(stream, true, monitor);
						file.setCharset(FileUtils.ENCODING_TYPE_UTF_8, monitor);
					}			
				} finally {
					stream.close();
				}
			}
			//Else file is opened outside of Carbide project
			else if (editorInput instanceof IPathEditorInput) {
				IPathEditorInput javaInput = (IPathEditorInput) editorInput;
				IPath path = javaInput.getPath();				
				String fileLocation = path.toOSString();
				FileUtils.writeToFile(fileLocation, getComponents().toXMLString(), FileUtils.ENCODING_TYPE_UTF_8);
			}else{// if (editorInput instanceof FileStoreEditorInput) {
				FileStoreEditorInput inp = ((FileStoreEditorInput) editorInput);	
				URI uri = inp.getURI();				
				java.io.File fi = new java.io.File(uri);
				String fileLocation = fi.getAbsolutePath();
				FileUtils.writeToFile(fileLocation, getComponents().toXMLString(), FileUtils.ENCODING_TYPE_UTF_8);				
			}				
			
			setDirty(false);
			
		} catch (Exception e) {
			showErrorDialog("Errors on save", "Save was not compleate.",
					e.getMessage());
			e.printStackTrace();
		}
		
	}
	/**
	 * Saves the multi-page editor's document as another file.
	 * Also updates the text for page 0's tab, and updates this multi-page editor's input
	 * to correspond to the nested editor's.
	 */
	public void doSaveAs() {

		
		try {
			IEditorInput editorInput = getEditorInput();
			SaveAsDialog saveas = new SaveAsDialog(getContainer().getShell());
			//Setting default saveasdialog file name and location
			//If API name is changed IFileEditorInput is still the same
			//and if orginal file is not saved yet (xml.isAPINameChanged())
			//Setting default name to UI as new generated file name instead of old filename and path
			if (editorInput instanceof IFileEditorInput ) {
				IFileEditorInput iFile = (IFileEditorInput) editorInput;				
				saveas.setOriginalFile(iFile.getFile());
			} 
			//Else file is opened outside of Carbide project
			else if (editorInput instanceof IPathEditorInput) {
				IPathEditorInput javaInput = (IPathEditorInput) editorInput;
				IPath path = javaInput.getPath();				
				saveas.setOriginalName(path.lastSegment());				
			}else{// if (editorInput instanceof FileStoreEditorInput) {
				FileStoreEditorInput inp = ((FileStoreEditorInput) editorInput);
				saveas.setOriginalName(inp.getName());				
			}			

			saveas.open();
			
			if (SaveAsDialog.CANCEL == saveas.getReturnCode()) {
				return;
			}

			if (saveas.getResult() == null
					|| !saveas.getResult().toString().endsWith(".creatorxml")
					|| !saveas.getResult().getFileExtension().equals("creatorxml")) {
				showErrorDialog("Wrong file type",
						"Save as was not complete. Please correct file type.",
						"File type must be .creatorxml");
				return;
			}			
			
			String containerName = saveas.getResult().removeLastSegments(1)
			.toOSString();
			String fileName = saveas.getResult().lastSegment();
		
			IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
			IResource resource = root.findMember(new Path(containerName));
		
			//This is also check by SaveAsDialog, double check
			if (resource == null || !resource.exists()
					|| !(resource instanceof IContainer)) {
				showErrorDialog(
						"Container must exist",
						"Save as was not complete, container must exist. Please select existing container.",
						"Conteiner does not exist");
				return;
			}
			IContainer container = (IContainer) resource;
			final IFile file = container.getFile(new Path(fileName));
			//Creating temp file because given API name must be found
			InputStream stream = openUTF8ContentStream(getComponents().toXMLString());
			ProgressMonitorPart monitor = new ProgressMonitorPart(
					getContainer(), new GridLayout());
			file.create(stream, true, monitor);
			file.setCharset(FileUtils.ENCODING_TYPE_UTF_8, null);			
			stream.close();

			
		} catch (Exception e) {
			showErrorDialog("Errors on save as", "Save as was not compleate.",
					e.getMessage());
			e.printStackTrace();
		}
	}
	
	/**
	 * Open a stream from components in UTF-8 format
	 * Client must close the opened stream.
	 * @return components XML as stream
	 * @throws UnsupportedEncodingException 
	 */
	private InputStream openUTF8ContentStream(String string) throws UnsupportedEncodingException {
		return openContentStream(string, FileUtils.ENCODING_TYPE_UTF_8);
	}
	/**
	 * Open a stream from components in specified character set format
	 * Client must close the opened stream.
	 * @return components XML as stream
	 * @throws UnsupportedEncodingException 
	 */
	private InputStream openContentStream(String string, String charset) throws UnsupportedEncodingException {
		if(charset == null){
			charset = FileUtils.ENCODING_TYPE_UTF_8;
		}
		InputStream in = new ByteArrayInputStream(string.getBytes(charset));	
		return in;
	}		
	
	/* (non-Javadoc)
	 * Method declared on IEditorPart
	 */
	public void gotoMarker(IMarker marker) {
		setActivePage(0);
		IDE.gotoMarker(getEditor(0), marker);
	}
	/**
	 * Checks that the input is an instance of <code>IFileEditorInput, IPathEditorInput or FileStoreEditorInput</code>.
	 * And sets Title and content of file.
	 */
	public void init(IEditorSite site, IEditorInput editorInput)
		throws PartInitException {
		if (!(editorInput instanceof IFileEditorInput)
				&& !(editorInput instanceof IPathEditorInput)
				&& !(editorInput instanceof FileStoreEditorInput) ){
			throw new PartInitException(
					"Invalid Input: Must be IFileEditorInput or IPathEditorInput, was: "
							+ editorInput.getClass().getName());
		}
		setTitle(editorInput);
		setTitleImage(CreatorActivator
				.getImageForKey(CreatorActivator.CREATOR_SCRIPT_EDITOR_ICON));		

		parseXMLAndSetComponents(editorInput);
		super.init(site, editorInput);

	}
	
	/**
	 * When open a file, setting this.xml from file.
	 * If file was old existing file, parsing file and set it to this.xml
	 * if file was new just created file, creating new xml object and setting
	 * it to this.xml
	 * @param input
	 */
	private void parseXMLAndSetComponents(IEditorInput input) {

		String fileName = "";
		String filePath = "";
		try {

			String xmlString;
			String charSet = null;
			//Type is IFileEditorInput allways when file is opened from project
			//If file opened outside project in Carbide 1.2 editor type is IPathEditorInput
			//and in Carbide 1.3 its FileStoreEditorInput
			if (input instanceof IFileEditorInput) {
				IFileEditorInput iFile = (IFileEditorInput) input;
				IFile file = iFile.getFile();
				fileName = file.getName();
				filePath = file.getLocation().toOSString();
				InputStream in = file.getContents();
				//Found out file charset, and opening file contents by that. So also non UTF-8 files is working
				//Note! If e.g. file is edited in Windows, and charset is cp1252, then "official" (windows-1252) charset name must be in XML file
				//e.g. <?xml version="1.0" encoding="windows-1252"?>, otherwise parsing XML file will fail.
				charSet = file.getCharset();
				xmlString = getFileContentsWithCharSet(in, charSet);
				in.close();
				in = null;

			} else if(input instanceof IPathEditorInput){
				IPath path = ((IPathEditorInput) input).getPath();
				fileName = path.lastSegment();
				filePath = path.toOSString();
				java.io.File fi = path.toFile();				
				FileInputStream in = new FileInputStream(fi);
				xmlString = getFileContentsAsUTF8(in);
				in.close();
				in = null;
			}else{
				FileStoreEditorInput inp = ((FileStoreEditorInput) input);	
				URI uri = inp.getURI();				
				java.io.File fi = new java.io.File(uri);
				fileName = fi.getName();
				filePath = fi.getAbsolutePath();				
				FileInputStream in = new FileInputStream(fi);
				xmlString = getFileContentsAsUTF8(in);
				in.close();
				in = null;

			}
			
			//if file is just created new API metadatafile with wizard
			//creating new xml object and opening it
			if (xmlString.startsWith(CreatorXML.NEW_API_CREATOR_FILE_UID)) {
				components = new Components();
				setDirty(true);
			}
			//Otherwise file is old existing metadata file, parsing it and setting 
			//xml to this.xml
			else {
				components = parseXML(fileName, openContentStream(xmlString, charSet));
				setDirty(false);
				CreatorEditorConsole.getInstance().println(
						"Creator Script XML file: '" + fileName
								+ "' opened.");
			}

			components.setFileName(fileName);
			components.setFilePath(filePath);

		} catch (CreatorScriptNotValidException e) {
			//If parsing was ok, but xml was not walid setting xml so user can correct errors
			CreatorEditorConsole.getInstance().println(
					"CreatorScriptNotValidException on init: " + e.toString());
			showErrorDialog("Errors on Creator Script XML", "Creator XML file "
					+ fileName + " could not be parsed.", e.getMessage());
		} catch (Exception e) {
			e.printStackTrace();
			CreatorEditorConsole.getInstance().println(
					"Exception on init: " + e.toString());
			showErrorDialog("Error",
					"Creator editor could not be opened. Errors on "
							+ fileName, e.getMessage());
		}

	}
	
	/**
	 * Get xml String from stream
	 * @param is
	 * @return
	 * @throws CoreException
	 * @throws IOException
	 */
	private String getFileContentsAsUTF8(InputStream is) throws CoreException,
			IOException {

		int READ_BYTES = 3;
		
		StringBuffer buf = new StringBuffer();
		PushbackInputStream pushIn = new PushbackInputStream(is, READ_BYTES);

		byte[] bomBytes = new byte[READ_BYTES];//for reading 3 first bytes
		pushIn.read(bomBytes,0, READ_BYTES);//read 3 bytes
		
		boolean isBomFile = false;

		//BOM Files starts with 0xEF, 0xBB, 0xBF
		if(bomBytes[0] == (byte)0xEF && bomBytes[1] == (byte)0xBB && bomBytes[2] == (byte)0xBF){
			isBomFile = true;
		}
		
		//If file was not started with BOM, must put first characters to buffer 
		if (!isBomFile) {
			pushIn.unread(bomBytes, 0, READ_BYTES);
		}
	
		InputStreamReader isr = new InputStreamReader(pushIn, FileUtils.ENCODING_TYPE_UTF_8);
		BufferedReader br = new BufferedReader(isr);
		
		String line;
		while ((line = br.readLine()) != null) {			
			buf.append(line);
		}
		String xmlString = buf.toString();
		// Closing streams
		pushIn.close();
		br.close();
		isr.close();
		return xmlString;
	}	
	
	/**
	 * Get xml String from stream
	 * @param is
	 * @param character set
	 * @return
	 * @throws CoreException
	 * @throws IOException
	 */
	private String getFileContentsWithCharSet(InputStream is, String charset) throws CoreException,
			IOException {
		
		if(charset == null){
			charset = FileUtils.ENCODING_TYPE_UTF_8;
		}
		if(charset.equalsIgnoreCase(FileUtils.ENCODING_TYPE_UTF_8)){
			return getFileContentsAsUTF8(is);
		}
		
		StringBuffer buf = new StringBuffer();
		InputStreamReader isr = new InputStreamReader(is, charset);
		BufferedReader br = new BufferedReader(isr);
		
		String line;
		while ((line = br.readLine()) != null) {			
			buf.append(line);
		}
		String xmlString = buf.toString();
		// Closing streams
		is.close();
		br.close();
		isr.close();
		return xmlString;

	}		
	
	/**
	 * Setting editor title as file name
	 * @param editorInput
	 */
	private void setTitle(IEditorInput editorInput) {

		if (editorInput instanceof IFileEditorInput) {
			IFileEditorInput iFile = (IFileEditorInput) editorInput;
			IFile file = iFile.getFile();
			setPartName(file.getName());
		} else if (editorInput instanceof IPathEditorInput) {
			IPath path = ((IPathEditorInput) editorInput).getPath();
			String filename = path.lastSegment();
			setPartName(filename);
		}else if (editorInput instanceof FileStoreEditorInput) {
			FileStoreEditorInput inp = ((FileStoreEditorInput) editorInput);
			String filename = inp.getName();
			setPartName(filename);
		}		
	}	
	
	/* (non-Javadoc)
	 * Method declared on IEditorPart.
	 */
	public boolean isSaveAsAllowed() {
		return true;
	}
	/**
	 * Calculates the contents of page 2 when the it is activated.
	 */
	protected void pageChange(int newPageIndex) {
		super.pageChange(newPageIndex);
	}
	/**
	 * Closes all project files on project close.
	 */
	public void resourceChanged(final IResourceChangeEvent event){
		if(event.getType() == IResourceChangeEvent.PRE_CLOSE){
			Display.getDefault().asyncExec(new Runnable(){
				public void run(){
					IWorkbenchPage[] pages = getSite().getWorkbenchWindow().getPages();
					for (int i = 0; i<pages.length; i++){
						if(((FileEditorInput)editor.getEditorInput()).getFile().getProject().equals(event.getResource())){
							IEditorPart editorPart = pages[i].findEditor(editor.getEditorInput());
							pages[i].closeEditor(editorPart,true);
						}
					}
				}            
			});
		}
	}

	
	/**
	 * Get Arial Unicode MS font
	 * @return
	 */
	private Font getUnicodeFont() {
		Font defaultFont = componentsList.getFont();
		FontData defaulFD [] = defaultFont.getFontData();
		//Font Arial Unicode MS is Supplied with Microsoft Office 2002 (XP) and Microsoft Office 2003.
		//@see http://www.alanwood.net/unicode/fonts.html
		FontData fd = new FontData("Arial Unicode MS", defaulFD[0].getHeight(),  defaulFD[0].getStyle());
		return new Font(Display.getCurrent(), fd);
	}
	
	/**
	 * Parse xml String to XML object
	 * @param fileName
	 * @return
	 * @throws MetadataNotValidException
	 */
	private Components parseXML(String fileName, InputStream inUTF8)
			throws CreatorScriptNotValidException {
		CreatorXMLParser parser = new CreatorXMLParser();
		//This can be optimized by sending Reader as parameter instead of Stream,
		//That will also require logic update when opening file and checking how file was opened 
		//(was it a new file, project file or outside of the project file)
		Components comps = parser.parse(inUTF8);
		
		if(parser.wasErrors()){
			showErrorDialog("Errors on parsing", 
					"There was some errors when parsing file: " +fileName, 
					parser.getErrors());
		}
		
		return comps;
	}
	
	
	/* (non-Javadoc)
	 * @see com.nokia.s60tools.creator.editors.IComponentProvider#getComponents()
	 */
	public Components getComponents() {
		return components;
	}
	
	/**
	 * Get selected component
	 * @param selectionIndex
	 * @return Component
	 */
	private AbstractComponent getComponentBySelection(int selectionIndex) {
		String componentToString = componentsList.getItem(selectionIndex);

//		AbstractComponent comp = (AbstractComponent)componentsList.getData("" +selectionIndex);
		AbstractComponent comp = getComponents().getComponentByComponentString(componentToString);
		return comp;
	}
	
	/**
	 * Show an error dialog
	 * @param title
	 * @param message
	 * @param errors
	 */
	private void showErrorDialog(String title, String message, String errors) {
		Status status = new Status(IStatus.ERROR,
				"com.nokia.s60tools.metadataeditor", 0, errors, null);
		Shell sh;
		if (getContainer() != null) {
			try {
				sh = getContainer().getShell();
			} catch (SWTException e) {
				sh = CreatorActivator.getCurrentlyActiveWbWindowShell();
			}
		} else {
			sh = CreatorActivator.getCurrentlyActiveWbWindowShell();
		}

		ErrorDialog.openError(sh, title, message, status);
	}

	/**
	 * Show an information dialog
	 * @param title
	 * @param message
	 */
	@SuppressWarnings("unused")
	private void showInformationDialog(String title, String message) {
		Shell sh;
		if (getContainer() != null) {			
			try {
				sh = getContainer().getShell();
			} catch (SWTException e) {
				sh = CreatorActivator.getCurrentlyActiveWbWindowShell();
			}
			
		} else {
			sh = CreatorActivator.getCurrentlyActiveWbWindowShell();
		}

		MessageDialog.openInformation(sh, title, message);
	}	
	
	/**
	 * If data is modified, editor is dirty and can be saved
	 * NOTE: If editor data is changed and then changed back to original
	 * data contents, status still remain as dirty
	 */
	public boolean isDirty() {
		return this.isDirty;
	}	
	
	/**
	 * If editor contents is modified, save is allowed
	 * @param isDirty
	 */
	private void setDirty(boolean isDirty) {
		this.isDirty = isDirty;
		if(runInDeviceButton!=null){
			runInDeviceButton.setEnabled(!isDirty);
		}
		if (isDirty) {
			firePropertyChange(PROP_DIRTY);
		} else {
			firePropertyChange(PROP_INPUT);
		}
	}



	
	/* (non-Javadoc)
	 * @see com.nokia.s60tools.creator.editors.IComponentProvider#getComponents(java.lang.String)
	 */
	public Vector<AbstractComponent> getComponents(String type) {		
		return getComponents().getComponents(type);
	}


	/* (non-Javadoc)
	 * @see com.nokia.s60tools.creator.editors.IComponentProvider#getEditable()
	 */
	public AbstractComponent getEditable() {
		//Get a dialog by selection type
		int compIndex = componentsList.getSelectionIndex();
		AbstractComponent comp = null;
		if(compIndex != -1){
			comp = getComponentBySelection(compIndex);
		}
		return comp;
	}


	public boolean isInEditMode() {
		return isEditLaunched ;
	}

	
	/* (non-Javadoc)
	 * @see com.nokia.s60tools.creator.editors.IComponentProvider#addComponent(com.nokia.s60tools.creator.components.AbstractComponent)
	 */
	public void addComponent(AbstractComponent comp, IAddComponentListener listener) {

		//Adding a component to list, currently using outside of editor only when a contact set is created by dialog, not by editor

		if (comp.isValid()) {
			getComponents().addComponent(comp);
			addComponentToList(comp, componentsList);
			setDirty(true);
			if(listener != null){
				// tell listeners that a component was added to list
				listener.componentAdded(comp);
			}
		} else {
			// Error handling
			showErrorDialog("Component is not valid",
					"Component added is not valid",
					"Not valid component");
		}		
		
	}
	
}
