/*******************************************************************************
 * 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 org.eclipse.bpel.common.ui.details.ButtonIValue;
import org.eclipse.bpel.common.ui.details.IDetailsAreaConstants;
import org.eclipse.bpel.common.ui.flatui.FlatFormAttachment;
import org.eclipse.bpel.common.ui.flatui.FlatFormData;
import org.eclipse.bpel.model.Assign;
import org.eclipse.bpel.model.BPELFactory;
import org.eclipse.bpel.model.BPELPackage;
import org.eclipse.bpel.model.Copy;
import org.eclipse.bpel.model.From;
import org.eclipse.bpel.model.To;
import org.eclipse.bpel.ui.Messages;
import org.eclipse.bpel.ui.commands.AddCopyCommand;
import org.eclipse.bpel.ui.commands.RemoveCopyCommand;
import org.eclipse.bpel.ui.commands.SwapCopyCommand;
import org.eclipse.bpel.ui.util.BPELUtil;
import org.eclipse.bpel.ui.util.MultiObjectAdapter;
import org.eclipse.core.resources.IMarker;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.jface.viewers.ListViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;

/**
 * AssignImplDetails allows viewing and editing of the copy elements in an
 * Assign.
 * 
 * The input of this section is the Assign activity.
 * 
 * 
 * @author IBM Original Contribution
 * @author Michal Chmielewski (michal.chmielewski@oracle.com)
 * @date Jun 7, 2007
 * 
 */

@SuppressWarnings("nls")
public class AssignImplSection extends BPELPropertySection {

	static String CURRENT_INDEX_PROPERTY = "currentIndex";

	/** The to section */
	CategorySection fToSection = new CategorySection(this);

	/** The from section */
	CategorySection fFromSection = new CategorySection(this);

	/** The current copy rule being edited. */
	Copy fCurrentCopy;

	org.eclipse.swt.widgets.List fCopyList;

	Composite copySelectComposite;

	protected ListViewer fCopyListViewer;

	Button fDeleteCopy;
	Button fMoveUp;
	Button fMoveDown;

	Button fKeepSrcElement;
	Button fIgnoreMissingSourceData;

	EditController fKeepSrcElementController;
	EditController fIgnoreMissingSourceDataController;

	/**
	 * 
	 */

	public AssignImplSection() {
		super();

		fToSection.fAllowed = new IAssignCategory[] {
				new VariablePartAssignCategory(this),
				new ExpressionAssignCategory(this),
				new VariablePropertyAssignCategory(this),
				new PartnerRoleAssignCategory(this, false) };

		fFromSection.fAllowed = new IAssignCategory[] {
				new VariablePartAssignCategory(this),
				new ExpressionAssignCategory(this),
				new LiteralAssignCategory(this),
				new VariablePropertyAssignCategory(this),
				new PartnerRoleAssignCategory(this, true),
				new EndpointReferenceAssignCategory(this),
				new OpaqueAssignCategory(this) };

	}

	@Override
	protected MultiObjectAdapter[] createAdapters() {
		return new MultiObjectAdapter[] {
		/* model object */
		new MultiObjectAdapter() {

			@Override
			public void notify(Notification n) {

				adjustCopyRulesList();
				if (n.getFeature() == BPELPackage.eINSTANCE.getAssign_Copy()) {
					Copy copy = (Copy) n.getNewValue();
					Assign assign = getModel();
					if (copy != null) {
						selectCopyInList(assign.getCopy().indexOf(copy));
					}

				}

			}
		}, };
	}

	@Override
	protected void addAllAdapters() {
		super.addAllAdapters();
		EList<Copy> list = ((Assign) getInput()).getCopy();
		for (Copy copy : list) {
			fAdapters[0].addToObject(copy);
			if (copy.getFrom() != null)
				fAdapters[0].addToObject(copy.getFrom());
			if (copy.getTo() != null)
				fAdapters[0].addToObject(copy.getTo());
		}
	}

	protected void createCopySelectWidgets(Composite parent) {

		FlatFormData data;

		Composite c = copySelectComposite = createFlatFormComposite(parent);

		Button insertCopy = fWidgetFactory.createButton(c,
				Messages.AssignImplDetails_New__5, SWT.PUSH);
		fDeleteCopy = fWidgetFactory.createButton(c,
				Messages.AssignImplDetails_Delete__6, SWT.PUSH);
		fMoveUp = fWidgetFactory.createButton(c, "Move Up", SWT.PUSH);
		fMoveDown = fWidgetFactory.createButton(c, "Move Down", SWT.PUSH);

		fCopyList = fWidgetFactory.createList(c, SWT.BORDER | SWT.V_SCROLL
				| SWT.SINGLE);

		int preferredWidth = 200;

		data = new FlatFormData();
		data.left = new FlatFormAttachment(0, 0);
		data.right = new FlatFormAttachment(0, preferredWidth);
		data.top = new FlatFormAttachment(0, 0);
		data.bottom = new FlatFormAttachment(100, 0);
		c.setLayoutData(data);

		data = new FlatFormData();
		data.left = new FlatFormAttachment(0, 0);
		data.right = new FlatFormAttachment(100, 0);
		data.top = new FlatFormAttachment(0, 0);
		data.bottom = new FlatFormAttachment(insertCopy,
				-IDetailsAreaConstants.VSPACE);
		fCopyList.setLayoutData(data);

		data = new FlatFormData();
		data.left = new FlatFormAttachment(0, 0);
		data.right = new FlatFormAttachment(50, 0);
		data.bottom = new FlatFormAttachment(fMoveUp, 0);
		insertCopy.setLayoutData(data);

		data = new FlatFormData();
		data.left = new FlatFormAttachment(insertCopy, 0);
		data.right = new FlatFormAttachment(100, 0);
		data.bottom = new FlatFormAttachment(fMoveDown, 0);
		fDeleteCopy.setLayoutData(data);

		data = new FlatFormData();
		data.left = new FlatFormAttachment(0, 0);
		data.right = new FlatFormAttachment(50, 0);
		data.bottom = new FlatFormAttachment(100, 0);
		fMoveUp.setLayoutData(data);

		data = new FlatFormData();
		data.left = new FlatFormAttachment(fMoveUp, 0);
		data.right = new FlatFormAttachment(100, 0);
		data.bottom = new FlatFormAttachment(100, 0);
		fMoveDown.setLayoutData(data);

		insertCopy.addSelectionListener(new SelectionListener() {
			public void widgetSelected(SelectionEvent e) {

				Copy copy = BPELFactory.eINSTANCE.createCopy();

				getCommandFramework().execute(
						wrapInShowContextCommand(new AddCopyCommand(
								(Assign) getInput(), copy)));
				refreshAdapters();

			}

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

		fDeleteCopy.addSelectionListener(new SelectionListener() {
			public void widgetSelected(SelectionEvent e) {
				Assign assign = getModel();
				int index = fCopyList.getSelectionIndex();
				index = (index == assign.getCopy().size() - 1) ? index - 1
						: index;

				getCommandFramework().execute(
						wrapInShowContextCommand(new RemoveCopyCommand(
								(Assign) getInput(), fCurrentCopy)));
				selectCopyInList(index);
			}

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

		fCopyList.addSelectionListener(new SelectionListener() {

			public void widgetSelected(SelectionEvent e) {
				int index = fCopyList.getSelectionIndex();
				selectCopyInList(index);
			}

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

		fMoveUp.addSelectionListener(new SelectionListener() {
			public void widgetSelected(SelectionEvent e) {
				Assign assign = getModel();
				int index = fCopyList.getSelectionIndex();
				getCommandFramework().execute(
						wrapInShowContextCommand(new SwapCopyCommand(assign,
								index, index - 1)));
				selectCopyInList(index - 1);
			}

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

		fMoveDown.addSelectionListener(new SelectionListener() {
			public void widgetSelected(SelectionEvent e) {
				Assign assign = getModel();
				int index = fCopyList.getSelectionIndex();
				getCommandFramework().execute(
						wrapInShowContextCommand(new SwapCopyCommand(assign,
								index, index + 1)));
				selectCopyInList(index + 1);
			}

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

	}

	protected void createCategorySectionWidgets(Composite composite,
			final CategorySection section, boolean isFrom) {

		FlatFormData data;

		section.fLabel = fWidgetFactory.createLabel(composite,
				isFrom ? Messages.AssignImplDetails_From__1
						: Messages.AssignImplDetails_To__2);
		section.fCombo = new Combo(composite, SWT.FLAT | SWT.BORDER
				| SWT.READ_ONLY);
		data = new FlatFormData();

		if (isFrom) {
			data.left = new FlatFormAttachment(0, BPELUtil.calculateLabelWidth(
					section.fLabel, STANDARD_LABEL_WIDTH_SM));
			data.right = new FlatFormAttachment(50,
					-IDetailsAreaConstants.CENTER_SPACE);
		} else {
			data.left = new FlatFormAttachment(50,
					IDetailsAreaConstants.CENTER_SPACE
							+ BPELUtil.calculateLabelWidth(section.fLabel,
									STANDARD_LABEL_WIDTH_SM));
			data.right = new FlatFormAttachment(100, 0);
		}

		data.top = new FlatFormAttachment(0, 0);
		section.fCombo.setLayoutData(data);

		data = new FlatFormData();
		if (isFrom) {
			data.left = new FlatFormAttachment(0, 0);
		} else {
			data.left = new FlatFormAttachment(50,
					IDetailsAreaConstants.CENTER_SPACE);
		}
		data.right = new FlatFormAttachment(section.fCombo,
				-IDetailsAreaConstants.CENTER_SPACE);
		data.top = new FlatFormAttachment(section.fCombo, 0, SWT.CENTER);
		section.fLabel.setLayoutData(data);

		for (IAssignCategory category : section.fAllowed) {
			if (category.getName() != null) {
				section.fCombo.add(category.getName());
			}
		}

		section.fCombo.addSelectionListener(new SelectionListener() {

			public void widgetSelected(SelectionEvent e) {
				int index = section.fCombo.getSelectionIndex();

				updateCategorySelection(section, index, true);

			}

			// TODO: is this correct?
			public void widgetDefaultSelected(SelectionEvent e) {
				widgetSelected(e);
			}
		});

		section.fOuterComposite = createFlatFormComposite(composite);
		data = new FlatFormData();
		data.left = new FlatFormAttachment(section.fLabel, 0, SWT.LEFT);
		data.right = new FlatFormAttachment(section.fCombo, 0, SWT.RIGHT);
		data.top = new FlatFormAttachment(section.fCombo,
				IDetailsAreaConstants.VSPACE);
		data.bottom = new FlatFormAttachment(100, -25);
		section.fOuterComposite.setLayoutData(data);

		// Create the keepSrcElement check box button
		if (isFrom) {
			fIgnoreMissingSourceData = fWidgetFactory.createButton(composite,
					Messages.AssignImplDetails_IgnoreMissingSourceData,
					SWT.CHECK);
			data = new FlatFormData();
			data.right = new FlatFormAttachment(50,
					-IDetailsAreaConstants.HSPACE);
			data.bottom = new FlatFormAttachment(100, 0);
			fIgnoreMissingSourceData.setLayoutData(data);

			fIgnoreMissingSourceDataController = createEditController();
			fIgnoreMissingSourceDataController.setFeature(BPELPackage.eINSTANCE
					.getCopy_IgnoreMissingFromData());
			fIgnoreMissingSourceDataController.setViewIValue(new ButtonIValue(
					fIgnoreMissingSourceData));
			fIgnoreMissingSourceDataController
					.startListeningTo(fIgnoreMissingSourceData);

		} else {

			fKeepSrcElement = fWidgetFactory.createButton(composite,
					Messages.AssignImplDetails_KeepSrcElementName, SWT.CHECK);
			data = new FlatFormData();
			data.right = new FlatFormAttachment(100,
					-IDetailsAreaConstants.HSPACE);
			data.bottom = new FlatFormAttachment(100, 0);
			fKeepSrcElement.setLayoutData(data);

			fKeepSrcElementController = createEditController();
			fKeepSrcElementController.setFeature(BPELPackage.eINSTANCE
					.getCopy_KeepSrcElementName());
			fKeepSrcElementController.setViewIValue(new ButtonIValue(
					fKeepSrcElement));
			fKeepSrcElementController.startListeningTo(fKeepSrcElement);

		}

	}

	/**
	 * @see org.eclipse.ui.views.properties.tabbed.AbstractPropertySection#shouldUseExtraSpace()
	 */
	@Override
	public boolean shouldUseExtraSpace() {
		return true;
	}

	@Override
	protected void createClient(Composite parent) {

		Composite composite = createFlatFormComposite(parent);
		createCopySelectWidgets(composite);
		Composite mainComposite = createFlatFormComposite(composite);
		FlatFormData data = new FlatFormData();
		data.left = new FlatFormAttachment(copySelectComposite,
				IDetailsAreaConstants.HSPACE);
		data.top = new FlatFormAttachment(0, 0);
		data.right = new FlatFormAttachment(100, 0);
		data.bottom = new FlatFormAttachment(100, 0);
		mainComposite.setLayoutData(data);

		createCategorySectionWidgets(mainComposite, fFromSection, true);
		createCategorySectionWidgets(mainComposite, fToSection, false);

	}

	// Total Hack until we have Copy objects in graphical editor
	@Override
	protected void basicSetInput(EObject newInput) {

		saveUserContextToInput();

		super.basicSetInput(newInput);
		adjustCopyRulesList();

		restoreUserContextFromInput();

		if (fToSection.fCurrent != null) {
			fToSection.fCurrent.refresh();
		}
		if (fFromSection.fCurrent != null) {
			fFromSection.fCurrent.refresh();
		}

	}

	/**
	 * Called when the copy rule changes or is created.
	 * 
	 */
	@SuppressWarnings("boxing")
	protected void selectCategoriesForInput() {

		fKeepSrcElementController.setInput(fCurrentCopy);
		fIgnoreMissingSourceDataController.setInput(fCurrentCopy);

		if (fCurrentCopy == null) {
			fToSection.hideCurrent();
			fFromSection.hideCurrent();
			fKeepSrcElement.setEnabled(false);
			fIgnoreMissingSourceData.setEnabled(false);
			return;
		}

		fKeepSrcElement.setEnabled(true);
		fIgnoreMissingSourceData.setEnabled(true);

		// Find the proper copy-from category
		boolean bFound = false;
		for (IAssignCategory category : fFromSection.fAllowed) {
			if (category.isCategoryForModel(fCurrentCopy.getFrom())) {
				updateCategorySelection(fFromSection, category, false);
				bFound = true;
				break;
			}
		}

		/** In case we can't find the appropriate one, just display the first one */
		if (bFound == false || fFromSection.fCurrent == null) {
			updateCategorySelection(fFromSection, 0, false);
		}

		// Find the proper copy-to category
		bFound = false;
		for (IAssignCategory category : fToSection.fAllowed) {
			if (category.isCategoryForModel(fCurrentCopy.getTo())) {
				updateCategorySelection(fToSection, category, false);
				bFound = true;
				break;
			}
		}

		/** In case we can't find the appropriate one, just display the first one */
		if (bFound == false || fToSection.fCurrent == null) {
			updateCategorySelection(fToSection, 0, false);
		}
	}

	/**
	 * @see org.eclipse.bpel.ui.properties.BPELPropertySection#refresh()
	 */
	@Override
	public void refresh() {
		super.refresh();
	}

	/**
	 * @see org.eclipse.bpel.ui.properties.BPELPropertySection#aboutToBeHidden()
	 */
	@Override
	public void aboutToBeHidden() {
		super.aboutToBeHidden();

		if (fToSection.fCurrent != null) {
			fToSection.fCurrent.aboutToBeHidden();
		}
		if (fFromSection.fCurrent != null) {
			fFromSection.fCurrent.aboutToBeHidden();
		}

	}

	/**
	 * @see org.eclipse.bpel.ui.properties.BPELPropertySection#aboutToBeShown()
	 */
	@Override
	public void aboutToBeShown() {
		super.aboutToBeShown();
		if (fToSection.fCurrent != null) {
			fToSection.fCurrent.aboutToBeShown();
		}
		if (fFromSection.fCurrent != null) {
			fFromSection.fCurrent.aboutToBeShown();
		}
	}

	protected void adjustCopyRulesList() {
		Assign assign = getModel();
		EList<Copy> copyList = assign.getCopy();
		int sz = copyList.size();
		int idx = fCopyList.getSelectionIndex();
		String[] items = new String[sz];
		for (int i = 0; i < sz; i++) {
			Copy copy = copyList.get(i);
			items[i] = asText(copy.getFrom()) + " to " + asText(copy.getTo());
		}
		fCopyList.setItems(items);
		if (idx != -1) {
			fCopyList.setSelection(idx);
		}
		fDeleteCopy.setEnabled(sz > 0);
		fToSection.fCombo.setEnabled(sz > 0);
		fFromSection.fCombo.setEnabled(sz > 0);
	}

	private String asText(From from) {
		for (IAssignCategory category : fFromSection.fAllowed) {
			if (category.isCategoryForModel(from)) {
				return category.getName();
			}
		}
		return "?";
	}

	private String asText(To to) {
		for (IAssignCategory category : fToSection.fAllowed) {
			if (category.isCategoryForModel(to)) {
				return category.getName();
			}
		}
		return "?";
	}

	void updateCategorySelection(CategorySection section, int index,
			boolean bVisual) {
		updateCategorySelection(section, section.fAllowed[index], bVisual);
	}

	void updateCategorySelection(CategorySection section,
			IAssignCategory newCurrent, boolean bVisual) {

		/** Hide current */
		section.hideCurrent();

		/** Update current to the one that picked from */
		section.fCurrent = newCurrent;
		section.ensureCategoryCompositeCreated();

		if (bVisual == false) {
			section.updateCombo();
		}

		if (section == fToSection) {
			if (bVisual || fCurrentCopy.getTo() == null) {
				fCurrentCopy.setTo(BPELFactory.eINSTANCE.createTo());
			}
			section.fCurrent.setInput(fCurrentCopy.getTo());

		} else {

			if (bVisual || fCurrentCopy.getFrom() == null) {
				fCurrentCopy.setFrom(BPELFactory.eINSTANCE.createFrom());
			}
			section.fCurrent.setInput(fCurrentCopy.getFrom());
		}

		section.showCurrent();
		section.fCurrent.refresh();
		refreshAdapters();

		// TODO: should the categories only store when a widget change is
		// committed?
		// Cons of that idea:
		// - Changing the category in the combo, but *not* changing anything
		// else,
		// then clicking elsewhere and back, would cause the combo to revert.
		// - The OpaqueAssignCategory doesn't have any widgets..
	}

	/**
	 * @see org.eclipse.bpel.ui.properties.BPELPropertySection#getUserContext()
	 */
	@SuppressWarnings("boxing")
	@Override
	public Object getUserContext() {
		Assign assign = getModel();
		if (assign == null) {
			return -1;
		}
		return assign.getCopy().indexOf(fCurrentCopy);
	}

	/**
	 * @see org.eclipse.bpel.ui.properties.BPELPropertySection#restoreUserContext(java.lang.Object)
	 */
	@SuppressWarnings("boxing")
	@Override
	public void restoreUserContext(Object userContext) {

		int idx = 0;
		if (userContext instanceof Number) {
			Number num = (Number) userContext;
			idx = num.intValue();
		}

		selectCopyInList(idx);
	}

	/**
	 * @see org.eclipse.bpel.ui.properties.BPELPropertySection#gotoMarker(org.eclipse.core.resources.IMarker)
	 */
	@Override
	public void gotoMarker(IMarker marker) {
		// TODO: This code do nothing and sometime causes NPE (from==null), so
		// temporary disabled (Oleg)
		// String uriFragment =
		// marker.getAttribute(IBPELUIConstants.MARKER_ATT_FROM, EMPTY_STRING);
		// EObject from = fModelObject.eResource().getEObject(uriFragment);
		// EObject copy = from.eContainer();
		// currentCopyIndex = ((Assign)getModel()).getCopy().indexOf(copy);
		refresh();
	}

	/*
	 * Select copy, refresh buttons & categories
	 */
	private void selectCopyInList(int index) {
		EList<Copy> list = ((Assign) getModel()).getCopy();
		if (index < 0 || index >= list.size()) {
			fCurrentCopy = null;
			fMoveUp.setEnabled(false);
			fMoveDown.setEnabled(false);
		} else {
			fCurrentCopy = list.get(index);
			fCopyList.select(index);
			fMoveUp.setEnabled(index > 0);
			fMoveDown.setEnabled(index < fCopyList.getItemCount() - 1);
		}
		selectCategoriesForInput();
	}
}
