package org.iri_research.renkan.controller;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.servlet.http.HttpServletResponse;

import org.iri_research.renkan.Constants;
import org.iri_research.renkan.Constants.EditMode;
import org.iri_research.renkan.RenkanException;
import org.iri_research.renkan.RenkanProperties;
import org.iri_research.renkan.models.Project;
import org.iri_research.renkan.repositories.ProjectsRepository;
import org.iri_research.renkan.repositories.SpacesRepository;
import org.iri_research.renkan.rest.ObjectMapperProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.servlet.ModelAndView;

import com.fasterxml.jackson.annotation.ObjectIdGenerators.UUIDGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

@Controller
@RequestMapping("/p")
public class RenkanController {

    private final Logger logger = LoggerFactory
            .getLogger(RenkanController.class);

    @Autowired
    private ProjectsRepository projectsRepository;

    @Autowired
    private SpacesRepository spacesRepository;
    
    @Autowired
    private ObjectMapperProvider mapperProvider;

    private void checkCowebkey(String cowebkey, Project project,
            Constants.EditMode editMode) {
        if (cowebkey == null || cowebkey.isEmpty()) {
            throw new HttpClientErrorException(HttpStatus.BAD_REQUEST,
                    "Cowebkey missing");
        }
        try {
            if (!project.checkKey(cowebkey, editMode)) {
                throw new HttpClientErrorException(HttpStatus.BAD_REQUEST,
                        "Bad cowebkey");
            }
        } catch (RenkanException e) {
            throw new HttpServerErrorException(
                    HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage());
        }
    }

    @RequestMapping(value = "/copy", method = RequestMethod.POST, produces = { "application/json;charset=UTF-8" })
    public @ResponseBody
    Project copyProject(@RequestParam(value = "project_id") String projectId) {

        if (projectId == null || projectId.length() == 0) {
            throw new IllegalArgumentException(
                    "RenkanContoller.renkanProject.copyProject: Project id is null or empty.");
        }
        Project project = this.projectsRepository.findOne(projectId);
        if (project == null) {
            throw new HttpClientErrorException(HttpStatus.NOT_FOUND, "Project "
                    + projectId + " not found for copyProject.");
        }

        Project newProject = this.projectsRepository.copy(project,
                project.getTitle() + " (copy)");

        return newProject;
    }

    @RequestMapping(value = "/{project_id}", method = RequestMethod.GET, produces = {
            "text/html;charset=UTF-8", "!image/*" })
    public ModelAndView renkanProject(
            @PathVariable(value = "project_id") String project_id,
            @RequestHeader(value = "Accept") String accept_header,
            @RequestParam(value = "cowebkey") String cowebkey)
            throws HttpMediaTypeNotSupportedException {

        this.logger.debug("renkanProject : " + project_id + " Accept : "
                + accept_header != null ? accept_header : "" + ", cowebkey: "
                + cowebkey != null ? cowebkey : "");

        if (project_id == null || project_id.length() == 0) {
            throw new IllegalArgumentException(
                    "RenkanContoller.renkanProject: Project id is null or empty.");
        }

        Project project = this.projectsRepository.findOne(project_id);

        if (project == null) {
            throw new HttpClientErrorException(HttpStatus.NOT_FOUND, "Project "
                    + project_id + " not found.");
        }

        this.checkCowebkey(cowebkey, project, EditMode.EDITION);

        Map<String, Object> model = new HashMap<String, Object>();
        model.put("coweb_debug", Boolean.parseBoolean(RenkanProperties
                .getInstance().getProperty("renkan.coweb.debug", "false")));
        model.put("coweb_websockets", Boolean.parseBoolean(RenkanProperties
                .getInstance().getProperty("renkan.coweb.websocket", "true")));
        model.put("project", project);
        model.put("space", spacesRepository.findOne(project.getSpaceId()));

        return new ModelAndView("renkanProjectEdit", model);
    }

    @RequestMapping(value = "/pub/{project_id}", method = RequestMethod.GET, produces = {
            "text/html;charset=UTF-8", "!image/*" })
    public String renkanPublishProject(Model model,
            @PathVariable(value = "project_id") String projectId,
            @RequestParam(value = "cowebkey") String cowebkey) {
        if (projectId == null || projectId.length() == 0) {
            throw new IllegalArgumentException(
                    "RenkanContoller.renkanProject: Project id is null or empty.");
        }

        Project project = this.projectsRepository.findOne(projectId);
        if (project == null) {
            throw new HttpClientErrorException(HttpStatus.NOT_FOUND, "Project "
                    + projectId + " not found.");
        }

        this.checkCowebkey(cowebkey, project, EditMode.READ_ONLY);

        model.addAttribute("project", project);
        model.addAttribute("space",
                spacesRepository.findOne(project.getSpaceId()));

        return "renkanProjectPublish";
    }
    
    @RequestMapping(value = "/exp/{project_id}", method = RequestMethod.GET, produces = { "application/json;charset=UTF-8" })
    public @ResponseBody String exportProject(@PathVariable(value = "project_id") String projectId, HttpServletResponse response) throws JsonProcessingException {
        
        ObjectMapper mapper = this.mapperProvider.getContext(ObjectMapper.class);
        
        Project project = this.projectsRepository.findOne(projectId);
        
        if (project == null) {
            throw new HttpClientErrorException(HttpStatus.NOT_FOUND, "Project "
                    + projectId + " not found.");
        }
        
        ObjectNode jsonNode = mapper.valueToTree(project);
        
        jsonNode.remove("id");
        
        Iterator<JsonNode> nodes = jsonNode.get("nodes").elements();

        HashMap<String, String> nodeIds = new HashMap<String, String>();
        UUIDGenerator uuidgens = new UUIDGenerator();
        
        while(nodes.hasNext()) {
            ObjectNode nodeNode = (ObjectNode) nodes.next();
            String nodeId = nodeNode.get("id").asText();
            String atId = uuidgens.generateId(nodeNode).toString();
            nodeIds.put(nodeId, atId);
            nodeNode.put("@id", atId);
            nodeNode.remove("id");
        }
        
        Iterator<JsonNode> edges = jsonNode.get("edges").elements();
        while(edges.hasNext()) {
            ObjectNode edgeNode = (ObjectNode) edges.next();
            edgeNode.put("from", nodeIds.get(edgeNode.get("from").asText()));
            edgeNode.put("to", nodeIds.get(edgeNode.get("to").asText()));
            edgeNode.remove("id");
        }

        String res =  mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonNode);
        
        response.setContentType("application/force-download");
        response.setHeader("Content-Transfer-Encoding", "binary");
        response.setHeader("Content-Disposition", "attachment; filename=\""+ projectId +".json\"");
        
        return res;
    }

}
