/*******************************************************************************
 * Copyright (C) 2003-2007, 2013, Guillaume Brocker
 * 
 * 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:
 *     Guillaume Brocker - Initial API and implementation
 *
 ******************************************************************************/ 

package eclox.ui.editor.editors;

import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Vector;

import org.eclipse.jface.viewers.IOpenListener;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.ListViewer;
import org.eclipse.jface.viewers.OpenEvent;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.forms.widgets.FormToolkit;

import eclox.core.doxyfiles.Setting;

/**
 * Implements a list setting editor. This class is abstract since it provides no way to
 * edit value compounds. See derived classes.
 * 
 * @author gbrocker
 */
public abstract class ListEditor extends SettingEditor {
	
	/**
	 * Implements the table viewer content provider.
	 */
	private class MyContentProvider implements IStructuredContentProvider {

		public Object[] getElements(Object inputElement) {
			Vector	compounds = (Vector) inputElement;
			
			return compounds.toArray();
		}

		public void dispose() {}

		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {}
		
	}
	
	/**
	 * Implements the table viewer label provider.
	 */
	private class MyLabelProvider extends LabelProvider {
		
		public String getText(Object element) {
			return new String( (String)element );
		}
		
	}
	
	/**
	 * Implements an open listener that will trigger the edition of the selected list viewer item.
	 */
	private class MyOpenListener implements IOpenListener {

		public void open(OpenEvent event) {
			editSelectedValueCompound();
		}
		
	}
	
	/**
	 * Implements a button selection listener.
	 */
	private class MyButtonSelectionListener implements SelectionListener {

		public void widgetDefaultSelected(SelectionEvent e) {
			widgetSelected( e );
		}

		public void widgetSelected(SelectionEvent e) {
			if( e.widget == addButton ) {
				addValueCompound();
			}
			else if( e.widget == removeButton ) {
				removeValueCompounds();
			}
			else if( e.widget == upButton ) {
				moveValueCompoundsUp();
			}
			else if( e.widget == downButton ) {
				moveValueCompoundsDown();
			}
			else {
				// Unsuported widget.
				assert false;
			}
		}
		
	}
	
	/**
	 * Implements a selection change listener to update the button states.
	 */
	private class MySelectionChangedListener implements ISelectionChangedListener {

		public void selectionChanged(SelectionChangedEvent event) {
			updateButtons();
		}
		
	}
	
	private Vector valueCompounds;	///< The collection of the setting's value compounds.
	
	private ListViewer listViewer;	///< the table viewer used to edit the managed setting
	private Button addButton;		///< the button allowing to trigger a new value addition
	private Button removeButton;	///< the button allowing to trigger the deletion of selected values
	private Button upButton;		///< the button allowing to move the selected values up
	private Button downButton;		///< the button allowing to move the selected values down
	
	/**
	 * @see eclox.ui.editor.editors.IEditor#commit()
	 */
	public void commit() {
		if( hasInput() ) {
			getInput().setValue( valueCompounds );
			fireEditorChanged();
		}
	}

	/**
	 * @see eclox.ui.editor.editors.IEditor#createContent(org.eclipse.swt.widgets.Composite, org.eclipse.ui.forms.widgets.FormToolkit)
	 */
	public void createContent(Composite parent, FormToolkit formToolkit) {
		// Pre-condition
		assert listViewer == null;
		
		// Activates border painting.
		formToolkit.paintBordersFor( parent );
		
		// Installs the layout.
		FormLayout	layout = new FormLayout();
		layout.spacing = 2;
		parent.setLayout( layout );
		
		// Creates the list viewer and installs it in the layout.
		FormData		formData;
		
		listViewer = new ListViewer( parent, 0 );
		formData = new FormData();
		formData.top = new FormAttachment( 0, 2 );
		formData.right = new FormAttachment( 85, -1 );
		formData.bottom = new FormAttachment( 100, -2 );
		formData.left = new FormAttachment( 0, 1 );
		listViewer.getControl().setLayoutData( formData );
		listViewer.getControl().setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TEXT_BORDER);

		// Initializes the list viewer.
		listViewer.setContentProvider( new MyContentProvider() );
		listViewer.setLabelProvider( new MyLabelProvider() );
		listViewer.addOpenListener( new MyOpenListener() );
		
		// Creates various buttons and installs them in the layout.
		addButton		= formToolkit.createButton( parent, "Add",    0 );
		removeButton	= formToolkit.createButton( parent, "Remove", 0 );
		upButton		= formToolkit.createButton( parent, "Up",     0 );
		downButton		= formToolkit.createButton( parent, "Down",   0 );
		
		formData = new FormData();
		formData.top = new FormAttachment( 0, 0 );
		formData.right = new FormAttachment( 100, 0 );
		formData.left = new FormAttachment( listViewer.getControl(), 2, SWT.RIGHT );
		addButton.setLayoutData( formData );
		
		formData = new FormData();
		formData.top = new FormAttachment( addButton, 0, SWT.BOTTOM );
		formData.right = new FormAttachment( 100, 0 );
		formData.left = new FormAttachment( listViewer.getControl(), 2, SWT.RIGHT );
		removeButton.setLayoutData( formData );
		
		formData = new FormData();
		formData.top = new FormAttachment( removeButton, 6, SWT.BOTTOM );
		formData.right = new FormAttachment( 100, 0 );
		formData.left = new FormAttachment( listViewer.getControl(), 2, SWT.RIGHT );
		upButton.setLayoutData( formData );
		
		formData = new FormData();
		formData.top = new FormAttachment( upButton, 0, SWT.BOTTOM );
		formData.right = new FormAttachment( 100, 0 );
		formData.left = new FormAttachment( listViewer.getControl(), 2, SWT.RIGHT );
		downButton.setLayoutData( formData );
		
		// Assignes a selection listener to the managed buttons.
		MyButtonSelectionListener selectionListener = new MyButtonSelectionListener();
		
		addButton.addSelectionListener( selectionListener );
		removeButton.addSelectionListener( selectionListener );
		upButton.addSelectionListener( selectionListener );
		downButton.addSelectionListener( selectionListener );
		
		// Adds a selection change listener to the list viewer and initializes the button states.
		listViewer.addPostSelectionChangedListener( new MySelectionChangedListener() );
		updateButtons();
		
		// Post-condition
		assert listViewer != null;
	}

	public boolean grabVerticalSpace() {
		return true;
	}

	public void dispose() {
		// Pre-condition
		assert listViewer	!= null;
		assert addButton	!= null;
		assert removeButton	!= null;
		assert upButton		!= null;
		assert downButton	!= null;
		
		listViewer.getControl().dispose();
		listViewer = null;
		addButton.dispose();
		addButton = null;
		removeButton.dispose();
		removeButton = null;
		upButton.dispose();
		upButton = null;
		downButton.dispose();
		downButton = null;
		
		super.dispose();
		
		// Post-condition
		assert listViewer	== null;
		assert addButton	== null;
		assert removeButton	== null;
		assert upButton		== null;
		assert downButton	== null;
	}

	/**
	 * @see eclox.ui.editor.editors.IEditor#setEnabled(boolean)
	 */
	public void setEnabled(boolean enabled) {
		// Pre-condition
		assert listViewer	!= null;
		assert addButton	!= null;
		assert removeButton	!= null;
		assert upButton		!= null;
		assert downButton	!= null;	
		
		listViewer.getControl().setEnabled(enabled);
		addButton.setEnabled(enabled);
		removeButton.setEnabled(enabled);
		upButton.setEnabled(enabled);
		downButton.setEnabled(enabled);		
	}

	/**
	 * @see eclox.ui.editor.editors.IEditor#setFocus()
	 */
	public void setFocus() {
		// Pre-condition
		assert listViewer != null;
		
		listViewer.getControl().setFocus();

	}
	
	/**
	 * @see eclox.ui.editor.editors.IEditor#refresh()
	 */
	public void refresh() {
		// Pre-condition
		assert listViewer != null;
		
		if( hasInput() ) {
			valueCompounds = new Vector();
			getInput().getSplittedValue( valueCompounds );
			listViewer.setInput(valueCompounds);
			
			updateButtons();
			fireEditorChanged();
		}
	}

	/**
	 * @see eclox.ui.editor.editors.IEditor#isDirty()
	 */
	public boolean isDirty() {
		return false;
	}
	
	/**
	 * @see eclox.ui.editor.editors.IEditor#isStale()
	 */
	public boolean isStale() {
		boolean	result = false;
		
		if( hasInput() ) {
			Collection	values = new Vector();
			
			getInput().getSplittedValue(values);
			result = valueCompounds.equals(values) == false;
		}
		return result;
	}
	
	/**
	 * Edits the given value compound and returns the new comound value. 
	 * Null is interpreted as a canceled edition.
	 * 
	 * Sub classes have to implement this method to provide a dialog to the user to do
	 * the value compund edition.
	 * 
	 * @param parent		the parent shell that implementors may use as parent window for any dialog
	 * @param setting	the setting begin edited (it is just given as information and should be edited directly)
	 * @param compound	the value compund to edit
	 * 
	 * @return	a string containing the new compund value or null
	 */
	abstract protected String editValueCompound( Shell parent, Setting setting, String compound );
	
	/**
	 * Adds a new value compound.
	 */
	private void addValueCompound() {
		// Pre-condition
		assert listViewer != null;
		assert valueCompounds != null;
		
		// Edits a new value.
		String	newCompound = editValueCompound( listViewer.getControl().getShell(), getInput(), "new value" );
		
		// Inserts the new compound if it has been validated.
		if( newCompound != null ) {
			valueCompounds.add( newCompound );
			fireEditorChanged();
			commit();
			listViewer.refresh();
			listViewer.setSelection( new StructuredSelection(new Integer(valueCompounds.size() -1)) );
		}
	}
	
	/**
	 * Edits the value compound that is selected in the list viewer.
	 * 
	 * @return	true when a compound has been modified, false otherwise
	 */
	private boolean editSelectedValueCompound() {
		// Pre-condition
		assert listViewer != null;
		assert valueCompounds != null; 
		
		
		// Retrieves and handles the selection.
		IStructuredSelection	selection	= (IStructuredSelection) listViewer.getSelection();
		String					original	= (String) selection.getFirstElement();
		String					edited		= editValueCompound( listViewer.getControl().getShell(), getInput(), original );
		
		// Processes the edited compound.
		if( edited != null ) {			
			// Updates the setting.
			valueCompounds.set( valueCompounds.indexOf(original), edited );
			fireEditorChanged();
			
			// Commit changes and erstores the selection.
			listViewer.getControl().setRedraw( false );
			commit();
			listViewer.refresh();
			listViewer.setSelection( new StructuredSelection(edited) );
			listViewer.getControl().setRedraw( true );
			
			// Job's done.
			return true;
		}
		else {
			return false;
		}
	}
	
	/**
	 * Moves the value compounds corresponding to the current selection up.
	 */
	private void moveValueCompoundsUp() {
		// Pre-condition
		assert listViewer != null;
		assert valueCompounds != null;
		
		// Retrieves the current selection and skip if it is empty.
		IStructuredSelection	selection = (IStructuredSelection) listViewer.getSelection();
		if( selection.isEmpty() == true ) {
			return;
		}
		
		// Retrieves the list of the selected items.
		Vector		selected	= new Vector(selection.toList());
		Iterator	i			= valueCompounds.iterator();
		while( i.hasNext() == true && selected.isEmpty() == false ) {
			Object	current = i.next();
			
			if( current.equals(selected.get(0)) ) {
				int index = valueCompounds.indexOf(current);
				if( index > 0 ) {
					Collections.swap(valueCompounds, index, index-1);
				}
				else {
					break;
				}
				selected.remove(0);
			}
		}
		fireEditorChanged();

		// Commits changes and reselected moved objects.
		listViewer.getControl().setRedraw( false );
		commit();
		listViewer.refresh();
		listViewer.getControl().setRedraw( true );
	}

	/**
	 * Moves the value compounds corresponding to the current selection down.
	 */
	private void moveValueCompoundsDown() {
		// Pre-condition
		assert listViewer != null;
		assert valueCompounds != null;
		
		// Retrieves the current selection and skip if it is empty.
		IStructuredSelection		selection = (IStructuredSelection) listViewer.getSelection();
		if( selection.isEmpty() == true ) {
			return;
		}
		
		// Retrieves the list of the selected items.
		Vector		selected = new Vector(selection.toList());
		
		Collections.reverse( selected );
		Collections.reverse(valueCompounds);
		
		// Retrieves the list of the selected items.
		Iterator	i			= valueCompounds.iterator();
		while( i.hasNext() == true && selected.isEmpty() == false ) {
			Object	current = i.next();
			
			if( current.equals(selected.get(0)) ) {
				int index = valueCompounds.indexOf(current);
				if( index > 0 ) {
					Collections.swap(valueCompounds, index, index-1);
				}
				else {
					break;
				}
				selected.remove(0);
			}
		}
		
		Collections.reverse(valueCompounds);
		fireEditorChanged();
		
		// Commits changes and reselected moved objects.
		listViewer.getControl().setRedraw( false );
		commit();
		listViewer.refresh();
		listViewer.getControl().setRedraw( true );
	}
	
	/**
	 * Removes the selected value compounds.
	 */
	private void removeValueCompounds() {
		// Pre-condition
		assert listViewer != null;
		assert valueCompounds != null;
		
		// Retrieves the current selection and skip if it is empty.
		IStructuredSelection		selection = (IStructuredSelection) listViewer.getSelection();
		if( selection.isEmpty() == true ) {
			return;
		}
		
		// Removes selected items from the value compounds.
		valueCompounds.removeAll( selection.toList() );
		fireEditorChanged();
		
		// Commits changes.
		commit();
		listViewer.refresh();
	}

	/**
	 * Updates the state of some buttons.
	 */
	private void updateButtons() {
		// Pre-condition
		assert listViewer != null;
		assert removeButton != null;
		assert upButton != null;
		assert downButton != null;
		
		// Retrieves the selection emptiness and updates the buttons.
		boolean willBeEnabled = listViewer.getSelection().isEmpty() == false;
		
		removeButton.setEnabled( willBeEnabled );
		upButton.setEnabled( willBeEnabled );
		downButton.setEnabled( willBeEnabled );
	}

}
