package org.iri_research.renkan.rest;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
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.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;

import org.iri_research.renkan.models.IRenkanModel;
import org.iri_research.renkan.repositories.IRenkanRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.mongodb.BasicDBObject;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.sun.jersey.api.json.JSONWithPadding;

public abstract class RenkanResource<T extends IRenkanModel<ID>, ID extends Serializable> {
	
	private Logger logger = LoggerFactory.getLogger(RenkanResource.class);
	
	protected String[] baseObjectListFieldList = {"description","title","uri","created", "color"};

	abstract protected IRenkanRepository<T, ID> getRepository();
	abstract protected ID getNewId();
	abstract protected List<String> getObjectListFieldList();

	@Context
	private UriInfo uriInfo;

	protected DBCollection getCollection() {
		return this.getRepository().getCollection();
	}
	
	abstract protected void prepareObject(T obj);
	
	protected void doDeleteObject(ID objectId) {
		this.getRepository().delete(objectId);
	}
	
	
	//TODO: this produce application/javascript by default. I would rather have application/json. The prefered behaviour would be to produde js only od the callbacl query param is used
	@GET
	@Path("{id : [a-zA-Z\\-0-9]+}")
	@Produces({ "application/javascript",
        "application/x-javascript", "text/ecmascript", 
        "application/ecmascript", "text/jscript", MediaType.APPLICATION_JSON + ";charset=utf-8"})
	public JSONWithPadding getObject(@PathParam("id") ID objectId, @QueryParam("callback") @DefaultValue("callback") String callback) {
		
		this.logger.debug("GetObject: " + objectId);
				
		T obj = this.getRepository().findOne(objectId);
		
		if (null == obj) {
			throw new WebApplicationException(Status.NOT_FOUND);
		}
				
		return new JSONWithPadding(obj, callback);
	}
	
	@DELETE
	@Path("{id : [a-zA-Z\\-0-9]+}")
	@Produces(MediaType.TEXT_PLAIN + ";charset=utf-8")
	public Response deleteObject(@PathParam("id") ID objectId) {
		
		this.logger.debug("DeleteObject : id " + objectId);
		this.doDeleteObject(objectId);	
		
		return Response.ok(this.uriInfo.getAbsolutePathBuilder().build().toString() + " deleted").build();
		
	}
	
	/**
	 * test: curl -i -X PUT -H 'Content-Type: application/json' -d @test-data.json  http://localhost:8080/renkan/rest/spaces/12eff140-e65c-11e1-aff1-0800200c9a66
	 * @param objId
	 * @param objectContent
	 */
	@PUT
	@Path("{id : [a-zA-Z\\-0-9]+}")
	@Consumes(MediaType.APPLICATION_JSON + ";charset=utf-8")
	public Response putRenkanObject(@PathParam("id") ID objId, T obj) {

		if(!objId.equals(obj.getId())) {
			throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity("Id parameter and id in JSON do not match").build());
		}
		
		if(!this.getRepository().exists(objId)) {
			throw new WebApplicationException(Response.status(Status.NOT_FOUND).build());
		}		
		
		this.prepareObject(obj);
		this.getRepository().save(obj);
		return Response.noContent().build();
		
	}
	
	@POST
	@Consumes(MediaType.APPLICATION_JSON + ";charset=utf-8")
	@Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
	public Response postRenkanObject(T obj) {

		if(obj.getId() != null) {
			throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity("Id in JSON must be null").build());
		}
				
		obj.setId(getNewId());
		this.prepareObject(obj);
		obj = this.getRepository().save(obj);
		return Response.created(this.uriInfo.getAbsolutePathBuilder().segment(obj.getId().toString()).build()).entity(obj).build();		
	}
			

	@GET
	@Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
	public String getObjectList() throws JsonProcessingException {
		
		BasicDBObject keys = new BasicDBObject();
		
		for (String fieldname : this.getObjectListFieldList()) {
			keys.put(fieldname, 1);
		}
		DBCursor cursor = this.getCollection().find(new BasicDBObject(), keys);
		
		List<DBObject> res = new ArrayList<DBObject>();
				
		try {
			while(cursor.hasNext()) {
				DBObject obj = cursor.next();
				obj.put("id", obj.get("_id"));
				DBObject links = new BasicDBObject();
				
				DBObject linkdef = new BasicDBObject();
				linkdef.put("href", this.uriInfo.getAbsolutePathBuilder().path(obj.get("_id").toString()).build().toString());
				linkdef.put("method", "get");
				linkdef.put("produces", MediaType.APPLICATION_JSON + ";charset=utf-8");				
				links.put("view", linkdef);
				
				linkdef = new BasicDBObject();
				linkdef.put("href", this.uriInfo.getAbsolutePathBuilder().path(obj.get("_id").toString()).build().toString());
				linkdef.put("method", "put");
				linkdef.put("consumes", MediaType.APPLICATION_JSON + ";charset=utf-8");				
				links.put("update", linkdef);

				linkdef = new BasicDBObject();
				linkdef.put("href", this.uriInfo.getAbsolutePathBuilder().path(obj.get("_id").toString()).build().toString());
				linkdef.put("method", "delete");
				links.put("delete", linkdef);				
				
				obj.put("__links",  links);
				res.add(obj);
			}
		}
		finally {
			cursor.close();
		}
		ObjectMapper mapper = new ObjectMapper();
		mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
		return mapper.writeValueAsString(res);
	}


	
}
