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

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.InvocationTargetException;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialogWithToggle;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.window.IShellProvider;
import org.eclipse.swt.widgets.Display;

import com.patrikdufresne.managers.ManagerException;
import com.patrikdufresne.util.Localized;
import com.planimod.core.Employee;
import com.planimod.core.GeneratePlanifContext;
import com.planimod.core.GeneratePlanifException;
import com.planimod.core.IGeneratePlanifProgressMonitor;
import com.planimod.core.Task;
import com.planimod.core.Team;
import com.planimod.ui.MessageUtils;
import com.planimod.ui.databinding.Converters;
import com.planimod.ui.theme.Resources;
import com.planimod.ui.viewers.EmployeeViewerUtils;

/**
 * This action is used to generate the planif. To run this action, user should
 * sets the planif and week properties.
 * 
 * @author Patrik Dufresne
 * 
 */
public class GeneratePlanifAction extends Action {

	/**
	 * Private implementation of the generate planif progress monitor. This
	 * implementation adapt a {@link IProgressMonitor}.
	 * 
	 * @author Patrik Dufresne
	 * 
	 */
	class Monitor implements IGeneratePlanifProgressMonitor {

		boolean callbackContinue = false;

		/**
		 * The progress monitor being adapted
		 */
		private IProgressMonitor monitor;

		/**
		 * Create a new monitor to adapt a {@link IProgressMonitor}
		 * 
		 * @param monitor
		 *            the monitor to be adapted.
		 */
		public Monitor(IProgressMonitor monitor) {
			this.monitor = monitor;
		}

		/**
		 * This implementation convert the begin search into a begin task for
		 * the progress monitor.
		 */
		@Override
		public void beginSearch(int nbStep) {
			this.monitor.beginTask(GeneratePlanifAction.this.localized
					.get("GeneratePlanifAction.monitor.beginTask"), //$NON-NLS-1$
					nbStep);
		}

		/**
		 * This implementation display a dialog box to the user asking to
		 * continue the opperation or not.
		 */
		@Override
		public boolean callbackContinue() {
			Display.getDefault().syncExec(new Runnable() {
				@Override
				public void run() {
					Monitor.this.callbackContinue = handleContinue();
				}
			});
			return this.callbackContinue;
		}

		/**
		 * This operation call done.
		 */
		@Override
		public void done() {
			this.monitor.done();
		}

		/**
		 * True if the search should be stopped.
		 */
		@Override
		public boolean isCanceled() {
			return this.monitor.isCanceled();

		}

		@Override
		public void setStep(int step) {
			String name = ""; //$NON-NLS-1$
			switch (step) {
			case STEP_CREATE_VARIABLES:
				name = GeneratePlanifAction.this.localized
						.get("GeneratePlanifAction.monitor.createVariables"); //$NON-NLS-1$
				break;
			case STEP_MAXIMIZE_ASSIGNED_TASK:
				name = GeneratePlanifAction.this.localized
						.get("GeneratePlanifAction.monitor.maxAssignedTask"); //$NON-NLS-1$
				break;
			case STEP_MINIMIZE_EMPLOYEE:
				name = GeneratePlanifAction.this.localized
						.get("GeneratePlanifAction.monitor.minEmployee"); //$NON-NLS-1$
				break;
			case STEP_MAXIMIZE_SENIOR:
				name = GeneratePlanifAction.this.localized
						.get("GeneratePlanifAction.monitor.maxSenior"); //$NON-NLS-1$
				break;

			case STEP_MAXIMIZE_PREFERENCES:
				name = GeneratePlanifAction.this.localized
						.get("GeneratePlanifAction.monitor.maxPreferences"); //$NON-NLS-1$
				break;
			}
			this.monitor.subTask(name);
		}

		/**
		 * This implementation call the adapted class.
		 */
		@Override
		public void worked(int work) {
			this.monitor.worked(work);
		}

	}

	/**
	 * The runnable context to display the action progress.
	 */
	private IRunnableContext context;

	/**
	 * Listen to dirty field
	 */
	private PropertyChangeListener listener = new PropertyChangeListener() {
		@Override
		public void propertyChange(PropertyChangeEvent evt) {
			if (GeneratePlanifContext.DIRTY.equals(evt.getPropertyName())) {
				setEnabled(canRun());
			}
		}
	};

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

	/**
	 * The context used to generate a planif.
	 */
	GeneratePlanifContext planifContext;

	/**
	 * The shell provider to display message dialog.
	 */
	private IShellProvider shell;

	/**
	 * Create a new action.
	 * 
	 * @param context
	 *            the generate planif context to be used.
	 */
	public GeneratePlanifAction(GeneratePlanifContext planifContext,
			IShellProvider shell, IRunnableContext context) {
		super();
		if (planifContext == null) {
			throw new NullPointerException("planifContext"); //$NON-NLS-1$
		}
		if (context == null || shell == null) {
			throw new NullPointerException();
		}
		this.shell = shell;
		this.planifContext = planifContext;
		this.planifContext.addPropertyChangeListener(this.listener);

		this.context = context;

		setText(this.localized.get("GeneratePlanifAction.text")); //$NON-NLS-1$
		setToolTipText(this.localized.get("GeneratePlanifAction.tooltipText")); //$NON-NLS-1$
		setImageDescriptor(Resources
				.getImageDescriptor(Resources.ICON_GENERATE_PLANIF_16));
		setEnabled(canRun());
	}

	/**
	 * Returns True if the action can be run.
	 * 
	 * @return
	 */
	protected boolean canRun() {
		return this.planifContext != null && this.planifContext.isDirty();
	}

	/**
	 * Should be called to dispose this action
	 */
	public void dispose() {
		if (this.planifContext != null) {
			this.planifContext.removePropertyChangeListener(this.listener);
			this.planifContext = null;
		}
	}

	private <T extends Throwable> T findException(Throwable e, Class<T> cls) {
		if (e.getCause() != null) {
			if (cls.isAssignableFrom(e.getCause().getClass())) {
				return (T) e.getCause();
			}
			return findException(e.getCause(), cls);
		}
		return null;
	}

	/**
	 * Return the current generate planif context for this action.
	 * 
	 * @return
	 */
	public GeneratePlanifContext getGeneratePlanifContext() {
		return this.planifContext;
	}

	protected boolean handleContinue() {
		String message = this.localized
				.get("GeneratePlanifAction.callback.continue"); //$NON-NLS-1$
		String shortDetail = this.localized
				.get("GeneratePlanifAction.callback.continue.taskNotAllAssigned"); //$NON-NLS-1$
		MessageDialogWithToggle dlg = MessageUtils.openDetailYesNoQuestion(
				this.shell.getShell(), message, shortDetail);
		return dlg.getReturnCode() == IDialogConstants.YES_ID;
	}

	private void handleException(InvocationTargetException e) {

		final GeneratePlanifException e1 = findException(e,
				GeneratePlanifException.class);
		if (e1 != null) {
			Display.getDefault().asyncExec(new Runnable() {
				@Override
				public void run() {
					handleGeneratePlanifException(e1);
				}
			});
			return;
		}

		final ManagerException e2 = findException(e, ManagerException.class);
		if (e2 != null) {
			Display.getDefault().asyncExec(new Runnable() {
				@Override
				public void run() {
					handleManagerException(e2);
				}
			});
		}
	}

	/**
	 * Display an error message for the given exception;
	 * 
	 * @param e
	 */
	protected void handleGeneratePlanifException(GeneratePlanifException e) {

		// Compute the employee name
		Employee employee = e.getEmployee();
		String employeeName = e.getEmployee() != null ? EmployeeViewerUtils
				.getName(employee) : this.localized
				.get("GeneratePlanifAction.unknownEmployee"); //$NON-NLS-1$

		// Compute the task name
		// TODO Create a utility class to generate a description for the task.
		Task task = e.getTask();
		String taskName = task != null ? task.getPosition().getName()
				: this.localized.get("GeneratePlanifAction.unknownTask"); //$NON-NLS-1$

		Team team = e.getTeam();
		String teamName = team != null ? Converters.removeFrontNumber()
				.convert(team.getName()).toString() : this.localized
				.get("GeneratePlanifAction.unknownTeam"); //$NON-NLS-1$

		// Compute the reason description
		int reason = e.getReason();
		String shortDetail = null;
		switch (reason) {
		case GeneratePlanifException.EMPLOYEE_LOCKED_UNAVAIL_OR_UNQUALIFY:
			shortDetail = this.localized
					.format("GeneratePlanifAction.exception.employeeLockedUnavailOrUnqualify", //$NON-NLS-1$
							employeeName, taskName);
			// TODO Provide a default action to fix the problem.
			break;
		case GeneratePlanifException.EMPLOYEE_PREFERRED_SENIORITY:
			shortDetail = this.localized
					.format("GeneratePlanifAction.exception.employeePreferredSeniority", //$NON-NLS-1$
							employeeName, teamName);
			break;
		default:
			shortDetail = this.localized
					.get("GeneratePlanifAction.exception.unknownReason"); //$NON-NLS-1$
		}

		/*
		 * Display the message dialog.
		 */
		MessageUtils.openDetailWarning(this.shell.getShell(),
				this.localized.get("GeneratePlanifAction.generatePlanifError"), //$NON-NLS-1$
				shortDetail);

	}

	/**
	 * Display an error message for the given exception.
	 * 
	 * @param e
	 */
	protected void handleManagerException(ManagerException e) {
		String message = this.localized
				.get("GeneratePlanifAction.generatePlanifError"); //$NON-NLS-1$
		String shotrDetail = this.localized
				.get("GeneratePlanifAction.exception.unknownReason"); //$NON-NLS-1$
		MessageUtils
				.openWarning(this.shell.getShell(), message, shotrDetail, e);
	}

	/**
	 * This implementation run the generate planif in a runnable context.
	 */
	@Override
	public void run() {
		if (this.context == null) {
			return;
		}

		try {
			this.context.run(true, true, new IRunnableWithProgress() {
				@Override
				public void run(IProgressMonitor monitor)
						throws InvocationTargetException, InterruptedException {

					try {
						GeneratePlanifAction.this.planifContext
								.searchSolution(new Monitor(monitor));
					} catch (final ManagerException e) {

						GeneratePlanifException e2 = findException(e,
								GeneratePlanifException.class);
						if (e2 != null
								&& e2.getReason() == GeneratePlanifException.CANCEL_BY_USER) {
							throw new InterruptedException();
						}

						throw new InvocationTargetException(e);

					}
					monitor.done();
				}
			});
		} catch (InvocationTargetException e) {
			// An error occurred during the process let handle it properly
			handleException(e);
		} catch (InterruptedException e) {
			// Operation cancel by user nothing to do
		}
	}

}
