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

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

import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.databinding.conversion.Converter;
import org.eclipse.core.databinding.observable.Observables;
import org.eclipse.core.databinding.observable.set.ListToSetAdapter;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.viewers.IFilter;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.jface.window.IShellProvider;
import org.eclipse.jface.window.Window;
import com.patrikdufresne.jface.databinding.collections.FilteredObservableSet;
import com.patrikdufresne.jface.filter.PatternsFilter;
import com.patrikdufresne.jface.viewers.TextProposalViewer;
import com.patrikdufresne.managers.ManagerException;
import com.patrikdufresne.printing.IPrintFactory;
import com.patrikdufresne.printing.PrintAction;
import com.planimod.core.ApplicationSetting;
import com.planimod.core.ConcreteTimeRange;
import com.planimod.core.NonAvailability;
import com.planimod.core.PlanimodManagers;
import com.planimod.core.TimeRange;
import com.planimod.core.TimeRanges;
import com.planimod.ui.ApplicationSettingPreferenceStore;
import com.planimod.ui.PlanimodPolicy;
import com.planimod.ui.preference.PlanimodPreferences;

/**
 * This action intend to print the qualification.
 * 
 * @author Patrik Dufresne
 */
public class NonAvailabilityPrintAction extends PrintAction {

    /**
     * Property name of an action's date (value <code>"date"</code>). The date property is used to know the time range
     * to be printed by this action.
     */
    public static final String DATE = "date";

    /**
     * Define the preference key for this action
     */
    private static final String PREF_KEY = "NonAvailabilityPrintAction";

    private static final String PREFERENCE_END_DATE = ".endDate";

    private static final String PREFERENCE_FILTER = ".filter";

    private static final String PREFERENCE_START_DATE = ".startDate";

    private static final String PREFERENCE_TITLE = ".title";

    /**
     * Define the date state to update.
     */
    private Date date;

    private String filter;

    private int firstDayOfWeek;

    /**
     * Managers to use.
     */
    private PlanimodManagers managers;

    private TimeRange range;

    /**
     * Print title.
     */
    private String title;

    /**
     * Create a new Print Employee Action
     * 
     * @param shell
     *            A Shell provider used to create new windows
     * @param operation
     *            type of operation : PRINT or PREVIEW
     */
    public NonAvailabilityPrintAction(PlanimodManagers managers, IShellProvider shell, int operation, IRunnableContext context) {
        super(shell, new NonAvailabilityPrintFactory(), operation, context);
        if (managers == null) {
            throw new IllegalArgumentException();
        }
        this.managers = managers;
        setPrefKey(PREF_KEY);
        setPreferenceStore(PlanimodPreferences.getPreferenceStore());
        setEnabled(canRun());
        if (operation == PrintAction.PRINT_ACTION) {
            setText(_("Imprimer calendrier d'absence"));
        } else {
            setText(_("Aperçu avant impression"));
        }
    }

    /**
     * Returns True if the action can be run.
     * 
     * @return
     */
    protected boolean canRun() {
        return this.date != null;
    }

    /**
     * Returns the current date state of null if not define.
     * 
     * @return the date state or null.
     */
    public Date getDate() {
        return this.date;
    }

    /**
     * Returns the managers used by this action.
     * 
     * @return
     */
    protected PlanimodManagers getManagers() {
        return this.managers;
    }

    /**
     * This implementation set the employee to print
     * 
     * @param factory
     *            the factory
     */
    @Override
    protected void initFactory(IPrintFactory factory) {
        // Get date from state
        if (this.date == null) {
            return;
        }
        // Get matching event for the time range.
        List<NonAvailability> events;
        try {
            events = this.managers.getNonAvailabilityManager().list(this.range.getStartDate(), this.range.getEndDate());
        } catch (ManagerException e) {
            PlanimodPolicy.showException(e);
            return;
        }
        // Filter events using the FilteredObservableSet
        PatternsFilter patternFilter = PatternsFilter.create(this.filter);
        Iterator<NonAvailability> iter = events.iterator();
        while (iter.hasNext()) {
            NonAvailability na = iter.next();
            if (!patternFilter.select(na.getSummary())) {
                iter.remove();
            }
        }
        // Initialize the factory.
        ((NonAvailabilityPrintFactory) factory).setTitle(this.title);
        ((NonAvailabilityPrintFactory) factory).setFirstDayOfWeek(this.firstDayOfWeek);
        ((NonAvailabilityPrintFactory) factory).setTimeRange(this.range);
        ((NonAvailabilityPrintFactory) factory).setCalendarEvents(events);
    }

    /**
     * Restore the dialog state using the preference store.
     * 
     * @param dlg
     * @param store
     */
    private void restorePreferences(NonAvailabilityPrintDialog dlg, IPreferenceStore store) {
        if (store == null) {
            return;
        }
        // Title
        if (store.contains(PREF_KEY + PREFERENCE_TITLE)) {
            dlg.setPrintTitle(store.getString(PREF_KEY + PREFERENCE_TITLE));
        } else {
            dlg.setPrintTitle(_("Calendrier d'absence"));
        }
        long start = store.getLong(PREF_KEY + PREFERENCE_START_DATE);
        if (start != 0) {
            dlg.setStart(new Date(start));
        } else {
            dlg.setStart(TimeRanges.getMonthStart(this.date));
        }
        long end = store.getLong(PREF_KEY + PREFERENCE_START_DATE);
        if (end != 0) {
            dlg.setEnd(new Date(end));
        } else {
            dlg.setEnd(TimeRanges.getMonthStart(this.date));
        }
        String filter = store.getString(PREF_KEY + PREFERENCE_FILTER);
        if (filter != null) {
            dlg.setFilter(filter);
        } else {
            dlg.setFilter(_("vacances, vaco"));
        }
    }

    @Override
    public void run() {
        // Load the preference store from the managers.
        IPreferenceStore store;
        try {
            store = new ApplicationSettingPreferenceStore(getManagers());
        } catch (ManagerException e) {
            PlanimodPolicy.showException(e);
            return;
        }
        // Open a dialog to let the user select the printing time range.
        NonAvailabilityPrintDialog dlg = new NonAvailabilityPrintDialog(getShellProvider().getShell());
        restorePreferences(dlg, store);
        if (dlg.open() != Window.OK) {
            // Operation cancel by user.
            return;
        }
        if (dlg.getStart() == null || dlg.getEnd() == null) {
            return;
        }
        // Save user selection in preferences.
        savePreferences(dlg, store);
        // Keep reference on the user selection.
        this.firstDayOfWeek = store.getInt(ApplicationSetting.FIRST_DAY_OF_WEEK);
        this.title = dlg.getPrintTitle();
        this.range = new ConcreteTimeRange(TimeRanges.getFirstDayOfFirstWeekOfMonth(dlg.getStart(), this.firstDayOfWeek), TimeRanges
                .getLastDayOfLastWeekOfMonth(dlg.getEnd(), this.firstDayOfWeek));
        this.filter = dlg.getFilter();
        // Generate the print.
        super.run();
    }

    private void savePreferences(NonAvailabilityPrintDialog dlg, IPreferenceStore store) {
        if (store == null) {
            return;
        }
        // Title
        store.setValue(PREF_KEY + PREFERENCE_TITLE, dlg.getPrintTitle());
        // Start
        if (dlg.getStart() != null) {
            store.setValue(PREF_KEY + PREFERENCE_START_DATE, dlg.getStart().getTime());
        }
        // End
        if (dlg.getEnd() != null) {
            store.setValue(PREF_KEY + PREFERENCE_END_DATE, dlg.getEnd().getTime());
        }
        // Filter
        store.setValue(PREF_KEY + PREFERENCE_FILTER, dlg.getFilter());
    }

    /**
     * Sets the date state to be updated.
     * 
     * @param date
     *            the state.
     */
    public void setDate(Date date) {
        if ((this.date == null && date != null) || (this.date != null && date == null) || (this.date != null && date != null && !date.equals(this.date))) {
            Date oldValue = this.date;
            this.date = date;
            firePropertyChange(DATE, oldValue, this.date);
            setEnabled(canRun());
        }
    }
}
