package com.tmdworldwide.projectmanager.web;

import net.sourceforge.stripes.action.*;
import net.sourceforge.stripes.validation.Validate;
import net.sourceforge.stripes.validation.ValidateNestedProperties;
import net.sourceforge.stripes.integration.spring.SpringBean;
import com.tmdworldwide.projectmanager.db.*;
import com.tmdworldwide.projectmanager.persist.*;

import java.util.List;
import java.util.Set;
import java.util.Date;

import org.stripesstuff.stripersist.Stripersist;

import javax.persistence.EntityManager;
import javax.annotation.security.RolesAllowed;

public class WorkflowMasterActionBean extends PointcutableActionBean {

	/**
	 * The product we are currently dealing with
	 */
	Product product;

	/**
	 * A list of all products on the system
	 */
	List<Product> products;

	WorkflowMasterItem workflowMasterItem;

	/**
	 * A list of all workflow items for this workflow master
	 */
	Set<WorkflowMasterItem> workflowMasterItems;

	@SpringBean
	WorkflowMasterItemDao workflowMasterItemDao;

	@SpringBean
	WorkflowMasterDao workflowMasterDao;

	@SpringBean
	ProductDao productDao;

	@SpringBean
	WorkflowItemTypeDao workflowItemTypeDao;

	@SpringBean
	DurationTypeDao durationTypeDao;

	@DefaultHandler
	@RolesAllowed("manage master workflow")
	public Resolution page() {
		return new ForwardResolution("/WEB-INF/jsp/admin/master_workflow.jsp");
	}

	/**
	 * Delete a WorkflowMasterItem
	 * <p/>
	 * workflow:
	 * <ol>
	 * <li>find the previous item
	 * <li>Update the previous item to point to the item after the deleting one
	 * <li>Update the position of all items after the deleted one to be decremented by one
	 * </ol>
	 * @return a Resolution object back to the current page
	 */
	@RolesAllowed("manage master workflow")
	@SuppressWarnings("unchecked")
	public Resolution delete() {
		EntityManager em = Stripersist.getEntityManager();

		WorkflowMasterItem previousItem = (WorkflowMasterItem) em.createQuery("from WorkflowMasterItem wmi where wmi.successItem.id=:sid").setParameter("sid", workflowMasterItem.getId()).getSingleResult();
		if (previousItem != null) {
			previousItem.setSuccessItem(workflowMasterItem.getSuccessItem());
			previousItem.setUpdatedTimestamp(new Date());
			previousItem.setUpdatedBy(getContext().getCurrentUser());
			workflowMasterItemDao.save(previousItem);
		}

		// TODO update when JPQL is less crap
		// You just can't do update workflow_master_item set position=position-1 where position>:position and workflow_master_id = :id
		// arithmetic operations aren't supported in HQL or JPQL as far as I can see which sucks.
		//
		List<WorkflowMasterItem> wmil = em.createQuery("from WorkflowMasterItem wmi where wmi.position>:position and wmi.workflowMaster.id = :id")
				.setParameter("position", workflowMasterItem.getPosition())
				.setParameter("id", workflowMasterItem.getWorkflowMaster().getId())
				.getResultList();

		for (WorkflowMasterItem wmi : wmil) {
			wmi.setPosition(wmi.getPosition() - 1);
			workflowMasterItemDao.save(wmi);
		}

		workflowMasterItemDao.commit();

		return new RedirectResolution(getClass());
	}

	/**
	 * create a new WorkflowMasterItem
	 * <p/>
	 * workflow:<br/>
	 * <ol>
	 * <li>Update the previous item if there is one to point to our new item</li>
	 * <li>update the new item to point to the next item</li>
	 * <li>go through and increment the positions of all items after the new item</li>
	 * <li>set update properties on new item</li>
	 * <li>save and commit</li>
	 * </ol>
	 * @return a Resultion object that points back to the current page
	 */
	@RolesAllowed("manage master workflow")
	@SuppressWarnings("unchecked")
	public Resolution create() {
		EntityManager em = Stripersist.getEntityManager();

		// Maybe we should throw an error here because technically a product should never be created without a
		// workflow master object
		// but then it's murphy's law that we won't have it sometimes, and I think it's better to just build one
		// than throw a unescesary nasty error.
		if (product.getWorkflowMaster()==null) {
			WorkflowMaster wm = new WorkflowMaster();
			wm.setProduct(product);
			wm.setCreatedBy(getContext().getCurrentUser());
			wm.setCreatedTimestamp(new Date());
			wm.setUpdatedBy(getContext().getCurrentUser());
			wm.setUpdatedTimestamp(new Date());
			wm.setName(product.getName());
			workflowMasterDao.save(wm);
		}

		workflowMasterItem.setWorkflowMaster(product.getWorkflowMaster());

		// If the position of the new item is greater than 0 - update the previuos item to point to the new item
		if (workflowMasterItem.getPosition() > 0) {
			WorkflowMasterItem previousItem = (WorkflowMasterItem) em
					.createQuery("from WorkflowMasterItem wmi where wmi.position=:position and wmi.workflowMaster.id=:id")
					.setParameter("position", workflowMasterItem.getPosition() - 1)
					.setParameter("id", workflowMasterItem.getWorkflowMaster().getId())
					.getSingleResult();

			if (previousItem != null) {
				previousItem.setSuccessItem(workflowMasterItem);
				previousItem.setUpdatedTimestamp(new Date());
				previousItem.setUpdatedBy(getContext().getCurrentUser());
				workflowMasterItemDao.save(previousItem);
			}
		}

		// Find the next item so we can point the new item to it
		WorkflowMasterItem nextItem = (WorkflowMasterItem) em
				.createQuery("from WorkflowMasterItem wmi where wmi.position=:position and wmi.workflowMaster.id=:id")
				.setParameter("position", workflowMasterItem.getPosition())
				.setParameter("id", workflowMasterItem.getWorkflowMaster().getId())
				.getSingleResult();

		if (nextItem != null) {
			workflowMasterItem.setSuccessItem(nextItem);
		}

		// Get all the items after the new item and increment their position
		List<WorkflowMasterItem> wmil = em.createQuery("from WorkflowMasterItem wmi where wmi.position>:position and wmi.workflowMaster.id = :id")
				.setParameter("position", workflowMasterItem.getPosition())
				.setParameter("id", workflowMasterItem.getWorkflowMaster().getId())
				.getResultList();

		for (WorkflowMasterItem wmi : wmil) {
			wmi.setPosition(wmi.getPosition() + 1);
			// Should we update updatedBy and updatedTimestamp here?
			// we are really only just bumping it in the list, we aren't really changing it in any
			// significant way
			workflowMasterItemDao.save(wmi);
		}

		// Set all our update properties of the new record TODO create hibernate XML to do the timestamp update
		workflowMasterItem.setUpdatedTimestamp(new Date());
		workflowMasterItem.setUpdatedBy(getContext().getCurrentUser());
		workflowMasterItem.setWorkflowMaster(product.getWorkflowMaster());
		workflowMasterItemDao.save(workflowMasterItem);

		workflowMasterItemDao.commit();

		return new RedirectResolution(getClass());
	}

	@RolesAllowed("manage master workflow")
	public Resolution setProduct() {
		getContext().setCurrentProduct(product);
		return new RedirectResolution(getClass());
	}

	@Validate(required = true, on = "create")
	public Product getProduct() {
		return product;
	}

	public void setProduct(Product product) {
		this.product = product;
	}

	public List<Product> getProducts() {
		if (products == null) {
			products = productDao.read();
		}
		return products;
	}

	public void setProducts(List<Product> products) {
		this.products = products;
	}

	@ValidateNestedProperties({
			@Validate(field = "id", required = true, on = "delete"),
			@Validate(field = "name", required = true, on = "create", label="Name"),
			@Validate(field = "description", required = true, on = "create", label="Description"),
			@Validate(field = "position", required = true, on = "create", label="Position"),
			@Validate(field = "duration", required = true, minvalue = 0, on = "create", label="Duration"),
			@Validate(field = "durationType", required = true, on = "create", label="Duration Type")
	})
	public WorkflowMasterItem getWorkflowMasterItem() {
		return workflowMasterItem;
	}

	public void setWorkflowMasterItem(WorkflowMasterItem workflowMasterItem) {
		this.workflowMasterItem = workflowMasterItem;
	}

	public Set<WorkflowMasterItem> getWorkflowMasterItems() {
		if (workflowMasterItems == null && product != null) {
			workflowMasterItems = workflowMasterItemDao.findByProduct(product.getId());
		}
		return workflowMasterItems;
	}

	public void setWorkflowMasterItems(Set<WorkflowMasterItem> workflowMasterItems) {
		this.workflowMasterItems = workflowMasterItems;
	}

	public List<WorkflowItemType> getWorkflowItemTypes() {
		return workflowItemTypeDao.read();
	}

	public List<DurationType> getDurationTypes() {
		return durationTypeDao.read();
	}
}