package ch.zhaw.gartenverwaltung.taskList;

import ch.zhaw.gartenverwaltung.Config;
import ch.zhaw.gartenverwaltung.io.*;
import ch.zhaw.gartenverwaltung.types.*;

import java.io.IOException;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class TaskListModel {
    private TaskDatabase taskDatabase;
    private PlantDatabase plantDatabase;

    /**
     * Comparators to create sorted Task List
     */
    static final Comparator<Task> sortByStartDate = Comparator.comparing(Task::getStartDate);

    public TaskListModel(){
        taskDatabase = new JsonTaskDatabase();
        plantDatabase = new JsonPlantDatabase();
    }

    /**
     * Constructor to create Database Objects.
     */
    public TaskListModel(TaskDatabase taskDatabase, PlantDatabase plantDatabase) {
        this.taskDatabase = taskDatabase;
        this.plantDatabase = plantDatabase;
    }

    /**
     * Method to save a new Task to Task Database
     * @param task the Task to save
     * @throws IOException If the database cannot be accessed
     */
    public void addTask(Task task) throws IOException {
        taskDatabase.saveTask(task);
    }

    /**
     * Method to add all Tasks for a new crop
     * @param crop the crop which is added
     * @throws PlantNotFoundException if the plantId in the crop doesn't exist in Plant Database
     * @throws HardinessZoneNotSetException If there is no Hardiness Zone Set in Plant Database
     * @throws IOException If the database cannot be accessed
     */
    public void planTasksForCrop(Crop crop) throws PlantNotFoundException, HardinessZoneNotSetException, IOException {
        Plant plant = plantDatabase.getPlantById(Config.getCurrentHardinessZone(), crop.getPlantId()).orElseThrow(PlantNotFoundException::new);
        for (GrowthPhase growthPhase : plant.lifecycle()) {
            for (TaskTemplate taskTemplate : growthPhase.taskTemplates()) {
                addTask(taskTemplate.generateTask(crop.getStartDate(), crop.getCropId().orElse(0L)));
            }
        }
    }

    /**
     * Method to remove all Tasks for a specific Crop
     * @param cropId The crop which is removed
     * @throws IOException If the database cannot be accessed
     */
    public void removeTasksForCrop(long cropId) throws IOException {
        taskDatabase.removeTasksForCrop(cropId);
    }

    /**
     * Method to remove a Task from Database
     * @param task the Task to remove
     * @throws IOException If the database cannot be accessed
     */
    public void removeTask(Task task) throws IOException {
        taskDatabase.removeTask(task);
    }

    private List<Task> filterListByCrop(List<Task> taskList, Long cropId) {
        return taskList.stream().filter(task -> task.getCropId() == cropId).collect(Collectors.toList());
    }

    /**
     * Method to get all Tasks
     * @return List of all Tasks
     * @throws IOException If the database cannot be accessed
     */
    public List<Task> getTaskList() throws IOException {
        return getFilteredTaskList(LocalDate.MIN, LocalDate.MAX);
    }

    /**
     * Method to get all Tasks for specific Crop
     * @return List of all Tasks for with given CropID
     * @throws IOException If the database cannot be accessed
     */
    public List<Task> getTaskListForCrop(Long cropId) throws IOException {
        return filterListByCrop(getTaskList(), cropId);
    }

    /**
     * Method to get all Tasks which are today or in future
     * @return List of Tasks
     * @throws IOException If the database cannot be accessed
     */
    public List<Task> getFutureTasks() throws IOException {
        return getFilteredTaskList(LocalDate.now(), LocalDate.MAX);
    }

    /**
     * Method to get all Tasks which are today or in future for specific Crop
     * @return List of Tasks with given crop ID
     * @throws IOException If the database cannot be accessed
     */
    public List<Task> getFutureTasksForCrop(Long cropId) throws IOException {
        return filterListByCrop(getFutureTasks(), cropId);
    }

    /**
     * Method to get all Tasks which are today or in past
     * @return List of Tasks
     * @throws IOException If the database cannot be accessed
     */
    public List<Task> getPastTasks() throws IOException {
        return getFilteredTaskList(LocalDate.MIN, LocalDate.now());
    }

    /**
     * Method to get all Tasks which are today or in past for specifc crop
     * @return List of Tasks with given grop id
     * @throws IOException If the database cannot be accessed
     */
    public List<Task> getPastTasksForCrop(Long cropId) throws IOException {
        return filterListByCrop(getPastTasks(), cropId);
    }

    /**
     * Method to get an List of 7 Tasklists for the next 7 days. Index 0 is Tasklist for Today.
     * @return List with length 7 (List<List<Task>>)
     * @throws IOException If the database cannot be accessed
     */
    public List<List<Task>> getTasksUpcomingWeek() throws IOException {
        List<List<Task>> dayTaskList = new ArrayList<>();
        for(int i = 0; i < 7; i++) {
            LocalDate date = LocalDate.now().plusDays(i);
            dayTaskList.add(taskDatabase.getTaskList(date, date));
        }
        return dayTaskList;
    }

    /**
     * Method to get an List of 7 Tasklists for the next 7 days. (Filtered Index 0 is Tasklist for Today.
     * @return List with length 7 (List<List<Task>>)
     * @throws IOException If the database cannot be accessed
     */
    public List<List<Task>> getTasksUpcomingWeekForCrop(Long cropId) throws IOException {
        List<List<Task>> dayTaskList = new ArrayList<>();
        for(int i = 0; i < 7; i++) {
            LocalDate date = LocalDate.now().plusDays(i);
            dayTaskList.add(filterListByCrop(taskDatabase.getTaskList(date, date), cropId));
        }
        return dayTaskList;
    }

    /**
     * Method to get Tasklist filtered by date.
     * @param start the start date for the filter
     * @param end the end date for the filter
     * @return List of Tasks matched by the filter
     * @throws IOException If the database cannot be accessed
     */
    public List<Task> getFilteredTaskList(LocalDate start, LocalDate end) throws IOException {
        return getSortedTaskList(taskDatabase.getTaskList(start, end), sortByStartDate);
    }

    /**
     * Method to sort a Tasklist by a given Comparator
     * @param taskList The Tasklist to sort
     * @param comparator the comparator to sort
     * @return a sorted coppy of the given Tasklist
     */
    private List<Task> getSortedTaskList(List<Task> taskList, Comparator<Task> comparator) {
        return taskList.stream().sorted(comparator).collect(Collectors.toList());
    }
}
