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

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

import java.util.Date;

import org.eclipse.core.databinding.AggregateValidationStatus;
import org.eclipse.core.databinding.BindingProperties;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.ObservablesManager;
import org.eclipse.core.databinding.UpdateValueStrategy;
import org.eclipse.core.databinding.beans.BeanProperties;
import org.eclipse.core.databinding.conversion.Converter;
import org.eclipse.core.databinding.observable.set.IObservableSet;
import org.eclipse.core.databinding.observable.value.DateAndTimeObservableValue;
import org.eclipse.core.databinding.observable.value.WritableValue;
import org.eclipse.core.databinding.property.value.IValueProperty;
import org.eclipse.core.databinding.validation.MultiValidator;
import org.eclipse.core.databinding.validation.ValidationStatus;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.databinding.dialog.TitleAreaDialogSupport;
import org.eclipse.jface.databinding.swt.SWTObservables;
import org.eclipse.jface.databinding.viewers.ViewerSupport;
import org.eclipse.jface.databinding.viewers.ViewersObservables;
import org.eclipse.jface.dialogs.TitleAreaDialog;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.jface.window.IShellProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.DateTime;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;

import com.patrikdufresne.jface.databinding.datetime.DateTimeSupport;
import com.patrikdufresne.jface.databinding.viewers.PropertyViewerComparator;
import com.patrikdufresne.jface.dialogs.DefaultValidationMessageProvider;
import com.patrikdufresne.jface.resource.OverlayImageDescriptor;
import com.patrikdufresne.util.DateFormatRegistry;
import com.planimod.core.PlanimodManagers;
import com.planimod.core.Team;
import com.planimod.core.comparators.TeamComparators;
import com.planimod.ui.databinding.Converters;
import com.planimod.ui.databinding.manager.PlanimodObservables;
import com.planimod.ui.theme.Resources;

/**
 * Dialog to create or edit a non-availability event.
 * 
 * @author Patrik Dufresne
 * 
 */
public class ShiftDialog extends TitleAreaDialog {

    /**
     * The dialog style constant for editing event behavior.
     */
    public static final int EDIT = 1;
    /**
     * The dialog style constant for creating event behavior.
     */
    public static final int NEW = 0;

    /**
     * Check the style flag.
     * 
     * @param style
     * @return
     */
    private static int checkStyle(int style) {
        return style == NEW ? NEW : EDIT;
    }

    private DataBindingContext dbc;
    /**
     * Observable ending date.
     */
    protected WritableValue end;
    /**
     * Selected end date.
     */
    /**
     * Widget to select the ending date.
     */
    private DateTime endDate;
    /**
     * Widget to select the ending time.
     */
    private Combo endTime;

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

    private ObservablesManager om;

    /**
     * Selected shift.
     */
    protected WritableValue shift;
    /**
     * Widget to select the shift.
     */
    private ComboViewer teamViewer;
    /**
     * Observable starting date.
     */
    protected WritableValue start;
    /**
     * Widget to select start date.
     */
    private DateTime startDate;
    /**
     * Widget to select start time.
     */
    private Combo startTime;
    /**
     * Define the dialog style.
     */
    private int style;

    public ShiftDialog(IShellProvider provider, PlanimodManagers managers, int style) {
        this(provider.getShell(), managers, style);
    }

    /**
     * Create a new dialog.
     * 
     * @param parentShell
     *            the parent shell, or <code>null</code> to create a top-level shell
     */
    public ShiftDialog(Shell parentShell, PlanimodManagers managers, int style) {
        super(parentShell);
        if (managers == null) {
            throw new NullPointerException();
        }
        this.style = checkStyle(style);
        this.managers = managers;
        this.start = new WritableValue(new Date(), Date.class);
        this.end = new WritableValue(new Date(), Date.class);
        this.shift = new WritableValue(null, Team.class);
    }

    /**
     * Sets the binding between the values and the widgets.
     */
    protected void bindValues() {
        /*
         * Bind start date time
         */
        WritableValue startTimeValue = new WritableValue(null, Date.class);
        WritableValue endTimeValue = new WritableValue(null, Date.class);
        DateTimeSupport startTimeSupport = DateTimeSupport.create(this.startTime, this.dbc, startTimeValue, DateFormatRegistry
                .getFormat(DateFormatRegistry.TIME | DateFormatRegistry.MEDIUM), DateTimeSupport.STEP_60);

        this.dbc.bindValue(new DateAndTimeObservableValue(SWTObservables.observeSelection(this.startDate), startTimeValue), this.start);
        if (this.startTime.getLayoutData() instanceof GridData) {
            ((GridData) this.startTime.getLayoutData()).widthHint = startTimeSupport.getWidthHint();
        }

        DateTimeSupport endTimeSupport = DateTimeSupport.create(this.endTime, this.dbc, endTimeValue, DateFormatRegistry.getFormat(DateFormatRegistry.TIME
                | DateFormatRegistry.MEDIUM), DateTimeSupport.STEP_60);
        this.dbc.bindValue(new DateAndTimeObservableValue(SWTObservables.observeSelection(this.endDate), endTimeValue), this.end);
        if (this.endTime.getLayoutData() instanceof GridData) {
            ((GridData) this.endTime.getLayoutData()).widthHint = endTimeSupport.getWidthHint();
        }

        /*
         * Bind team viewer.
         */
        IObservableSet teams = PlanimodObservables.listTeam(this.managers);
        IValueProperty teamLabel = BeanProperties.value(Team.class, Team.NAME).value(BindingProperties.convertedValue(Converters.removeFrontNumber()));
        this.teamViewer.setComparator(new PropertyViewerComparator(TeamComparators.byName()));
        ViewerSupport.bind(this.teamViewer, teams, teamLabel);
        this.dbc.bindValue(ViewersObservables.observeSingleSelection(this.teamViewer), this.shift);

        /*
         * Create validator for shift selection
         */
        MultiValidator validator = new MultiValidator() {
            @Override
            protected IStatus validate() {
                if (ShiftDialog.this.shift.getValue() == null) {
                    return ValidationStatus.error(_("Sélectionnez un équipe de travail."));
                }
                return ValidationStatus.ok();
            }
        };
        this.dbc.addValidationStatusProvider(validator);

        /*
         * Create the multi-validator for start and end time
         */
        validator = new MultiValidator() {
            @Override
            protected IStatus validate() {
                // Calculate the validation status
                Date start = (Date) ShiftDialog.this.start.getValue();
                Date end = (Date) ShiftDialog.this.end.getValue();
                if (start == null || end == null || start.compareTo(end) > 0) {
                    return ValidationStatus.error(_("Entrez la date de début et la date de fin."));
                }
                return ValidationStatus.ok();
            }
        };
        this.dbc.addValidationStatusProvider(validator);

        // Bind the title area to the binding context (to display error message)
        TitleAreaDialogSupport.create(this, this.dbc).setValidationMessageProvider(
                new DefaultValidationMessageProvider(_("Sélectionnez l'équipe de travail associé et modifiez la plage horaire.")));

        // Bind the status to the OK button
        UpdateValueStrategy modelToTarget = new UpdateValueStrategy();
        modelToTarget.setConverter(new Converter(IStatus.class, Boolean.TYPE) {
            @Override
            public Object convert(Object fromObject) {
                return Boolean.valueOf(((IStatus) fromObject).getSeverity() == IStatus.OK);
            }
        });
        this.dbc.bindValue(SWTObservables.observeEnabled(this.getButton(OK)), new AggregateValidationStatus(
                this.dbc.getValidationStatusProviders(),
                AggregateValidationStatus.MAX_SEVERITY), new UpdateValueStrategy(UpdateValueStrategy.POLICY_NEVER), modelToTarget);

    }

    /**
     * This implementation sets the binding.
     */
    @Override
    protected Control createContents(Composite parent) {
        Control control = super.createContents(parent);
        /*
         * Bind widgets
         */
        this.dbc = new DataBindingContext();
        this.om = new ObservablesManager();
        this.om.runAndCollect(new Runnable() {
            @Override
            public void run() {
                bindValues();
            }
        });
        this.om.addObservablesFromContext(this.dbc, true, true);
        return control;

    }

    /**
     * This implementation create required widgets to edit a calendar event.
     * 
     * @param parent
     * @return
     */
    @Override
    protected Control createDialogArea(Composite parent) {
        Composite comp = (Composite) super.createDialogArea(parent);

        Composite composite = new Composite(comp, SWT.NONE);
        composite.setLayout(new GridLayout(1, false));
        composite.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true));

        // Setup the title area
        getShell().setText(this.style == NEW ? _("Ajouter") : _("Modifier"));
        setTitle(this.style == NEW ? _("Ajouter un quart de travail") : _("Modifier un quart de travail"));
        setTitleImage(Resources.getImage(Resources.DLG_EDIT_TITLE_IMAGE));

        // Employee
        Composite compShift = new Composite(composite, SWT.NONE);
        GridLayout layout = new GridLayout(2, false);
        layout.marginHeight = 0;
        layout.marginWidth = 0;
        layout.verticalSpacing = 0;
        compShift.setLayout(layout);
        compShift.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
        Label label = new Label(compShift, SWT.NONE);
        label.setText(_("Équipe de travaille :"));
        this.teamViewer = new ComboViewer(compShift);
        this.teamViewer.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));

        /*
         * Create start date To End date widgets
         */
        Composite compDates = new Composite(composite, SWT.NONE);
        layout = new GridLayout(5, false);
        layout.marginHeight = 0;
        layout.marginWidth = 0;
        layout.verticalSpacing = 0;
        compDates.setLayout(layout);
        this.startDate = new DateTime(compDates, SWT.DATE | SWT.DROP_DOWN | SWT.BORDER);
        this.startDate.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
        this.startTime = new Combo(compDates, SWT.BORDER);
        this.startTime.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));

        label = new Label(compDates, SWT.NONE);
        label.setText(_("à"));
        this.endDate = new DateTime(compDates, SWT.DATE | SWT.DROP_DOWN | SWT.BORDER);
        this.endDate.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
        this.endTime = new Combo(compDates, SWT.BORDER);
        this.endTime.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));

        // Build the separator line
        Label separator = new Label(comp, SWT.HORIZONTAL | SWT.SEPARATOR);
        separator.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

        return comp;
    }

    /**
     * Returns the end date.
     * 
     * @return
     */
    public Date getEnd() {
        return (Date) this.end.getValue();
    }

    /**
     * Return the employee.
     * 
     * @return
     */
    public Team getShift() {
        return (Team) this.shift.getValue();
    }

    /**
     * Returns the start date.
     * 
     * @return
     */
    public Date getStart() {
        return (Date) this.start.getValue();
    }

    /**
     * Sets the end date.
     * 
     * @param end
     */
    public void setEnd(Date end) {
        if (end == null) {
            this.end.setValue(new Date());
        } else {
            this.end.setValue(end);
        }
    }

    /**
     * Set the employee.
     * 
     * @param shift
     */
    public void setShift(Team shift) {
        this.shift.setValue(shift);
    }

    /**
     * Sets the start date.
     * 
     * @param start
     */
    public void setStart(Date start) {
        if (start == null) {
            this.start.setValue(new Date());
        } else {
            this.start.setValue(start);
        }
    }
}
