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

import java.util.Date;

import org.eclipse.core.databinding.BindingProperties;
import org.eclipse.core.databinding.UpdateListStrategy;
import org.eclipse.core.databinding.UpdateValueStrategy;
import org.eclipse.core.databinding.beans.BeanProperties;
import org.eclipse.core.databinding.beans.IBeanValueProperty;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.list.WritableList;
import org.eclipse.core.databinding.observable.map.IObservableMap;
import org.eclipse.core.databinding.observable.set.IObservableSet;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.observable.value.WritableValue;
import org.eclipse.core.databinding.property.value.IValueProperty;
import org.eclipse.jface.action.ActionContributionItem;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.databinding.util.JFaceProperties;
import org.eclipse.jface.databinding.viewers.ObservableSetContentProvider;
import org.eclipse.jface.databinding.viewers.ViewersObservables;
import org.eclipse.jface.viewers.IOpenListener;
import org.eclipse.jface.viewers.OpenEvent;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;

import com.patrikdufresne.jface.databinding.preference.PreferenceStoreProperties;
import com.patrikdufresne.jface.databinding.viewers.ColumnSupport;
import com.patrikdufresne.jface.databinding.viewers.PropertyViewerComparator;
import com.patrikdufresne.jface.viewers.ColumnViewerPreferences;
import com.patrikdufresne.planner.Planner;
import com.patrikdufresne.planner.databinding.ObservableMapPlannerLabelProvider;
import com.patrikdufresne.planner.databinding.PlannerProperties;
import com.patrikdufresne.planner.databinding.PlannerViewerUpdater;
import com.patrikdufresne.planner.viewer.PlannerViewer;
import com.patrikdufresne.printing.PrintAction;
import com.patrikdufresne.util.Localized;
import com.planimod.core.AbstractCalendarEvent;
import com.planimod.core.NonAvailability;
import com.planimod.core.PlanimodManagers;
import com.planimod.core.comparators.NonAvailabilityComparators;
import com.planimod.ui.databinding.ComputedObservableTimeRange;
import com.planimod.ui.databinding.DateColorControlStyleSupport;
import com.planimod.ui.databinding.DateFormatRangeValueProperty;
import com.planimod.ui.databinding.ListObservableValue;
import com.planimod.ui.preference.PlanimodPreferences;
import com.planimod.ui.printing.PrintActionMenuManager;
import com.planimod.ui.viewers.EmployeeViewerUtils;
import com.planimod.ui.viewers.planner.PlanimodPlannerLook;
import com.planimod.ui.views.AbstractViewPart;
import com.planimod.ui.views.ChangeDateAction;
import com.planimod.ui.views.RemoveAction;

/**
 * This view part display employee's non-availabilities.
 * 
 * @author Patrik Dufresne
 * 
 */
public class NonAvailabilitiesViewPart extends AbstractViewPart {
	/**
	 * The part id.
	 */
	public static final String ID = "comp.planimod.ui.views.nonavailabilities.NonAvailabilitiesViewPart"; //$NON-NLS-1$

	/**
	 * Preference key to access the column's width.
	 */
	private static final String PREFERENCE_COLUMNS = "NonAvailabilitiesViewPart.columns"; //$NON-NLS-1$

	/**
	 * Key used to store the sash weight.
	 */
	private static final String PREFERENCE_SASH_WEIGHTS = "NonAvailabilitiesViewPart.sash.weights"; //$NON-NLS-1$

	/**
	 * Default sash weights.
	 */
	private static final int[] PREFERENCE_SASH_DEFAULT_WEIGHTS = new int[] {
			70, 30 };

	private AddNonAvailabilityAction addNonAvailability;
	/**
	 * Action used to edit a non-availability event.
	 */
	EditNonAvailabilityAction editNonAvailability;
	/**
	 * Used for localization.
	 */
	private Localized localized = Localized
			.load(NonAvailabilitiesViewPart.class);
	private TableViewer longEventViewer;

	/**
	 * Next action.
	 */
	private ChangeDateAction nextAction;

	/**
	 * Preview action.
	 */
	private NonAvailabilityPrintAction previewAllAction;

	/**
	 * Previous action.
	 */
	private ChangeDateAction previousAction;

	/**
	 * Printing action.
	 */
	private NonAvailabilityPrintAction printAllAction;
	/**
	 * Remove action.
	 */
	private RemoveAction removeAction;
	/**
	 * Today action.
	 */
	private ChangeDateAction todayAction;

	private PlannerViewer viewer;

	/**
	 * Create a new view part.
	 */
	public NonAvailabilitiesViewPart() {
		super(ID);
		setTitle(this.localized.get("NonAvailabilitiesViewPart.title")); //$NON-NLS-1$
	}

	/**
	 * This implementation create the tool-bar action.
	 */
	@Override
	public void activate(Composite parent) {

		SashForm sash = createSashForm(parent, SWT.HORIZONTAL | SWT.SMOOTH,
				PlanimodPreferences.getPreferenceStore(),
				PREFERENCE_SASH_WEIGHTS, PREFERENCE_SASH_DEFAULT_WEIGHTS);
		sash.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

		/*
		 * Create planner viewer on the left
		 */
		this.viewer = createPlannerViewer(sash);
		/*
		 * Create table viewer on the right.
		 */
		this.longEventViewer = new TableViewer(sash);
		this.longEventViewer.getControl().setLayoutData(
				new GridData(SWT.FILL, SWT.FILL, true, true));
		this.longEventViewer.getTable().setHeaderVisible(true);

		// A open listener to edit the event
		this.longEventViewer.addOpenListener(new IOpenListener() {
			@Override
			public void open(OpenEvent event) {
				// JFace data binding, this action is already updated with the
				// current selection. We only need to run the action.
				NonAvailabilitiesViewPart.this.editNonAvailability.run();
			}
		});

		// Populate the tool bar.
		fillToolBar();

		// Create binding
		bind();

	}

	/**
	 * Binds the planner date selection to a shared state.
	 */
	@Override
	protected void bindValues() {

		/*
		 * Bind current selection to intermediate variable since there is two
		 * selection provider.
		 */
		IObservableValue singleSelection = new WritableValue();
		getDbc().bindValue(
				ViewersObservables.observeSingleSelection(this.viewer),
				singleSelection, null,
				new UpdateValueStrategy(UpdateValueStrategy.POLICY_NEVER));
		getDbc().bindValue(
				ViewersObservables.observeSingleSelection(this.longEventViewer),
				singleSelection, null,
				new UpdateValueStrategy(UpdateValueStrategy.POLICY_NEVER));
		IObservableList selection = new WritableList();
		getDbc().bindList(
				ViewersObservables.observeMultiSelection(this.viewer),
				selection, null,
				new UpdateListStrategy(UpdateListStrategy.POLICY_NEVER));
		getDbc().bindList(
				ViewersObservables.observeMultiSelection(this.longEventViewer),
				selection, null,
				new UpdateListStrategy(UpdateListStrategy.POLICY_NEVER));

		/*
		 * Get the date state.
		 */
		final WritableValue date = new WritableValue(null, Date.class);
		getDbc().bindValue(
				date,
				PreferenceStoreProperties.value(PlanimodPreferences.DATE,
						String.class).observe(
						PlanimodPreferences.getPreferenceStore()));
		if (date.getValue() == null) {
			date.setValue(new Date());
		}

		/*
		 * Bind date with planner date selection.
		 */
		Planner planner = (Planner) this.viewer.getControl();
		getDbc().bindValue(PlannerProperties.dateSelection().observe(planner),
				date);
		IObservableValue start = PlannerProperties.startDateValue().observe(
				planner);
		IObservableValue end = PlannerProperties.endDateValue()
				.observe(planner);

		IObservableValue timeRange = new ComputedObservableTimeRange(start, end);

		/*
		 * Bind the planner viewer (visible availabilities)
		 */
		IObservableSet shortNonAvailabilities = new NonAvailabilitiesComputedSet(
				getSite().getService(PlanimodManagers.class), timeRange, true);
		IObservableMap nonAvailabilityLabels = BindingProperties
				.convertedValue(EmployeeViewerUtils.fullnameConverter())
				.observeDetail(
						BeanProperties.value(NonAvailability.class,
								NonAvailability.EMPLOYEE).observeDetail(
								shortNonAvailabilities));
		IObservableMap nonAvailabilityStart = BeanProperties.value(
				AbstractCalendarEvent.class, AbstractCalendarEvent.START_DATE)
				.observeDetail(shortNonAvailabilities);
		IObservableMap nonAvailabilityEnd = BeanProperties.value(
				AbstractCalendarEvent.class, AbstractCalendarEvent.END_DATE)
				.observeDetail(shortNonAvailabilities);

		this.viewer.setContentProvider(new ObservableSetContentProvider(
				new PlannerViewerUpdater(this.viewer)));
		this.viewer.setComparator(new PropertyViewerComparator(
				NonAvailabilityComparators.byDateAndEmployeeName()));
		this.viewer
				.setLabelProvider(new ObservableMapPlannerLabelProvider(
						nonAvailabilityLabels, nonAvailabilityStart,
						nonAvailabilityEnd));
		this.viewer.setInput(shortNonAvailabilities);

		DateColorControlStyleSupport.create(this.viewer.getPlanner(), date);

		/*
		 * Bind table viewer (hidden non-availabilities)
		 */
		IObservableSet longNonAvailabilities = new NonAvailabilitiesComputedSet(
				getSite().getService(PlanimodManagers.class), timeRange, false);
		this.longEventViewer
				.setContentProvider(new ObservableSetContentProvider());
		createColumns(this.longEventViewer, longNonAvailabilities);
		this.longEventViewer.setInput(longNonAvailabilities);

		/*
		 * Bind actions
		 */
		this.previousAction.setDate(date);
		this.todayAction.setDate(date);
		this.nextAction.setDate(date);

		getDbc().bindValue(
				JFaceProperties.value(NonAvailabilityPrintAction.class,
						NonAvailabilityPrintAction.DATE,
						NonAvailabilityPrintAction.DATE).observe(
						this.printAllAction), date);
		getDbc().bindValue(
				JFaceProperties.value(NonAvailabilityPrintAction.class,
						NonAvailabilityPrintAction.DATE,
						NonAvailabilityPrintAction.DATE).observe(
						this.previewAllAction), date);
		getDbc().bindValue(
				JFaceProperties.value(AddNonAvailabilityAction.class,
						AddNonAvailabilityAction.DATE,
						AddNonAvailabilityAction.DATE).observe(
						this.addNonAvailability), date);
		getDbc().bindValue(
				JFaceProperties.value(EditNonAvailabilityAction.class,
						EditNonAvailabilityAction.NON_AVAILABILITY,
						EditNonAvailabilityAction.NON_AVAILABILITY).observe(
						this.editNonAvailability), singleSelection);
		getDbc().bindValue(
				JFaceProperties.value(RemoveAction.class, RemoveAction.OBJECTS,
						RemoveAction.OBJECTS).observe(this.removeAction),
				new ListObservableValue(selection, AbstractCalendarEvent.class));

	}

	protected void createColumns(TableViewer viewer,
			IObservableSet knownElements) {

		// Date range
		IValueProperty dateRangeLabel = DateFormatRangeValueProperty
				.createDateShort();
		IBeanValueProperty startDateProperty = BeanProperties.value(
				AbstractCalendarEvent.class, AbstractCalendarEvent.START_DATE,
				Date.class);
		ColumnSupport
				.create(viewer,
						this.localized
								.get("NonAvailabilitiesViewPart.columns.dateRange"), //$NON-NLS-1$
						knownElements, dateRangeLabel).setWidth(200)
				.addPropertySorting(startDateProperty);

		// Employee's name
		IValueProperty employeeLabel = BeanProperties.value(
				NonAvailability.class, NonAvailability.EMPLOYEE).value(
				BindingProperties.convertedValue(EmployeeViewerUtils
						.fullnameConverter()));
		ColumnSupport
				.create(viewer,
						this.localized
								.get("NonAvailabilitiesViewPart.columns.employee"), //$NON-NLS-1$
						knownElements, employeeLabel).setWidth(150)
				.addPropertySorting().activateSorting();

		// Summary
		IValueProperty summaryProperty = BeanProperties.value(
				AbstractCalendarEvent.class, AbstractCalendarEvent.SUMMARY);
		ColumnSupport
				.create(viewer,
						this.localized
								.get("NonAvailabilitiesViewPart.columns.summary"), //$NON-NLS-1$
						knownElements, summaryProperty).addPropertySorting();

		ColumnViewerPreferences.create(viewer,
				PlanimodPreferences.getPreferenceStore(), PREFERENCE_COLUMNS);

	}

	/**
	 * Create a new planner viewer.
	 */
	protected PlannerViewer createPlannerViewer(Composite parent) {

		PlannerViewer viewer = new PlannerViewer(parent);

		// Sets the planner look
		viewer.getPlanner().setLook(new PlanimodPlannerLook());

		// A open listener to edit the event
		viewer.addOpenListener(new IOpenListener() {
			@Override
			public void open(OpenEvent event) {
				// JFace data binding, this action is already updated with the
				// current selection. We only need to run the action.
				NonAvailabilitiesViewPart.this.editNonAvailability.run();
			}
		});

		return viewer;

	}

	@Override
	public void deactivate() {
		this.addNonAvailability = null;
		this.editNonAvailability = null;
		this.longEventViewer = null;
		this.nextAction = null;
		this.previewAllAction = null;
		this.previousAction = null;
		this.printAllAction = null;
		this.todayAction = null;
		this.removeAction = null;
		this.viewer = null;
		super.deactivate();
	}

	@Override
	public void dispose() {
		this.localized = null;
		super.dispose();
	}

	/**
	 * Populate the tool bar.
	 */
	void fillToolBar() {

		IToolBarManager toolbar = getSite().getToolBarManager();
		toolbar.removeAll();

		// Previous
		toolbar.add(this.previousAction = ChangeDateAction.previousAction());
		this.previousAction.setUnit(ChangeDateAction.UNIT_WEEK);

		// Today
		toolbar.add(this.todayAction = ChangeDateAction.todayAction());

		// Next
		toolbar.add(this.nextAction = ChangeDateAction.nextAction());
		this.nextAction.setUnit(ChangeDateAction.UNIT_WEEK);

		// Separator
		toolbar.add(new Separator());

		// Add
		ActionContributionItem aci;
		toolbar.add(aci = new ActionContributionItem(
				this.addNonAvailability = new AddNonAvailabilityAction(
						getSite().getService(PlanimodManagers.class), getSite())));
		aci.setMode(ActionContributionItem.MODE_FORCE_TEXT);

		// Edit
		toolbar.add(aci = new ActionContributionItem(
				this.editNonAvailability = new EditNonAvailabilityAction(
						getSite().getService(PlanimodManagers.class), getSite())));
		aci.setMode(ActionContributionItem.MODE_FORCE_TEXT);

		// Remove
		toolbar.add(aci = new ActionContributionItem(
				this.removeAction = new RemoveAction(getSite().getService(
						PlanimodManagers.class), getSite())));
		this.removeAction
				.setConfirmationMessage(this.localized
						.get("NonAvailabilitiesViewPart.removeAction.confirmationMessage")); //$NON-NLS-1$
		this.removeAction
				.setConfirmationShortDetail(this.localized
						.get("NonAvailabilitiesViewPart.removeAction.confirmationShortDetail")); //$NON-NLS-1$
		aci.setMode(ActionContributionItem.MODE_FORCE_TEXT);

		// Separator
		toolbar.add(new Separator());

		// Print menu
		PrintActionMenuManager printMenu = new PrintActionMenuManager(
				"NonAvailabilitiesViewPart.print"); //$NON-NLS-1$
		printMenu.add(this.printAllAction = new NonAvailabilityPrintAction(
				getSite().getService(PlanimodManagers.class), getSite(),
				PrintAction.PRINT_ACTION, getSite().getMainWindow()));
		printMenu.add(this.previewAllAction = new NonAvailabilityPrintAction(
				getSite().getService(PlanimodManagers.class), getSite(),
				PrintAction.PREVIEW_ACTION, getSite().getMainWindow()));
		toolbar.add(printMenu);

	}

}
