/*
 * Copyright (c) 2011, Patrik Dufresne. All rights reserved.
 * Patrik Dufresne PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package com.planimod.ui.views.planif.printing;

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.List;
import java.util.Map;

import net.sf.paperclips.DefaultGridLook;
import net.sf.paperclips.GridPrint;
import net.sf.paperclips.LineBorder;
import net.sf.paperclips.Print;
import net.sf.paperclips.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.patrikdufresne.util.Localized;
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 {

	/**
	 * Used for localization.
	 */
	static Localized localized = Localized
			.load(PlanifDaySummaryPrintFactory.class);

	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(Localized.get(PlanifDaySummaryPrintFactory.class,
				"PlanifDaySummaryPrintFactory.jobName")); //$NON-NLS-1$

		// 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"); //$NON-NLS-1$
		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.remove(null);
			if (over.size() > 0) {
				// Create group
				helper.addGroup(
						localized
								.get("PlanifDaySummaryPrintFactory.over").toUpperCase(), true); //$NON-NLS-1$

				// Add employee to section
				Collections.sort(over, EmployeeComparators.byName());
				for (Employee employee : over) {
					helper.addItem(PlanimodPrintUtil.getName(employee));
				}

			}

			/*
			 * Create vacant group
			 */
			Collection<Task> vacants = dayData.listVacantTasksByTeam(team);
			if (vacants.size() > 0) {
				helper.addGroup(
						(vacants.size() == 1 ? localized
								.get("PlanifSummaryPrintFactory.vacant") //$NON-NLS-1$
								: localized
										.get("PlanifSummaryPrintFactory.vacants")) //$NON-NLS-1$
								.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(
						localized
								.get("PlanifSummaryPrintFactory.nonAvailabilities.team") //$NON-NLS-1$
								.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(
					localized.get("PlanifSummaryPrintFactory.nonAvailabilities"), //$NON-NLS-1$
					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;
	}

	/**
	 * 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 = localized.format(
						"PlanifDaySummaryPrintFactory.title", title); //$NON-NLS-1$
				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);
	}

}
