package com.k_int.ciim.ui.resources;

import java.util.Set;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.orm.hibernate3.SessionFactoryUtils;

import com.k_int.ciim.core.update.UpdateService;
import com.k_int.ciim.data.dto.DataException;
import com.k_int.ciim.data.util.CacheManager;
import com.k_int.ciim.ref.MuseumDataTypeEnum;
import com.k_int.ciim.ui.exceptions.MissingResourceException;
import com.k_int.ciim.ui.exceptions.NotAuthorisedException;
import com.k_int.ciim.ui.exceptions.ResourceCreationException;
import com.k_int.ciim.ui.exceptions.ResourceDeletionException;
import com.k_int.ciim.ui.json.ContextJSON;
import com.k_int.ciim.ui.kernel.ContextQueryCore;
import com.k_int.ciim.ui.kernel.ContextTransactionCore;
import com.k_int.ciim.ui.ref.RoleDefinitionEnum;
import com.sun.jersey.api.view.ImplicitProduces;
import com.sun.jersey.api.view.Viewable;

@XmlRootElement
@ImplicitProduces("text/html;qs=5") //for FireFox?
@XmlAccessorType(XmlAccessType.FIELD)
@Path("/resources/contexts/{id}")
public class ContextResource extends AbstractResource implements ApplicationContextAware
{
	@XmlTransient
	private static Log log = LogFactory.getLog(ContextResource.class);
	
	private ContextJSON resource = null;
	
	private MuseumDataTypeEnum[] data_types = null;
	
	@XmlTransient
	private ApplicationContext ctx;

	public void setApplicationContext(ApplicationContext ctx) 
	{
		this.ctx = ctx;
	}
	  	
	@XmlTransient
	private ContextQueryCore cqc = null;
	@XmlTransient
	private ContextTransactionCore ctc = null;
	@XmlTransient
	private SessionFactory factory = null;
	@XmlTransient
	private UpdateService update_service = null;
	
	/* App Context setters */
	public void setContextQueryCore(ContextQueryCore cqc) {	this.cqc = cqc;	}
	public void setContextTransactionCore(ContextTransactionCore ctc) {	this.ctc = ctc;	}
	public void setSessionFactory(SessionFactory factory) {	this.factory = factory; }	
	public void setUpdateService(UpdateService update_service) { this.update_service = update_service; }
	
	public ContextResource(){;}
	
	@GET
	@Produces({MediaType.TEXT_HTML,MediaType.APPLICATION_XHTML_XML})
	public Viewable getHtml(@PathParam("id") Long id, @QueryParam("augment") Boolean augment) 
	{
		resource = new ContextJSON(cqc.get(id));
		
		//now set the assigned schemas
		resource.setAssignedSchemas(cqc.getAssignedSchemas(id));
		
		if(augment != null && augment.booleanValue())
		{
			return new Viewable("augment",this);
		}
		else
		{
			return new Viewable("index",this);
		}
	}
	
	@GET
	@Produces("application/json")
	public ContextJSON getJson(@PathParam("id") Long id) 
	{		
		resource = new ContextJSON(cqc.get(id));
		
		//now set the assigned schemas
		resource.setAssignedSchemas(cqc.getAssignedSchemas(id));
		
		return resource;
	}
		
	@POST
	@Consumes("application/x-www-form-urlencoded")
	@Produces({MediaType.TEXT_PLAIN})
	public String formActions(	@PathParam("id") Long id,
								@QueryParam("action") String action,
								MultivaluedMap<String, String> form_params)
	{	
		String retval = null;
		
		if(action != null)
		{
			if(action.equalsIgnoreCase("update"))
			{
				retval = this.update(id, form_params);
			}
			else if(action.equalsIgnoreCase("augment"))
			{
				retval = this.augment(id, form_params);
			}
			else if(action.equalsIgnoreCase("add_schema"))
			{
				retval = this.addContextSchema(id, form_params);
			}
			else
			{
				throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity("Action parameter " + action + " was not recognised as a valid action.").type("text/plain").build());
			}
		}
		else
		{
			throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity("No action parameter supplied").type("text/plain").build());
		}
		
		return retval;
	}
	
	@POST
	@Produces({MediaType.TEXT_PLAIN})
	public String actions(	@PathParam("id") Long id,
							@QueryParam("action") String action,
							@QueryParam("context_schema_id") Long context_schema_id)
	{				
		String retval = null;
		
		if(action != null)
		{
			if(action.equalsIgnoreCase("delete"))
			{
				retval = this.delete(id);
			}
			else if(action.equalsIgnoreCase("publish"))
			{
				retval = this.publish(id);
			}
			else if(action.equalsIgnoreCase("remove_schema"))
			{
				retval = this.deleteContextSchema(context_schema_id);
			}
			else
			{
				throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity("Action parameter " + action + " was not recognised as a valid action.").type("text/plain").build());
			}
		}
		else
		{
			throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity("No action parameter supplied").type("text/plain").build());
		}
		
		return retval;
	}
	
	public String update(	@PathParam("id") Long id,
							MultivaluedMap<String, String> form_params)
	{
		String retval = "Failed to update Context.";
		
		if(this.meetsRoleRequirement(RoleDefinitionEnum.SUPER.toString()))
		{
			if(validate("update", form_params) == null)
			{
				try
				{
					if(ctc.update(id, form_params, request.getUserPrincipal().getName()))
					{
						retval = "Context successfully updated.";		
					}
				}
				catch(MissingResourceException mre)
				{
					throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(mre.getMessage()).type("text/plain").build());
				}
			}
			else
			{
				//return validation error message
				throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(validate("update", form_params)).type("text/plain").build());
			}
		}
		else
		{
			throw new NotAuthorisedException();
		}  
	
		return retval;
	}

	public String augment(	@PathParam("id") Long id,
							MultivaluedMap<String, String> form_params)
	{
		String retval = "Failed to augment Context.";
		
		if(this.meetsRoleRequirement(RoleDefinitionEnum.SUPER.toString()))
		{
			try
			{
				if(ctc.augment(id, form_params, request.getUserPrincipal().getName()))
				{
					retval = "Context successfully augmented.";		
				}
			}
			catch(MissingResourceException mre)
			{
				throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(mre.getMessage()).type("text/plain").build());
			}
		}
		else
		{
			throw new NotAuthorisedException();
		}  
	
		return retval;
	}
	
	public String publish(Long id)
	{
		String retval = "Failed to publish Context.";
		
		if(this.meetsRoleRequirement(RoleDefinitionEnum.SUPER.toString()))
		{
			try
			{
				boolean is_running = update_service.running();
				
				if(ctc.publish(id, request.getUserPrincipal().getName()))
				{
					if(is_running)
					{
						retval = "Successfully queued for publishing";
					}
					else
					{
						retval = "Publication successful";
					}
				}
			}
			catch(MissingResourceException mre)
			{
				throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(mre.getMessage()).type("text/plain").build());
			}
		}
		else
		{
			throw new NotAuthorisedException();
		}  
	
		return retval;
	}
	
	@DELETE
	@Produces({MediaType.TEXT_PLAIN})
	public String delete(@PathParam("id") Long id)
	{
		String retval = "Failed to delete Context.";
		
		if(this.meetsRoleRequirement(RoleDefinitionEnum.SUPER.toString()))
		{
			try
			{
				boolean is_running = update_service.running();
				
				if(ctc.delete(id))
				{	
					if(is_running)
					{
						retval = "Successfully queued for archiving";
					}
					else
					{
						retval = "Archive successful";
					}		
				}
			}
			catch(MissingResourceException mre)//couldn't find context
			{
				throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(mre.getMessage()).type("text/plain").build());
			}
			catch(ResourceDeletionException rde)//unable to delete context
			{
				throw new WebApplicationException(Response.status(Status.CONFLICT).entity(rde.getMessage()).type("text/plain").build());
			}
		}
		else
		{
			throw new NotAuthorisedException();
		}  
	
		return retval;
	}

	//#TODO : make own resources class for this
	public String addContextSchema(	@PathParam("id") Long id,
									MultivaluedMap<String, String> form_params)
	{
		String retval = "Failed to add a Context Schema.";
		
		if(this.meetsRoleRequirement(RoleDefinitionEnum.SUPER.toString()))
		{
			if(validate("add_schema", form_params) == null)
			{
				try
				{
					if(ctc.addContextSchema(id, form_params, request.getUserPrincipal().getName()))
					{
						retval = "Context Schema successfully added.";		
					}
				}
				catch(ResourceCreationException rce)
				{
					throw new WebApplicationException(Response.status(Status.CONFLICT).entity(rce.getMessage()).type("text/plain").build());
				}
				catch(MissingResourceException mre)//couldn't find context
				{
					throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(mre.getMessage()).type("text/plain").build());
				}
			}
			else
			{
				//return validation error message
				throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(validate("add_schema", form_params)).type("text/plain").build());
			}
		}
		else
		{
			throw new NotAuthorisedException();
		}  
	
		return retval;
	}
	
	public String validate(String action, MultivaluedMap<String, String> form_params)
	{
		String retval = null;
	
		if(action.equalsIgnoreCase("update"))
		{
			if(form_params.getFirst("name") == null || form_params.getFirst("name").trim().length() == 0)
			{
				return "name is required.";
			}
			else if(form_params.getFirst("name").trim().equalsIgnoreCase("GLOBAL"))
			{
				return form_params.getFirst("name") + " is a reserved name, please try something else.";
			}
		}
		else if(action.equalsIgnoreCase("add_schema"))
		{
			if(form_params.getFirst("type") == null || form_params.getFirst("type").equalsIgnoreCase("Please Select"))
			{
				retval = "type is required.";
			}
			else if(form_params.getFirst("schema") == null || form_params.getFirst("schema").equalsIgnoreCase("Please Select"))
			{
				retval = "schema is required.";
			}
		}
		
		return retval;
	}
	
	//#TODO : make own resources class for this
	public String deleteContextSchema(@QueryParam("context_schema_id") Long context_schema_id)
	{
		String retval = "Failed to delete Context Schema.";
		
		if(this.meetsRoleRequirement(RoleDefinitionEnum.SUPER.toString()))
		{
			try
			{
				if(ctc.deleteContextSchema(context_schema_id, request.getUserPrincipal().getName()))
				{
					retval = "Context Schema successfully deleted.";		
				}
			}
			catch(MissingResourceException mre)//couldn't find context schema
			{
				throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(mre.getMessage()).type("text/plain").build());
			}
			catch(ResourceDeletionException rde)
			{
				throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(rde.getMessage()).type("text/plain").build());
			}
		}
		else
		{
			throw new NotAuthorisedException();
		}  
	
		return retval;
	}
	
	public ContextJSON getResource() 
	{
		return resource;
	}

	public void setResource(ContextJSON resource) 
	{
		this.resource = resource;
	}
	
	public MuseumDataTypeEnum[] getDataTypes() 
	{
		CacheManager cache_manager = (CacheManager) ctx.getBean("CacheManager"); 
		
		try
		{
			Session session = SessionFactoryUtils.getSession(factory, true);
			
			Set<MuseumDataTypeEnum> types = cache_manager.getDataTypes(session);
			if(types != null && types.size() > 0)
			{
				int array_size = types.size() - resource.getAssignedSchemas().size();
				data_types = new MuseumDataTypeEnum[array_size];
				
				int counter = 0;
				
				for(MuseumDataTypeEnum data_type_val : types)
				{
					//only add if not currently in use
					if(resource.getAssignedSchemas().size() == 0 || resource.getAssignedSchemas().containsKey(data_type_val.toString()) == false)
					{
						data_types[counter] = data_type_val;
						counter++;
					}
				}

			}
		}
		catch(DataException de)
		{
			log.error("Unable to retrieve data types (in use) from cache manager.");
		}
		
		return data_types;
	}
	
	public void setDataTypes(MuseumDataTypeEnum[] data_types) 
	{
		this.data_types = data_types;
	}
}
