/**
 * Copyright (C) 2019 Patrik Dufresne Service Logiciel <info@patrikdufresne.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package com.planimod.ui.views.planif.printing;

import static com.planimod.ui.Localized._;

import static com.planimod.ui.views.planif.printing.PlanifSummaryHelper.getEmployeeName;
import static com.planimod.ui.views.planif.printing.PlanifSummaryHelper.getNonAvailabilityDesc;
import static com.planimod.ui.views.planif.printing.PlanifSummaryHelper.getVacantDesc;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import org.eclipse.nebula.paperclips.core.grid.DefaultGridLook;
import org.eclipse.nebula.paperclips.core.grid.GridPrint;
import org.eclipse.nebula.paperclips.core.border.LineBorder;
import org.eclipse.nebula.paperclips.core.Print;
import org.eclipse.nebula.paperclips.core.text.TextPrint;

import com.patrikdufresne.printing.BackgroundProvider;
import com.patrikdufresne.printing.ColorUtil;
import com.patrikdufresne.printing.FontDataUtil;
import com.patrikdufresne.printing.GapPrint;
import com.patrikdufresne.util.BidiMultiMap;
import com.patrikdufresne.util.DateFormatRegistry;
import com.planimod.core.Employee;
import com.planimod.core.EmployeePreference;
import com.planimod.core.NonAvailability;
import com.planimod.core.Position;
import com.planimod.core.Section;
import com.planimod.core.Shift;
import com.planimod.core.Task;
import com.planimod.core.Team;
import com.planimod.core.TimeRange;
import com.planimod.core.comparators.EmployeePreferenceComparators;
import com.planimod.core.comparators.NonAvailabilityComparators;
import com.planimod.core.comparators.SectionComparators;
import com.planimod.core.comparators.TeamComparators;
import com.planimod.ui.printing.PlanimodPrintFactory;
import com.planimod.ui.printing.PlanimodPrintUtil;

/**
 * This class is used to generate a planif report. This report intend to generalized the solution.
 * 
 * @author Patrik Dufresne
 * 
 */
public class PlanifWeekSummaryPrintFactory extends PlanimodPrintFactory {

    /**
     * Define the first day of week
     */
    private int firstDayOfWeek;

    /**
     * List of non-availabilities.
     */
    private List<NonAvailability> nonAvailabilities;

    /**
     * List of employee preferences.
     */
    private List<EmployeePreference> preferences;

    /**
     * Tasks to print.
     */
    private List<Task> tasks;

    /**
     * Create a new factory.
     */
    public PlanifWeekSummaryPrintFactory() {
        super(_("Planification main d'oeuvre hebdomadaire"));

        // Sets first day of week using the localized calendar. Used should
        // redefine the first day of week using the function
        // #setFirstDayOfWeek(Date).
        Calendar cal = Calendar.getInstance();
        this.firstDayOfWeek = cal.getFirstDayOfWeek();
    }

    /**
     * This implementation create a new print body.
     */
    @Override
    protected void createSections() {

        // Build the data to be printed.
        Set<TimeRange> ranges = DataTable.computeWeekTimeRange(this.tasks, this.firstDayOfWeek);

        // Create print to hold the pages content
        GridPrint topGrid = new GridPrint();
        topGrid.addColumn("c:d:g");

        /*
         * Loop on week time range
         */
        for (TimeRange range : ranges) {

            // Create data structure from the list of task for the current week
            final DataTable data = new DataTable(range, this.tasks, this.preferences, this.nonAvailabilities);
            String title = DateFormatRegistry.format(data.getRange().getStartDate(), DateFormatRegistry.DATE | DateFormatRegistry.MEDIUM);
            title = _("Planification main d'oeuvre hebdomadaire\r\nSemaine du %s", title);
            addSection(new PlanimodSection(title, getSubTitle()) {
                @Override
                public Print createBodyArea() {
                    return createWeekGrid(data);
                }
            });
        }

    }

    /**
     * Creating a print for a week.
     * 
     * @param parent
     *            the parent Print.
     * @param data
     *            the data associated to the week.
     */
    protected Print createWeekGrid(DataTable data) {

        // Create a grid to hold all the data related to the time range (a
        // week)
        GridPrint weekGrid = new GridPrint();
        weekGrid.addColumn("l:d:g");
        BackgroundProvider background = new BackgroundProvider(weekGrid);
        DefaultGridLook look = new DefaultGridLook();
        LineBorder border = new LineBorder();
        border.setGapSize(0);
        look.setCellBorder(border);
        look.setBodyBackgroundProvider(background);
        look.setCellPadding(0, 0);
        weekGrid.setLook(look);

        List<Team> teams = new ArrayList<Team>(data.listTeam());
        Collections.sort(teams, TeamComparators.byName());

        /*
         * Loop on team
         */
        for (Team team : teams) {

            // Add a team labels to weekPrint
            int countEmployee = data.listEmployeeByTeam(team).size();
            TextPrint textPrint = new TextPrint(PlanimodPrintUtil.getName(team, countEmployee), FontDataUtil.BOLD_SMALL);
            textPrint.setForeground(ColorUtil.WHITE);
            Print print = new GapPrint(textPrint, 1, 5);
            background.setBackground(print, ColorUtil.BLACK);
            weekGrid.add(print);

            GridHelper helper = new GridHelper();

            /*
             * Create a print group for each Section.
             */
            List<Section> sections = new ArrayList<Section>(data.listSectionByTeam(team));
            Collections.sort(sections, SectionComparators.byName());
            for (Section s : sections) {
                List<Employee> employees = new ArrayList<Employee>(data.listEmployeeByTeamAndSection(team, s));
                if (employees.size() > 0) {
                    // Add the section
                    helper.addGroup(PlanimodPrintUtil.getName(s).toUpperCase());
                    // Sort Employee by seniority.
                    Collections.sort(employees, EmployeePreferenceComparators.byPreferredSectionAndSeniority(s, this.preferences));
                    int count = 1;
                    for (Employee employee : employees) {
                        boolean classified = data.listEmployeeWithClassifiedPositionByTeam(team).contains(employee);
                        helper.addItem(getEmployeeName(count, employee, classified, false));
                        count++;
                    }
                }
            }

            /*
             * Create vacant group
             */
            Collection<Task> vacants = data.listVacantTasksByTeam(team);
            if (vacants.size() > 0) {
                helper.addGroup((vacants.size() == 1 ? _("Poste vacant") : _("Postes vacants")).toUpperCase(), true);
                BidiMultiMap<Position, Task> table = DataTable.groupByPosition(vacants);
                for (Position pos : table.keySet()) {
                    helper.addItem(getVacantDesc(table.valueSet(pos)));
                }
            }

            /*
             * Create non-availability group
             */
            List<NonAvailability> list = new ArrayList<NonAvailability>(data.listVisibleNonAvailabilityByTeam(team));
            if (list.size() > 0) {
                helper.addGroup(_("Absences"), true);
                Collection<Shift> shifts = data.listShiftByTeam(team);
                Collections.sort(list, NonAvailabilityComparators.byEmployeeName());
                for (NonAvailability na : list) {
                    helper.addItem(getNonAvailabilityDesc(na, shifts));
                }
            }

            // Create the print and add it to the weekGrid.
            weekGrid.add(new GapPrint(helper.createPrint(5, 2), 5, 5, 12, 5));

        }

        /*
         * Print non-availabilities
         */
        List<NonAvailability> currentNonAvailabilities = new ArrayList<NonAvailability>(data.listVisibleNonAvailabilityWithoutTeam());
        if (currentNonAvailabilities.size() > 0) {

            // Add a team labels to weekPrint
            TextPrint textPrint = new TextPrint(_("Vacances et absences"), FontDataUtil.BOLD_SMALL);
            textPrint.setForeground(ColorUtil.WHITE);
            Print print = new GapPrint(textPrint, 1, 5);
            background.setBackground(print, ColorUtil.BLACK);
            weekGrid.add(print);

            GridHelper helper = new GridHelper();
            helper.addGroup("");

            // Sort non availability by enployee's name
            Collections.sort(currentNonAvailabilities, NonAvailabilityComparators.byEmployeeName());
            for (NonAvailability na : currentNonAvailabilities) {
                helper.addItem(getNonAvailabilityDesc(data.getRange(), na));
            }

            weekGrid.add(new GapPrint(helper.createPrint(4, 2), 5));
        }

        return weekGrid;
    }

    /**
     * Returns the employees preference list.
     * 
     * @return employees list or null
     */
    public Collection<EmployeePreference> getEmployeePreferences() {
        return Collections.unmodifiableList(this.preferences);
    }

    /**
     * Return the first day of week.
     * 
     * @return the first day of week value.
     */
    public int getFirstDayOfWeek() {
        return this.firstDayOfWeek;
    }

    /**
     * Return the non-availabilities.
     * 
     * @return
     */
    public Collection<NonAvailability> getNonAvailabilities() {
        return Collections.unmodifiableList(this.nonAvailabilities);
    }

    /**
     * Returns the tasks being printed by this factory.
     * 
     * @return
     */
    public Collection<Task> getTasks() {
        return Collections.unmodifiableCollection(this.tasks);
    }

    /**
     * Sets the list of employees preferences.
     * 
     * @param prefs
     *            a list or null
     */
    public void setEmployeePreferences(Collection<EmployeePreference> prefs) {
        this.preferences = new ArrayList<EmployeePreference>(prefs);
    }

    /**
     * Sets the first day of week value.
     * 
     * @param firstDayOfWeek
     */
    public void setFirstDayOfWeek(int firstDayOfWeek) {
        this.firstDayOfWeek = firstDayOfWeek;
    }

    /**
     * Sets the non-availabilities to be printed. There is no filter on the data provided.
     * 
     * @param nonAvailabilities
     */
    public void setNonAvailabilties(Collection<NonAvailability> nonAvailabilities) {
        this.nonAvailabilities = new ArrayList<NonAvailability>(nonAvailabilities);
    }

    /**
     * Sets the tasks to be printed.
     * <p>
     * User may sets task for a week or a single day.
     * 
     * @param tasks
     */
    public void setTasks(Collection<Task> tasks) {
        this.tasks = new ArrayList<Task>(tasks);
    }

}
