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.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.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.glassfish.jersey.server.JSONP;
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.fasterxml.jackson.datatype.joda.JodaModule;
import com.mongodb.BasicDBObject;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;

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]+}")
    @JSONP(callback = "callback", queryParam = "callback")
    @Produces({ "application/javascript", "application/x-javascript",
            "text/ecmascript", "application/ecmascript", "text/jscript",
            MediaType.APPLICATION_JSON + ";charset=utf-8" })
    public T getObject(@PathParam("id") ID objectId) {

        this.logger.debug("GetObject: " + objectId);

        T obj = this.getRepository().findOne(objectId);

        if (null == obj) {
            throw new WebApplicationException(Status.NOT_FOUND);
        }

        return obj;
    }

    @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();

    }
    
    protected T saveObject(T obj) {
        return this.getRepository().save(obj);
    }
    
    protected T saveObjectPost(T obj) {
        return this.saveObject(obj);
    }
    
    protected T saveObjectPut(T obj) {
        return this.saveObject(obj);
    }

    /**
     * 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.saveObjectPut(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.isIdSet()) {
            throw new WebApplicationException(Response
                    .status(Status.BAD_REQUEST)
                    .entity("Id in JSON must not be set").build());
        }

        obj.setId(getNewId());
        this.prepareObject(obj);
        obj = this.saveObjectPost(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);
        mapper.registerModule(new JodaModule());
        return mapper.writeValueAsString(res);
    }

}
