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

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

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.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.ObservableMapLabelProvider;
import org.eclipse.jface.databinding.viewers.ObservableSetContentProvider;
import org.eclipse.jface.databinding.viewers.ViewerSupport;
import org.eclipse.jface.databinding.viewers.ViewersObservables;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.TitleAreaDialog;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;

import com.patrikdufresne.jface.databinding.viewers.PropertyViewerComparator;
import com.patrikdufresne.jface.databinding.viewers.TextProposalViewerUpdater;
import com.patrikdufresne.jface.dialogs.DefaultValidationMessageProvider;
import com.patrikdufresne.jface.viewers.TextProposalViewer;
import com.planimod.core.PlanimodManagers;
import com.planimod.core.Position;
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;

/**
 * This dialog is used to select the preferred position.
 * 
 * @author Patrik Dufresne
 * 
 */
public class PreferredPositionDialog extends TitleAreaDialog {

    public static final int CLEAR_ID = 3;

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

    /**
     * The selected position value.
     */
    protected WritableValue position;

    /**
     * Viewer to select the preferred position
     */
    private TextProposalViewer positionViewer;

    /**
     * The selected shifts value.
     */
    protected WritableValue shift;

    /**
     * Viewer to select the shifts for the preferred position.
     */
    private ComboViewer teamViewer;

    private DataBindingContext dbc;

    private ObservablesManager om;

    /**
     * Create a new dialog.
     * 
     * @param parentShell
     *            the parent shell
     */
    public PreferredPositionDialog(Shell parentShell, PlanimodManagers managers) {
        super(parentShell);
        this.managers = managers;
        this.shift = new WritableValue(null, Team.class);
        this.position = new WritableValue(null, Position.class);
    }

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

    /**
     * Used to bind the values to the widget.
     */
    protected void bindValues() {
        /*
         * Bind the shift 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, null, null);

        /*
         * Bind the position viewer
         */
        IObservableSet classifiedPositions = new ClassifiedPositionComputedSet(this.managers);
        IValueProperty positionLabel = BeanProperties.value(Position.class, Position.NAME);
        this.positionViewer.setContentProvider(new ObservableSetContentProvider(new TextProposalViewerUpdater(this.positionViewer)));
        this.positionViewer.setLabelProvider(new ObservableMapLabelProvider(positionLabel.observeDetail(classifiedPositions)));
        this.positionViewer.setInput(classifiedPositions);

        this.dbc.bindValue(ViewersObservables.observeSingleSelection(this.positionViewer), this.position, null, null);

        // Create the multi-validator for start and end time
        MultiValidator validator = new MultiValidator() {
            @Override
            protected IStatus validate() {
                // Calculate the validation status for preferred position.
                Team shift = (Team) PreferredPositionDialog.this.shift.getValue();
                Position position = (Position) PreferredPositionDialog.this.position.getValue();
                if (shift == null && position == null) {
                    return ValidationStatus.info(_("Aucun poste attitré."));
                } else if (!(shift != null && position != null) && (shift != null || position != null)) {
                    return ValidationStatus.error(_("Sélectionnez l'équipe de travail ainsi que le poste attitré de l'employé."));
                }

                return ValidationStatus.ok();
            }
        };
        this.dbc.addValidationStatusProvider(validator);

        // Bind the title area to the binding context
        TitleAreaDialogSupport.create(this, this.dbc).setValidationMessageProvider(
                new DefaultValidationMessageProvider(_("Sélectionnez l'équipe de travail ainsi que le poste attitré de l'employé.")));

        // 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);
            }
        });
        UpdateValueStrategy targetToModel = new UpdateValueStrategy();
        targetToModel.setConverter(new Converter(Boolean.TYPE, IStatus.class) {
            @Override
            public Object convert(Object fromObject) {
                return ((Boolean) fromObject).booleanValue() ? ValidationStatus.ok() : ValidationStatus.error("");
            }
        });
        this.dbc.bindValue(SWTObservables.observeEnabled(this.getButton(OK)), new AggregateValidationStatus(
                this.dbc.getBindings(),
                AggregateValidationStatus.MAX_SEVERITY), targetToModel, modelToTarget);
    }

    /**
     * This implementation create the required widget to select the preferred position for an employee.
     */
    @Override
    protected Control createDialogArea(Composite parent) {
        Composite comp = (Composite) super.createDialogArea(parent);

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

        // Setup the title area
        getShell().setText(_("Poste attitré"));
        setTitle(_("Modifier le poste attitré d'un employé"));
        setTitleImage(Resources.getImage(Resources.DLG_EDIT_TITLE_IMAGE));

        // Shift selection
        Label label = new Label(composite, SWT.NONE);
        label.setText(_("équipe de travail :"));
        this.teamViewer = new ComboViewer(composite);
        this.teamViewer.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));

        // Preferred position
        label = new Label(composite, SWT.NONE);
        label.setText(_("Poste :"));
        this.positionViewer = new TextProposalViewer(composite);
        this.positionViewer.getControl().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 current position.
     * 
     * @return
     */
    public Position getPosition() {
        return (Position) this.position.getValue();
    }

    /**
     * Returns the selected shift.
     * 
     * @return a shift or null
     */
    public Team getShift() {
        return (Team) this.shift.getValue();
    }

    /**
     * Sets the selected position.
     * 
     * @param position
     *            a position or null.
     */
    public void setPosition(Position position) {
        this.position.setValue(position);
    }

    /**
     * Sets the selected shift.
     * 
     * @param shift
     *            a shift or null
     */
    public void setShift(Team shift) {
        this.shift.setValue(shift);
    }

    @Override
    protected void createButtonsForButtonBar(Composite parent) {
        // create OK and Cancel buttons by default
        createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true);
        createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false);
        createButton(parent, PreferredPositionDialog.CLEAR_ID, _("Supprimer"), false);
    }

    @Override
    protected void buttonPressed(int buttonId) {
        if (buttonId == CLEAR_ID) {
            clearPressed();
        } else {
            super.buttonPressed(buttonId);
        }

    }

    protected void clearPressed() {
        setReturnCode(CLEAR_ID);
        close();
    }

}
