/*
 * Copyright (c) 2011, Patrik Dufresne. All rights reserved.
 * Patrik Dufresne PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package com.planimod.ui.views.production;

import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.core.databinding.BindingProperties;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.ObservablesManager;
import org.eclipse.core.databinding.beans.BeanProperties;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.set.IObservableSet;
import org.eclipse.core.databinding.observable.set.WritableSet;
import org.eclipse.core.databinding.observable.value.WritableValue;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.ActionContributionItem;
import org.eclipse.jface.databinding.swt.SWTObservables;
import org.eclipse.jface.databinding.swt.WidgetProperties;
import org.eclipse.jface.databinding.util.JFaceProperties;
import org.eclipse.jface.databinding.viewers.ObservableMapLabelProvider;
import org.eclipse.jface.databinding.viewers.ObservableSetContentProvider;
import org.eclipse.jface.databinding.viewers.ViewerProperties;
import org.eclipse.jface.dialogs.TitleAreaDialog;
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.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;

import com.patrikdufresne.jface.databinding.viewers.PropertyViewerComparator;
import com.patrikdufresne.jface.databinding.viewers.TextProposalViewerUpdater;
import com.patrikdufresne.jface.viewers.TextProposalViewer;
import com.patrikdufresne.managers.ManagedObject;
import com.patrikdufresne.util.DateFormatRegistry;
import com.patrikdufresne.util.Localized;
import com.planimod.core.AbstractCalendarEvent;
import com.planimod.core.PlanimodManagers;
import com.planimod.core.Product;
import com.planimod.core.ProductionEvent;
import com.planimod.core.Shift;
import com.planimod.core.Team;
import com.planimod.core.comparators.ProductionEventComparators;
import com.planimod.ui.databinding.Converters;
import com.planimod.ui.databinding.DateFormatRangeValueProperty;
import com.planimod.ui.databinding.ListObservableValue;
import com.planimod.ui.databinding.ProductLabelValueProperty;
import com.planimod.ui.databinding.manager.PlanimodObservables;
import com.planimod.ui.theme.Resources;

/**
 * Dialog to create or edit a production event.
 * 
 * @author Patrik Dufresne
 * 
 */
public class EditProductionEventDialog extends TitleAreaDialog {
	/**
	 * This action remove the selected products.
	 * 
	 * @author Patrik Dufresne
	 */
	public class RemoveProductAction extends Action {
		/**
		 * Property name of this action objects (value <code>"objects"</code>).
		 * The objects property define the entities to be deleted.
		 */
		public static final String OBJECTS = "objects"; //$NON-NLS-1$
		/**
		 * The list of object to be removed.
		 */
		private List<ManagedObject> objects;

		/**
		 * Create a new remove action.
		 */
		public RemoveProductAction() {
			super(localized.get("ProductionEventDialog.removeAction.text"), //$NON-NLS-1$
					Resources.getImageDescriptor(Resources.ICON_LIST_REMOVE_16));
			setToolTipText(localized
					.get("ProductionEventDialog.removeAction.toolTipText")); //$NON-NLS-1$
			setEnabled(canRun());
		}

		/**
		 * Check if this action can be run.
		 * 
		 * @return
		 */
		protected boolean canRun() {
			return this.objects != null && this.objects.size() > 0;
		}

		public List<ManagedObject> getObjects() {
			return this.objects;
		}

		@Override
		public void run() {

			if (!canRun()) {
				return;
			}

			events.removeAll(objects);

			// Clear the selection
			productionEventsViewer.setSelection(new StructuredSelection());
		}

		public void setObjects(List<ManagedObject> objects) {
			this.objects = objects;
			setEnabled(canRun());
		}

	}

	private static final String DOT = "."; //$NON-NLS-1$

	/**
	 * The label used to display the event time range.
	 */
	private Label dateLabel;

	private DataBindingContext dbc;

	/**
	 * Observable list of event.
	 */
	WritableSet events;

	/**
	 * Used for localization
	 */
	protected Localized localized = Localized
			.load(EditProductionEventDialog.class);

	/**
	 * The managers to use.
	 */
	private PlanimodManagers managers;

	private ObservablesManager om;

	TableViewer productionEventsViewer;
	/**
	 * Widget to select the product.
	 */
	private TextProposalViewer productViewer;
	// Create an action to remove the selection
	private Action removeAction;

	private WritableValue shift;

	/**
	 * The label used to display the team's name.
	 */
	private Label teamLabel;

	/**
	 * Create a new dialog.
	 * 
	 * @param parentShell
	 *            the parent shell, or <code>null</code> to create a top-level
	 *            shell
	 */
	public EditProductionEventDialog(Shell parentShell,
			PlanimodManagers managers) {
		super(parentShell);
		if (managers == null) {
			throw new NullPointerException();
		}
		this.managers = managers;
		this.shift = new WritableValue(null, Shift.class);
		this.events = new WritableSet();
	}

	/**
	 * Sets the binding between the values and the widgets.
	 */
	protected void bindValues() {

		/*
		 * Bind the team label.
		 */
		this.dbc.bindValue(
				WidgetProperties.text().observe(this.teamLabel),
				BeanProperties
						.value(Shift.class, Shift.TEAM + DOT + Team.NAME)
						.value(BindingProperties.convertedValue(Converters
								.removeFrontNumber()))
						.observeDetail(this.shift));

		/*
		 * Bind date to date label
		 */
		this.dbc.bindValue(SWTObservables.observeText(this.dateLabel),
				new DateFormatRangeValueProperty(DateFormatRegistry.TIME
						| DateFormatRegistry.LONG).observeDetail(this.shift));

		/*
		 * Bind product viewer
		 */
		IObservableSet products = PlanimodObservables
				.listProduct(this.managers);
		// Setup the viewer to display the product list
		this.productViewer.setContentProvider(new ObservableSetContentProvider(
				new TextProposalViewerUpdater(this.productViewer)));
		this.productViewer.setLabelProvider(new ObservableMapLabelProvider(
				new ProductLabelValueProperty().observeDetail(products)));
		this.productViewer.setInput(products);

		/*
		 * Bind events viewer
		 */
		this.productionEventsViewer
				.setContentProvider(new ObservableSetContentProvider());
		this.productionEventsViewer
				.setLabelProvider(new ObservableMapLabelProvider(BeanProperties
						.value(ProductionEvent.class, ProductionEvent.PRODUCT)
						.value(new ProductLabelValueProperty())
						.observeDetail(this.events)));
		this.productionEventsViewer.setComparator(new PropertyViewerComparator(
				ProductionEventComparators.byRefIdFamilyName()));
		this.productionEventsViewer.setInput(this.events);

		/*
		 * Bind remove action
		 */
		IObservableList selections = ViewerProperties.multipleSelection()
				.observe(this.productionEventsViewer);
		this.dbc.bindValue(
				JFaceProperties.value(RemoveProductAction.class,
						RemoveProductAction.OBJECTS,
						RemoveProductAction.OBJECTS).observe(this.removeAction),
				new ListObservableValue(selections, AbstractCalendarEvent.class));

	}

	/**
	 * This implementation free the allocated resources.
	 */
	@Override
	public boolean close() {
		if (!super.close()) {
			return false;
		}
		if (this.om != null) {
			this.om.dispose();
			this.om = null;
		}
		if (this.dbc != null) {
			this.dbc.dispose();
			this.dbc = null;
		}
		return true;
	}

	/**
	 * This implementation sets the binding.
	 */
	@Override
	protected Control createContents(Composite parent) {
		Control control = super.createContents(parent);
		/*
		 * Bind widgets
		 */
		this.dbc = new DataBindingContext();
		this.om = new ObservablesManager();
		this.om.runAndCollect(new Runnable() {
			@Override
			public void run() {
				bindValues();
			}
		});
		this.om.addObservablesFromContext(this.dbc, true, true);
		return control;
	}

	/**
	 * This implementation create required widgets to edit a calendar event.
	 * 
	 * @param parent
	 * @return
	 */
	@Override
	protected Control createDialogArea(Composite parent) {
		Composite comp = (Composite) super.createDialogArea(parent);

		// Setup the title area
		getShell().setText(this.localized.get("ProductionEventDialog.title")); //$NON-NLS-1$
		setTitle(this.localized.get("ProductionEventDialog.titleArea.title")); //$NON-NLS-1$
		setMessage(this.localized
				.get("ProductionEventDialog.titleArea.message")); //$NON-NLS-1$
		setTitleImage(Resources.getImage(Resources.DLG_EDIT_TITLE_IMAGE));

		Composite composite = new Composite(comp, SWT.NONE);
		composite.setLayout(new GridLayout(1, false));
		composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

		/*
		 * Setup the date label
		 */
		this.teamLabel = new Label(composite, SWT.NONE);
		this.teamLabel.setLayoutData(new GridData(SWT.CENTER, SWT.FILL, true,
				false));
		this.teamLabel.setFont(Resources.getWeekTitleFont());

		this.dateLabel = new Label(composite, SWT.NONE);
		this.dateLabel.setLayoutData(new GridData(SWT.CENTER, SWT.FILL, true,
				false));
		this.dateLabel.setFont(Resources.getWeekTitleFont());

		Label label = new Label(composite, SWT.NONE);
		label.setText(this.localized.get("ProductionEventDialog.label.product")); //$NON-NLS-1$

		// Create the widget to add production event
		Control control = createProductViewer(composite);
		control.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));

		/*
		 * Create the events viewer
		 */
		this.productionEventsViewer = new TableViewer(composite, SWT.MULTI
				| SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER | SWT.FULL_SELECTION);
		GridData data = new GridData(SWT.FILL, SWT.FILL, true, true);
		data.heightHint = 230;
		this.productionEventsViewer.getControl().setLayoutData(data);
		// Add listener to delete the selected elements when used press Delete
		// or Backspace.
		this.productionEventsViewer.getTable().addListener(SWT.KeyUp,
				new Listener() {
					@Override
					public void handleEvent(Event event) {
						if (event.character == SWT.DEL
								|| event.character == SWT.BS) {
							removeAction.run();
						}
					}
				});

		// Create an action to remove the selection
		ActionContributionItem aci = new ActionContributionItem(
				this.removeAction = new RemoveProductAction());
		aci.setMode(ActionContributionItem.MODE_FORCE_TEXT);
		aci.fill(composite);

		// Build the separator line
		Label separator = new Label(comp, SWT.HORIZONTAL | SWT.SEPARATOR);
		separator.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

		return comp;
	}

	/**
	 * Create the widget to select a product and eventually create a production
	 * event.
	 * 
	 * @param compEvent
	 *            the composite parent
	 * @return the control
	 */
	private Control createProductViewer(Composite compEvent) {
		// Create a text proposal viewer
		this.productViewer = new TextProposalViewer(compEvent);

		// Add a listener on the selection to add it to the list
		this.productViewer
				.addSelectionChangedListener(new ISelectionChangedListener() {
					@Override
					public void selectionChanged(SelectionChangedEvent event) {
						Object element = ((IStructuredSelection) event
								.getSelection()).getFirstElement();
						if (element instanceof Product) {
							handleAddEvent((Product) element);
						}
						// Clear the selection
						event.getSelectionProvider().setSelection(
								new StructuredSelection());
					}
				});

		return this.productViewer.getControl();
	}

	/**
	 * Returns the edited product list.
	 * 
	 * @return the products
	 */
	public Collection<ProductionEvent> getProductionEvents() {
		Set<ProductionEvent> eventSet = new HashSet<ProductionEvent>();
		for (Object obj : this.events) {
			ProductionEvent event = (ProductionEvent) obj;
			eventSet.add(event);
		}
		return eventSet;
	}

	/**
	 * Returns the current start date.
	 * 
	 * @return
	 */
	public Shift getShift() {
		return (Shift) this.shift.getValue();
	}

	/**
	 * Add a production event to the list.
	 * 
	 * @param element
	 */
	protected void handleAddEvent(Product product) {
		// Add the product to the list
		final ProductionEvent event = new ProductionEvent();
		event.setProduct(product);
		this.events.add(event);

		Display.getDefault().asyncExec(new Runnable() {
			@Override
			public void run() {
				if (productionEventsViewer != null) {
					productionEventsViewer
							.setSelection(new StructuredSelection(event));
					productionEventsViewer.reveal(event);
				}
			}
		});

	}

	/**
	 * This implementation return true.
	 */
	@Override
	protected boolean isResizable() {
		return true;
	}

	/**
	 * Sets the product list to be edited.
	 * 
	 * @param products
	 *            the events
	 */
	public void setProductionEvents(Collection<ProductionEvent> products) {
		this.events.clear();
		if (products != null) {
			this.events.addAll(products);
		}
	}

	/**
	 * Sets the start date.
	 * 
	 * @param start
	 */
	public void setShift(Shift shift) {
		this.shift.setValue(shift);
	}

}
