/*******************************************************************************
 * Copyright (c) 2005 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.bpel.ui.properties;

import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;

import org.eclipse.bpel.common.ui.details.IDetailsAreaConstants;
import org.eclipse.bpel.common.ui.details.viewers.CComboViewer;
import org.eclipse.bpel.common.ui.details.viewers.ComboViewerCellEditor;
import org.eclipse.bpel.common.ui.flatui.FlatFormAttachment;
import org.eclipse.bpel.common.ui.flatui.FlatFormData;
import org.eclipse.bpel.model.BPELFactory;
import org.eclipse.bpel.model.BPELPackage;
import org.eclipse.bpel.model.Correlation;
import org.eclipse.bpel.model.CorrelationPattern;
import org.eclipse.bpel.model.CorrelationSet;
import org.eclipse.bpel.model.Correlations;
import org.eclipse.bpel.model.Invoke;
import org.eclipse.bpel.model.OnEvent;
import org.eclipse.bpel.model.OnMessage;
import org.eclipse.bpel.model.PartnerActivity;
import org.eclipse.bpel.ui.Messages;
import org.eclipse.bpel.ui.commands.AddCorrelationCommand;
import org.eclipse.bpel.ui.commands.RemoveCorrelationCommand;
import org.eclipse.bpel.ui.commands.SetCommand;
import org.eclipse.bpel.ui.commands.SetCorrelationInitiateCommand;
import org.eclipse.bpel.ui.commands.SetCorrelationSetRefCommand;
import org.eclipse.bpel.ui.details.providers.AbstractContentProvider;
import org.eclipse.bpel.ui.details.providers.AddNullFilter;
import org.eclipse.bpel.ui.details.providers.ColumnTableProvider;
import org.eclipse.bpel.ui.details.providers.CorrelationContentProvider;
import org.eclipse.bpel.ui.details.providers.ModelLabelProvider;
import org.eclipse.bpel.ui.details.providers.ModelViewerSorter;
import org.eclipse.bpel.ui.util.BPELUtil;
import org.eclipse.bpel.ui.util.ModelHelper;
import org.eclipse.bpel.ui.util.MultiObjectAdapter;
import org.eclipse.bpel.ui.util.TableCursor;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.gef.commands.Command;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.ComboBoxCellEditor;
import org.eclipse.jface.viewers.ICellModifier;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Table;


/**
 * Details section for the incoming/outgoing correlation sets used by an
 * Invoke/Receive/Reply activity.
 */
public class CorrelationSection extends BPELPropertySection {
	
	public static final String YES = "yes"; //$NON-NLS-1$
	public static final String NO = "no"; //$NON-NLS-1$
	public static final String JOIN = "join"; //$NON-NLS-1$
	
	protected Table correlationTable;
	protected TableViewer correlationViewer;
	protected ColumnTableProvider tableProvider;
	protected TableCursor tableCursor = null;

	protected Button addButton;
	protected Button removeButton;
	
	protected boolean allowOutgoing;
	protected boolean allowIncoming;

	public class DirectionColumn extends ColumnTableProvider.Column implements ILabelProvider, ICellModifier {
		@Override
		public String getHeaderText() { return Messages.CorrelationDetails_Direction_1; } 
		@Override
		public String getProperty() { return "direction"; } //$NON-NLS-1$
		@Override
		public int getInitialWeight() { return 15; }

		// TODO: convert DirectionColumn to use images instead of text?
		
		protected final String[] oneWayStrings = { Messages.CorrelationDetails_Send_5, Messages.CorrelationDetails_Receive_4 }; 
		protected final String[] twoWayStrings = { Messages.CorrelationDetails_Send_5, Messages.CorrelationDetails_Receive_6, Messages.CorrelationDetails_Both_7 }; 

		@Override
		public CellEditor createCellEditor(Composite parent) {
			return new ComboBoxCellEditor(parent, twoWayStrings);
		}
		protected int patternIndex(CorrelationPattern pattern) {
			if (pattern == CorrelationPattern.RESPONSE_LITERAL)  return 0;
			if (pattern == CorrelationPattern.REQUEST_LITERAL)  return 1;
			if (pattern == CorrelationPattern.REQUESTRESPONSE_LITERAL)  return 2;
			return allowOutgoing? 0 : 1;
		}
		protected CorrelationPattern indexPattern(int index) {
			switch (index) {
			case 0: return CorrelationPattern.RESPONSE_LITERAL;
			case 1: return CorrelationPattern.REQUEST_LITERAL;
			case 2: return CorrelationPattern.REQUESTRESPONSE_LITERAL;
			}
			throw new IllegalArgumentException();
		}
		@Override
		public Image getImage(Object element) {
			return null;
		}
		public String getText(Object element) {
			if (allowOutgoing && allowIncoming) {
				return twoWayStrings[patternIndex(((Correlation)element).getPattern())]; 
			}
			return oneWayStrings[allowOutgoing? 0 : 1];
		}
		public boolean canModify(Object element, String property) {
			return allowOutgoing && allowIncoming;
		}
		public Object getValue(Object element, String property) {
			return new Integer(patternIndex(((Correlation)element).getPattern()));
		}
		public void modify(Object element, String property, Object value) {
			int index = ((Integer)value).intValue();
			CorrelationPattern pattern = indexPattern(index);
			Command cmd = new SetCommand((EObject)element, pattern, BPELPackage.eINSTANCE.getCorrelation_Pattern());
			getCommandFramework().execute(wrapInShowContextCommand(cmd));
		}
	}

	public class InitiationColumn extends ColumnTableProvider.Column implements ILabelProvider, ICellModifier {
		@Override
		public String getHeaderText() { return Messages.CorrelationDetails_Initiation_8; } 
		@Override
		public String getProperty() { return "initiation"; } //$NON-NLS-1$
		@Override
		public int getInitialWeight() { return 10; }

		protected final String[] strings = {
			Messages.CorrelationDetails_No_10, 
			Messages.CorrelationDetails_Yes_11, 
			Messages.CorrelationDetails_Join_11
		};
		
		@Override
		public CellEditor createCellEditor(Composite parent) {
			ComboBoxCellEditor result = new ComboBoxCellEditor();
			result.setStyle(SWT.READ_ONLY);
			result.create(parent);
			result.setItems(strings);
			return result;
		}
		protected int initiationIndex(String initiation) {
			if (YES.equals(initiation))
				return 1;
			if (JOIN.equals(initiation))
				return 2;
			return 0;
		}
		public String getText(Object element) {
			return strings[initiationIndex(((Correlation)element).getInitiate())]; 
		}
		public boolean canModify(Object element, String property) {
			return true;
		}
		public Object getValue(Object element, String property) {
			return new Integer(initiationIndex(((Correlation)element).getInitiate()));
		}
		public void modify(Object element, String property, Object value) {
			int index = ((Integer)value).intValue();
			String initiation = NO;
			switch (index) {
				case 0:
					initiation = NO;
					break;
				case 1:
					initiation = YES;
					break;
				case 2:
					initiation = JOIN;
					break;
			}
			Command cmd = new SetCorrelationInitiateCommand((Correlation)element, initiation);
			getCommandFramework().execute(wrapInShowContextCommand(cmd));
		}
	}

	public class NameColumn extends ColumnTableProvider.Column implements ILabelProvider, ICellModifier {
		@Override
		public String getHeaderText() { return Messages.CorrelationDetails_Correlation_Set_12; } 
		@Override
		public String getProperty() { return "setName"; } //$NON-NLS-1$
		@Override
		public int getInitialWeight() { return 30; }

		ComboViewerCellEditor cellEditor;
		ModelLabelProvider labelProvider;

		@Override
		public CellEditor createCellEditor(Composite parent) {
			cellEditor = new ComboViewerCellEditor();
			cellEditor.setStyle(SWT.READ_ONLY);
			cellEditor.create(parent);
			labelProvider = new ModelLabelProvider();
			CComboViewer viewer = cellEditor.getViewer();
			viewer.addFilter(AddNullFilter.getInstance());
			viewer.setContentProvider(new AbstractContentProvider(){
				
				@Override
				public Object[] getElements(Object input)  {
					if (input instanceof EObject)						
						return BPELUtil.getVisibleCorrelationSets((EObject)input);
					return EMPTY_ARRAY;
				}
				
			});
			viewer.setLabelProvider(labelProvider);
			viewer.setSorter(ModelViewerSorter.getInstance());
			viewer.setInput(ModelHelper.getContainingScope(getInput()));
			return cellEditor;
		}
		public String getText(Object element) {
			return labelProvider.getText(((Correlation)element).getSet());
		}
		public boolean canModify(Object element, String property) {
			return true;
		}
		public Object getValue(Object element, String property) {
			return ((Correlation)element).getSet();
		}
		public void modify(Object element, String property, Object value) {
			Command cmd = new SetCorrelationSetRefCommand((Correlation)element, (CorrelationSet)value);
			getCommandFramework().execute(wrapInShowContextCommand(cmd));
		}
	}

	@Override
	public boolean shouldUseExtraSpace() { return true; }

	protected boolean isCorrelationListAffected(Notification n) {
		if (n.getNotifier() instanceof Correlations) return true;
		if (n.getNotifier() instanceof PartnerActivity) {
			return (n.getFeatureID(PartnerActivity.class) ==
				BPELPackage.PARTNER_ACTIVITY__CORRELATIONS);
		}
		if (n.getNotifier() instanceof OnMessage) {
			return (n.getFeatureID(OnMessage.class) == BPELPackage.ON_MESSAGE__CORRELATIONS);
		}
		if (n.getNotifier() instanceof OnEvent) {
			return (n.getFeatureID(OnEvent.class) == BPELPackage.ON_EVENT__CORRELATIONS);
		}
		return false;
	}

	@Override
	protected MultiObjectAdapter[] createAdapters() {
		return new MultiObjectAdapter[] {
			/* model object and Correlations object */
			new MultiObjectAdapter() {
				@Override
				public void notify(Notification n) {
					if (isCorrelationListAffected(n)) {
						updateCorrelationWidgets(null);
						refreshAdapters();
					}
				}
			},
			/* correlation(s) and correlation set(s) */
			new MultiObjectAdapter() {
				@Override
				public void notify(Notification n) {
					if (n.getNotifier() instanceof Correlation) {
						updateCorrelationWidgets((Correlation)n.getNotifier());
						refreshAdapters();
					} else if (n.getNotifier() instanceof CorrelationSet) {
						Correlations c = ModelHelper.getCorrelations(getInput());
						if (c != null) {
							for (Iterator it = c.getChildren().iterator(); it.hasNext(); ) {
								Correlation corr = (Correlation)it.next();
								fAdapters[1].addToObject(corr);
								if (n.getNotifier() == corr.getSet()) {
									updateCorrelationWidgets(corr);
									refreshAdapters();
									break;
								} 
							}
						}
					}
				}
			}
		};
	}

	@Override
	protected void addAllAdapters() {
		super.addAllAdapters();
		Correlations c = ModelHelper.getCorrelations(getInput());
		if (c != null) {
			fAdapters[0].addToObject(c);
			for (Iterator it = c.getChildren().iterator(); it.hasNext(); ) {
				Correlation corr = (Correlation)it.next();
				fAdapters[1].addToObject(corr);
				if (corr.getSet() != null)  fAdapters[1].addToObject(corr.getSet());
			}
		}
	}

	protected Correlation newDefaultCorrelation() {
		Correlation correlation = BPELFactory.eINSTANCE.createCorrelation();
		correlation.setInitiate(NO);
		if (getInput() instanceof Invoke) {
			correlation.setPattern(CorrelationPattern.RESPONSE_LITERAL);
		}
		
		// Try to select a correlation set that isn't already in the table.
		CorrelationSet firstUnusedSet = null;

		Correlations c = ModelHelper.getCorrelations(getInput());
		List corrList = (c == null)? Collections.EMPTY_LIST : c.getChildren();
		HashSet usedSets = new HashSet(corrList.size());
		for (Iterator it = corrList.iterator(); it.hasNext(); ) {
			usedSets.add(((Correlation)it.next()).getSet());
		}
		for (Iterator it = getProcess().getCorrelationSets().getChildren().iterator(); it.hasNext(); ) {
			CorrelationSet set = (CorrelationSet)it.next();
			if (!usedSets.contains(set)) {
				// found one we're not using.
				firstUnusedSet = set; break;
			}
		}
		// note: it's okay if firstUnusedSet is still null here (we'll show "-- none --").
		correlation.setSet(firstUnusedSet);
		return correlation;
	}

	protected void createCorrelationSetWidgets(Composite composite)  {
		FlatFormData data;
		
		addButton = fWidgetFactory.createButton(composite, Messages.CorrelationDetails_Add_18, SWT.PUSH); 
		removeButton = fWidgetFactory.createButton(composite, Messages.CorrelationDetails_Remove_19, SWT.PUSH); 
		Label correlationSetLabel = fWidgetFactory.createLabel(composite, Messages.CorrelationDetails_Correlation_Sets__20); 
		correlationTable = fWidgetFactory.createTable(composite, SWT.FULL_SELECTION | SWT.V_SCROLL);

		data = new FlatFormData();
		data.left = new FlatFormAttachment(0, 0);
		data.top = new FlatFormAttachment(0, 0);
		correlationSetLabel.setLayoutData(data);
		
		data = new FlatFormData();
		data.width = BPELUtil.calculateButtonWidth(addButton, STANDARD_BUTTON_WIDTH);
		data.right = new FlatFormAttachment(removeButton, -IDetailsAreaConstants.HSPACE);
		data.top = new FlatFormAttachment(0, 0);
		addButton.setLayoutData(data);
		
		data = new FlatFormData();
		data.width = BPELUtil.calculateButtonWidth(removeButton, STANDARD_BUTTON_WIDTH);
		data.right = new FlatFormAttachment(100, 0);
		data.top = new FlatFormAttachment(0, 0);
		removeButton.setLayoutData(data);
		removeButton.setEnabled(false);
		
		addButton.addSelectionListener(new SelectionListener() {
			public void widgetSelected(SelectionEvent e) {
				Correlation correlation = newDefaultCorrelation();
				Command cmd = new AddCorrelationCommand(getInput(), correlation);
				getCommandFramework().execute(wrapInShowContextCommand(cmd));
				correlationViewer.setSelection(new StructuredSelection(correlation));
				removeButton.setEnabled(true);
				if (tableCursor != null)
					tableCursor.refresh();
			}
			public void widgetDefaultSelected(SelectionEvent e) { widgetSelected(e); }
		});
		Listener removeListener = new Listener() {
			public void handleEvent(Event e) {
				if (e.type == SWT.KeyUp && e.character != SWT.DEL)  return;
				IStructuredSelection sel = (IStructuredSelection)correlationViewer.getSelection();
				Correlation correlation = (Correlation)sel.getFirstElement();
				int selectionIndex = correlationTable.getSelectionIndex();
				if (correlation != null)  {
					Command cmd = new RemoveCorrelationCommand(getInput(), correlation);
					getCommandFramework().execute(wrapInShowContextCommand(cmd));
				}
				// selects the element at the deleted element position
				int items = correlationTable.getItemCount();
				if (items > 0) {
					selectionIndex = (selectionIndex < items) ? selectionIndex : 0;
					correlationTable.setSelection(selectionIndex);
				}
				if (tableCursor != null)
					tableCursor.refresh();
				updateButtonEnablement();
			}
		};
		correlationTable.addListener(SWT.KeyUp, removeListener);
		
		removeButton.addListener(SWT.Selection, removeListener);
		removeButton.addListener(SWT.DefaultSelection, removeListener);

		
		data = new FlatFormData();
		data.left = new FlatFormAttachment(0, 0);
		data.right = new FlatFormAttachment(100, 0);
		data.top = new FlatFormAttachment(addButton, IDetailsAreaConstants.VSPACE);
		data.bottom = new FlatFormAttachment(100, 0);
		correlationTable.setLayoutData(data);

		correlationTable.setLinesVisible(true);
		correlationTable.setHeaderVisible(true);
		
		tableProvider = new ColumnTableProvider();
		tableProvider.add(new DirectionColumn());
		tableProvider.add(new InitiationColumn());
		tableProvider.add(new NameColumn());
		
		correlationTable.addSelectionListener(new SelectionListener() {
			public void widgetSelected(SelectionEvent e) {
				removeButton.setEnabled(!correlationViewer.getSelection().isEmpty());
			}
			public void widgetDefaultSelected(SelectionEvent e) { widgetSelected(e); }
		});
	
		correlationViewer = new TableViewer(correlationTable);
		tableProvider.createTableLayout(correlationTable);
		correlationViewer.setLabelProvider(tableProvider);
		correlationViewer.setCellModifier(tableProvider);
		correlationViewer.setContentProvider(new CorrelationContentProvider());
		// TODO: should this have a sorter?
		correlationViewer.setColumnProperties(tableProvider.getColumnProperties());
		correlationViewer.addSelectionChangedListener(new ISelectionChangedListener(){
			public void selectionChanged(SelectionChangedEvent event) {
				updateButtonEnablement();
			}
		});
		
		tableCursor = BPELUtil.createTableCursor(correlationTable, correlationViewer);
	}
	
	@Override
	protected void createClient(Composite parent)  {
		Composite composite = createFlatFormComposite(parent);
		createCorrelationSetWidgets(composite);
	}

	protected void updateCorrelationWidgets(Correlation corr) {
		Object input = getInput();
		if (input == null)  throw new IllegalStateException();
		
		allowOutgoing = ModelHelper.isMessageActivity(input, ModelHelper.OUTGOING);
		allowIncoming = ModelHelper.isMessageActivity(input, ModelHelper.INCOMING);
		correlationViewer.setInput(getInput());
		if (corr != null) {
			correlationViewer.refresh(corr, true);
		} else {
			correlationViewer.refresh();
		}
		if (tableCursor != null)
			tableCursor.refresh();
	}
	
	@Override
	public void refresh() {
		super.refresh();
		updateCorrelationWidgets(null);
	}

	@Override
	public Object getUserContext() {
		return ((StructuredSelection)correlationViewer.getSelection()).getFirstElement();
	}
	@Override
	public void restoreUserContext(Object userContext) {
		correlationTable.setFocus();
		if (userContext != null) {
			correlationViewer.setSelection(new StructuredSelection(userContext));
		}
	}
	
	@Override
	protected void basicSetInput(EObject newInput) {
		super.basicSetInput(newInput);
		correlationViewer.setCellEditors(tableProvider.createCellEditors(correlationTable));
	}
	

	protected void updateButtonEnablement(){
		boolean hasSelection = !correlationViewer.getSelection().isEmpty();
		removeButton.setEnabled(hasSelection);
	}	
}
