package com.controller.rest;

import com.entity.AjaxResponse;
import com.entity.Sprint;
import com.entity.Statistic;
import com.entity.Task;
import com.service.SprintsService;
import com.service.TaskService;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.*;

/**
 *
 * Rest controller for the Sprints page.
 *
 * @author Laurentiu Marcut
 */
@RestController
public class SprintsRestController {

    private static final Logger logger = Logger.getLogger(SprintsRestController.class);

    @Autowired
    private TaskService taskService;

    @Autowired
    private SprintsService sprintsService;

    /**
     *
     * Select a sprint.
     *
     * @param project
     * @param id
     * @return response
     */
    @RequestMapping(value = "/sprint/getSprint", method = RequestMethod.GET)
    public @ResponseBody Sprint select(@CookieValue(value = "currentProject", defaultValue = "") String project, @RequestParam("id") int id) {
        Sprint sprint = new Sprint();

        try {
            sprint = this.sprintsService.select(project, id);
            sprint.setStatistic(this.calculateStatistic(sprint));
        } catch (Exception e) {
            logger.error("Can not select the sprint. ", e);
        }

        return sprint;
    }

    private Statistic calculateStatistic(Sprint sprint) {
        Statistic statistic = new Statistic();

        // Total points
        int totalPoints = 0;
        for (Task task : sprint.getTasks()) {
            totalPoints += task.getPoints();
        }

        // Day's interval
        LocalDate dateBefore = sprint.getStartDate().toLocalDate();
        LocalDate dateAfter = sprint.getEndDate().toLocalDate();
        int daysBetween = Math.toIntExact(ChronoUnit.DAYS.between(dateBefore, dateAfter));

        // Calculate labels
        List<String> daysLabels = new ArrayList<>(daysBetween);
        for (int i = 1; i <= daysBetween; i++) {
            daysLabels.add("Day " + i);
        }

        // Burned points
        List<Integer> burnedpoints = new ArrayList<>(Collections.nCopies(daysBetween, totalPoints));
        for (Task task : sprint.getTasks()) {
            if (task.getCloseDate() != null) {
                LocalDate dayDate = task.getCloseDate().toLocalDate();
                int day = Math.toIntExact(ChronoUnit.DAYS.between(dateBefore, dayDate));

                for (int i = day; i < daysBetween; i++) {
                    burnedpoints.set(i, burnedpoints.get(i) - task.getPoints());
                }
            }
        }

        // Ideal points
        List<BigDecimal> idealPoints = new ArrayList<>(Collections.nCopies(daysBetween, new BigDecimal(totalPoints)));
        for (int i = 1; i < daysBetween; i++) {
            float points = idealPoints.get(i).floatValue() - (float) totalPoints / ((float) daysBetween-1) * (float) i;
            idealPoints.set(i, new BigDecimal(points).setScale(1, BigDecimal.ROUND_HALF_DOWN));
        }

        statistic.setLabels(daysLabels);
        statistic.setBurnedPoints(burnedpoints);
        statistic.setIdealPoints(idealPoints);
        statistic.setDuration(daysBetween);
        statistic.setCapacity(sprint.getTotalCapacity());
        statistic.setMembers(sprint.getMembers());
        statistic.setPlanned(totalPoints);
        statistic.setBurned(totalPoints - (burnedpoints.isEmpty() ? 0 : burnedpoints.get(burnedpoints.size()-1)));
        statistic.setRemaining((burnedpoints.isEmpty() ? totalPoints : burnedpoints.get(burnedpoints.size()-1)));

        BigDecimal idealVelocity = new BigDecimal((float) sprint.getTotalCapacity() / (float) sprint.getMembers() / (float) daysBetween).setScale(2, BigDecimal.ROUND_HALF_DOWN);
        statistic.setIdealVelocity(idealVelocity.floatValue());

        BigDecimal realVelocity = new BigDecimal((float) statistic.getBurned() / (float) sprint.getMembers() / (float) daysBetween).setScale(2, BigDecimal.ROUND_HALF_DOWN);
        statistic.setRealVelocity(realVelocity.floatValue());

        return statistic;
    }

    /**
     *
     * Insert a sprint.
     *
     * @param project
     * @return response
     */
    @RequestMapping(value = "/sprint/insert", method = RequestMethod.GET)
    public @ResponseBody AjaxResponse insert(@CookieValue(value = "currentProject", defaultValue = "") String project) {
        AjaxResponse response = new AjaxResponse();

        try {
            int totalSprints = this.sprintsService.selectTotalSprints(project);
            this.sprintsService.insert(totalSprints+1, project);

            response.setState("OK");
            response.setMessage("Sprint created.");
        } catch (Exception e) {
            logger.error("Can not create the sprint. ", e);
            response.setState("KO");
            response.setMessage("Can not create the sprint.");
        }

        return response;
    }

    /**
     *
     * Delete a sprint.
     *
     * @param project
     * @param id
     * @return response
     */
    @RequestMapping(value = "/sprint/delete", method = RequestMethod.POST)
    public @ResponseBody AjaxResponse delete(@CookieValue(value = "currentProject", defaultValue = "") String project,
                                                   @RequestBody String id) {
        AjaxResponse response = new AjaxResponse();

        try {
            this.sprintsService.delete(Integer.valueOf(id), project);

            response.setState("OK");
            response.setMessage("Sprint deleted.");
        } catch (Exception e) {
            logger.error("Can not delete the sprint. ", e);
            response.setState("KO");
            response.setMessage("Can not delete the sprint.");
        }

        return response;
    }

    /**
     *
     * Update start date.
     *
     * @param project
     * @param sprint
     * @return response
     */
    @RequestMapping(value = "/sprint/updateStartDate", method = RequestMethod.POST)
    public @ResponseBody AjaxResponse updateStartDate(@CookieValue(value = "currentProject", defaultValue = "") String project,
                                                   @RequestBody Sprint sprint) {
        AjaxResponse response = new AjaxResponse();

        try {
            this.sprintsService.updateStartDate(sprint.getId(), project, sprint.getStartDate());
            response.setState("OK");
            response.setMessage("Start date updated.");
        } catch (Exception e) {
            logger.error("Can not update the start date. ", e);
            response.setState("KO");
            response.setMessage("Can not update the start date.");
        }

        return response;
    }

    /**
     *
     * Update end date.
     *
     * @param project
     * @param sprint
     * @return response
     */
    @RequestMapping(value = "/sprint/updateEndDate", method = RequestMethod.POST)
    public @ResponseBody AjaxResponse updateEndDate(@CookieValue(value = "currentProject", defaultValue = "") String project,
                                                      @RequestBody Sprint sprint) {
        AjaxResponse response = new AjaxResponse();

        try {
            this.sprintsService.updateEndDate(sprint.getId(), project, sprint.getEndDate());
            response.setState("OK");
            response.setMessage("End date updated.");
        } catch (Exception e) {
            logger.error("Can not update the end date. ", e);
            response.setState("KO");
            response.setMessage("Can not update the end date.");
        }

        return response;
    }

    /**
     *
     * Update total capacity.
     *
     * @param project
     * @param sprint
     * @return response
     */
    @RequestMapping(value = "/sprint/updateTotalCapacity", method = RequestMethod.POST)
    public @ResponseBody AjaxResponse updateTotalCapacity(@CookieValue(value = "currentProject", defaultValue = "") String project,
                                                      @RequestBody Sprint sprint) {
        AjaxResponse response = new AjaxResponse();

        try {
            this.sprintsService.updateTotalCapacity(sprint.getId(), project, sprint.getTotalCapacity());
            response.setState("OK");
            response.setMessage("Capacity updated.");
        } catch (Exception e) {
            logger.error("Can not update the capacity. ", e);
            response.setState("KO");
            response.setMessage("Can not update the capacity.");
        }

        return response;
    }

    /**
     *
     * Update members.
     *
     * @param project
     * @param sprint
     * @return response
     */
    @RequestMapping(value = "/sprint/updateMembers", method = RequestMethod.POST)
    public @ResponseBody AjaxResponse updateMembers(@CookieValue(value = "currentProject", defaultValue = "") String project,
                                                          @RequestBody Sprint sprint) {
        AjaxResponse response = new AjaxResponse();

        try {
            this.sprintsService.updateMembers(sprint.getId(), project, sprint.getMembers());
            response.setState("OK");
            response.setMessage("Members updated.");
        } catch (Exception e) {
            logger.error("Can not update the members. ", e);
            response.setState("KO");
            response.setMessage("Can not update the members.");
        }

        return response;
    }

    /**
     *
     * Update task sprint.
     *
     * @param project
     * @param task
     * @return response
     */
    @RequestMapping(value = "/sprint/updateTaskSprint", method = RequestMethod.POST)
    public @ResponseBody AjaxResponse updateSprintTask(@CookieValue(value = "currentProject", defaultValue = "") String project,
                                                    @RequestBody Task task) {
        AjaxResponse response = new AjaxResponse();

        try {
            this.taskService.updateSprint(task.getId(), task.getSprint());
            response.setState("OK");
            response.setMessage("Task inserted to sprint.");
        } catch (Exception e) {
            logger.error("Can not insert the task to the sprint. ", e);
            response.setState("KO");
            response.setMessage("Can not insert the task to the sprint.");
        }

        return response;
    }

    /**
     *
     * Update state.
     *
     * @param project
     * @param sprint
     * @return response
     */
    @RequestMapping(value = "/sprint/updateState", method = RequestMethod.POST)
    public @ResponseBody AjaxResponse updateState(@CookieValue(value = "currentProject", defaultValue = "") String project,
                                                       @RequestBody Sprint sprint) {
        AjaxResponse response = new AjaxResponse();

        try {
            Date now = new java.sql.Date(Calendar.getInstance().getTimeInMillis());

            // Complete sprint
            if ("C".equals(sprint.getState())) {
                if (now.after(sprint.getEndDate())) {
                    /*response.setState("KO");
                    response.setMessage("You can not complete the sprint.");
                    return response;*/
                }
            } else {
                // Start sprint
                if (sprint.getStartDate() == null || sprint.getStartDate().after(now)) {
                    response.setState("KO");
                    response.setMessage("The start date is incorrect.");
                    return response;
                } else if (sprint.getEndDate() == null || sprint.getEndDate().before(now)) {
                    response.setState("KO");
                    response.setMessage("The end date is incorrect.");
                    return response;
                } else if (sprint.getEndDate().before(sprint.getStartDate())) {
                    response.setState("KO");
                    response.setMessage("The start and the end date are incorrect.");
                    return response;
                } else if (sprint.getTotalCapacity() <= 0 || sprint.getTotalCapacity() > 3000) {
                    response.setState("KO");
                    response.setMessage("The capacity value is incorrect.");
                    return response;
                } else if (sprint.getMembers() <= 0 || sprint.getMembers() > 30) {
                    response.setState("KO");
                    response.setMessage("The members value is incorrect.");
                    return response;
                }
            }

            this.sprintsService.updateState(project, sprint.getId(), sprint.getState());
            response.setState("OK");
            response.setMessage("State of sprint updated.");
        } catch (Exception e) {
            logger.error("Can not update the state of the sprint. ", e);
            response.setState("KO");
            response.setMessage("Can not update the state of the sprint.");
        }

        return response;
    }
}
