/**
 * 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.getProduction;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

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.Product;
import com.planimod.core.Section;
import com.planimod.core.Task;
import com.planimod.core.Team;
import com.planimod.core.TimeRange;
import com.planimod.core.comparators.EmployeeComparators;
import com.planimod.core.comparators.EmployeePreferenceComparators;
import com.planimod.core.comparators.NonAvailabilityComparators;
import com.planimod.core.comparators.ProductComparators;
import com.planimod.core.comparators.SectionComparators;
import com.planimod.core.comparators.TeamComparators;
import com.planimod.core.comparators.TimeRangeComparators;
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 PlanifDaySummaryPrintFactory extends PlanimodPrintFactory {

    private int firstDayOfWeek;

    private List<NonAvailability> nonAvailabilities;

    private List<EmployeePreference> preferences;

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

    /**
     * Create a new factory
     */
    public PlanifDaySummaryPrintFactory() {
        super(_("Planification main d'oeuvre journalière"));

        // Sets first day of week using the localize calendar.
        Calendar cal = Calendar.getInstance();
        this.firstDayOfWeek = cal.getFirstDayOfWeek();
    }

    /**
     * Create the print of a day
     * 
     * @param parent
     *            parent grid
     * @param weekData
     *            the week data
     * @param dayData
     *            the day data
     */
    protected Print createDayGrid(DataTable weekData, DataTable dayData) {

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

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

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

            // Add a team labels to weekPrint
            int countEmployee = dayData.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);
            dayGrid.add(print);

            GridHelper helper = new GridHelper();

            /*
             * Create a print group for each Section.
             */
            List<Section> sections = new ArrayList<Section>(dayData.listSectionByTeam(team));
            Collections.sort(sections, SectionComparators.byName());
            for (Section s : sections) {

                List<Employee> employees = new ArrayList<Employee>(dayData.listEmployeeByTeamAndSection(team, s));
                if (employees.size() > 0) {
                    // Create section
                    helper.addGroup(PlanimodPrintUtil.getName(s).toUpperCase());

                    // Add employee to the section.
                    Collections.sort(employees, EmployeePreferenceComparators.byPreferredSectionAndSeniority(s, this.preferences));
                    int count = 1;
                    for (Employee employee : employees) {
                        boolean swappable = !weekData.listEmployeeByTeam(team).contains(employee);
                        boolean classified = dayData.listEmployeeWithClassifiedPositionByTeam(team).contains(employee);
                        helper.addItem(getEmployeeName(count, employee, classified, swappable));
                        count++;
                    }
                }

            }

            // Identifies the 'over' employee -- employee not working
            List<Employee> over = new ArrayList<Employee>(weekData.listEmployeeByTeam(team));
            over.removeAll(dayData.listEmployee());
            over.removeAll(dayData.listEmployeeNotAvailableByTeam(team));
            over.removeAll(dayData.listEmployeeWithNonAvailabilityWithoutTeam());
            over.remove(null);
            if (over.size() > 0) {
                // Create group
                helper.addGroup(_("Autre").toUpperCase(), true);

                // Add employee to section
                int count = 1;
                Collections.sort(over, EmployeeComparators.bySeniority());
                for (Employee employee : over) {
                    Section sec = getEmployeePreferredSection(employee);
                    helper.addItem(getEmployeeName(count, employee, false, false, sec));
                    count++;
                }

            }

            /*
             * Create vacant group
             */
            Collection<Task> vacants = dayData.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(PlanimodPrintUtil.getName(pos));
                }
            }

            // Check if their is non-avails
            List<NonAvailability> list = new ArrayList<NonAvailability>(weekData.listVisibleNonAvailabilityByTeam(team));
            list.retainAll(dayData.listVisibleNonAvailabilities());
            if (list.size() > 0) {
                // Create group
                helper.addGroup(_("Absences").toUpperCase(), true);

                // Add employee to section
                Collections.sort(list, NonAvailabilityComparators.byEmployeeName());
                for (NonAvailability na : list) {
                    helper.addItem(getNonAvailabilityDesc(dayData.getRange(), na));
                }

            }

            // Print production information
            if (sections.size() > 1) {
                // Create group
                helper.addGroup(null);

                // Add productions
                Map<Product, Integer> productions = dayData.listProductionByTeam(team);
                List<Product> products = new ArrayList<Product>(productions.keySet());
                Collections.sort(products, ProductComparators.byRefIdFamilyName());
                for (Product product : products) {
                    helper.addItem(getProduction(product, productions.get(product)));
                }

            }

            dayGrid.add(new GapPrint(helper.createPrint(4, 2), 5, 5, 25, 5));

        }

        /*
         * Print non-availabilities for the time range
         */
        List<NonAvailability> currentNonAvailabilities = new ArrayList<NonAvailability>(weekData.listVisibleNonAvailabilityWithoutTeam());
        currentNonAvailabilities.retainAll(dayData.listVisibleNonAvailabilities());
        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);
            dayGrid.add(print);

            GridHelper helper = new GridHelper();

            // Create the section
            helper.addGroup(null);

            // Add non-availabilities
            Collections.sort(currentNonAvailabilities, NonAvailabilityComparators.byEmployeeName());
            for (NonAvailability na : currentNonAvailabilities) {
                helper.addItem(getNonAvailabilityDesc(dayData.getRange(), na));
            }

            dayGrid.add(new GapPrint(helper.createPrint(3, 2), 5));
        }

        return dayGrid;
    }

    /**
     * Return employee preferred section.
     * 
     * @param employee
     * @return prefered section or null.
     */
    private Section getEmployeePreferredSection(Employee e) {
        // Search the preference.
        Iterator<EmployeePreference> iter = this.preferences.iterator();
        EmployeePreference pref = null;
        while (iter.hasNext() && !(pref = iter.next()).getEmployee().equals(e)) {
            // Nothing to do;
        }
        return pref != null ? pref.getPreferredSection() : null;
    }

    /**
     * This implementation create a new print body.
     */
    @Override
    protected void createSections() {
        /*
         * Loop on week time range
         */
        List<TimeRange> weeks = new ArrayList<TimeRange>(DataTable.computeWeekTimeRange(this.tasks, this.firstDayOfWeek));
        Collections.sort(weeks, TimeRangeComparators.byDate());
        for (TimeRange week : weeks) {

            // Create data structure from the list of task for the current week
            final DataTable weekData = new DataTable(week, this.tasks, this.preferences, this.nonAvailabilities);

            List<TimeRange> days = new ArrayList<TimeRange>(DataTable.computeDayTimeRange(weekData.listTasks()));
            Collections.sort(days, TimeRangeComparators.byDate());
            for (TimeRange day : days) {

                final DataTable dayData = new DataTable(day, this.tasks, this.preferences, this.nonAvailabilities);
                String title = DateFormatRegistry.format(dayData.getRange().getStartDate(), DateFormatRegistry.DATE | DateFormatRegistry.LONG, true);
                title = _("Planification main d'oeuvre journaliére\r\n%s", title);
                addSection(new PlanimodSection(title, getSubTitle()) {
                    @Override
                    public Print createBodyArea() {
                        return createDayGrid(weekData, dayData);
                    }
                });

            }
        }
    }

    /**
     * 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);
    }

}
