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

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

import org.eclipse.core.databinding.BindingProperties;
import org.eclipse.core.databinding.UpdateValueStrategy;
import org.eclipse.core.databinding.beans.BeanProperties;
import org.eclipse.core.databinding.observable.map.IObservableMap;
import org.eclipse.core.databinding.observable.set.IObservableSet;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.property.value.IValueProperty;
import org.eclipse.core.internal.databinding.property.value.SelfValueProperty;
import org.eclipse.jface.action.ActionContributionItem;
import org.eclipse.jface.action.Separator;
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.ViewersObservables;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.viewers.ComboBoxViewerCellEditor;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
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.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.ui.forms.widgets.FormText;

import com.patrikdufresne.jface.databinding.ControlStyleSupport;
import com.patrikdufresne.jface.databinding.ControlStyleUpdater;
import com.patrikdufresne.jface.databinding.collections.CollectionProperties;
import com.patrikdufresne.jface.databinding.forms.FormWidgetProperties;
import com.patrikdufresne.jface.databinding.util.JFaceProperties;
import com.patrikdufresne.jface.databinding.viewers.ColumnSupport;
import com.patrikdufresne.jface.databinding.viewers.PropertyViewerComparator;
import com.patrikdufresne.jface.viewers.ColumnViewerPreferences;
import com.patrikdufresne.managers.databinding.ManagerUpdateValueStrategy;
import com.patrikdufresne.ui.IPageSite;
import com.patrikdufresne.ui.IViewPartPage;
import com.patrikdufresne.ui.SashFactory;
import com.planimod.core.AbstractCalendarEvent;
import com.planimod.core.PlanimodManagers;
import com.planimod.core.Position;
import com.planimod.core.Product;
import com.planimod.core.ProductionEvent;
import com.planimod.core.Section;
import com.planimod.core.Shift;
import com.planimod.core.Task;
import com.planimod.core.Team;
import com.planimod.core.comparators.EmployeeComparators;
import com.planimod.core.comparators.SectionComparators;
import com.planimod.core.comparators.TaskComparators;
import com.planimod.core.planif.GeneratePlanifContext;
import com.planimod.ui.databinding.Converters;
import com.planimod.ui.databinding.DateColorControlStyleSupport;
import com.planimod.ui.databinding.DateFormatRangeValueProperty;
import com.planimod.ui.preference.PlanimodPreferences;
import com.planimod.ui.theme.Resources;
import com.planimod.ui.viewers.EmployeeViewerUtils;
import com.planimod.ui.views.AbstractViewPart;
import com.planimod.ui.views.ChangeDateAction;

/**
 * This windows is used to change the locked status of the planif.
 * 
 * @author Patrik Dufresne
 */
public class ProposedTaskViewPartPage extends AbstractViewPart implements IViewPartPage {

    /**
     * Dot (.) separator.
     */
    private static final String DOT = ".";

    /**
     * The part id.
     */
    public static final String ID = "com.planimod.ui.views.planif.LockedStyleViewPart";

    /**
     * Key use to store the columne preferences.
     */
    private static final String PREFERENCE_COLUMNS_CURRENT_TASK = "LockedStyleViewPart.columns.current";

    /**
     * Key used to store the columns preferences.
     */
    private static final String PREFERENCE_COLUMNS_OFFER_TASK = "LockedStyleViewPart.columns.offer";

    /**
     * Default sash weights.
     */
    private static final int PREFERENCE_SASH1_DEFAULT_WIDTH = 320;

    /**
     * Key used to store the sash weight.
     */
    private static final String PREFERENCE_SASH1_WIDTH = "LockedStyleViewPart.sash1.width";

    /**
     * Default sash weights.
     */
    private static final int[] PREFERENCE_SASH2_DEFAULT_WEIGHTS = new int[] { 50, 50 };

    /**
     * Key used to store the sash weight.
     */
    private static final String PREFERENCE_SASH2_WEIGHTS = "LockedStyleViewPart.sash2.weights";

    /**
     * Label to display the date range.
     */
    private Label dateLabel;

    /**
     * The employee list.
     */
    private TableViewer employeeViewer;

    /**
     * Action to generate planif.
     */
    private GeneratePlanifAction generatePlanifAction;

    /**
     * Action to change the current week
     */
    private ChangeDateAction nextAction;

    /**
     * Action to change the current week
     */
    private ChangeDateAction previousAction;

    /**
     * Viewer used to display the current position.fFs
     */
    private TableViewer taskAssignedViewer;

    /**
     * The position to be offered.
     */
    private TableViewer taskProposedViewer;

    /**
     * Action to change the current week.
     */
    private ChangeDateAction todayAction;

    /**
     * Widget to display the warnings.
     */
    private FormText warningsFormText;

    /**
     * Create a new view part.
     */
    public ProposedTaskViewPartPage() {
        super(ID);
        setTitle(_("Verrouillé"));
    }

    /**
     * This implementation creates all the control required to change the locked status of any planif event.
     */
    @Override
    public void activate(Composite parent) {
        GridLayout layout = GridLayoutFactory.fillDefaults().extendedMargins(5, 5, 0, 5).create();
        parent.setLayout(layout);
        // Create a label to display the date
        this.dateLabel = new Label(parent, SWT.CENTER);
        this.dateLabel.setFont(Resources.getWeekTitleFont());
        this.dateLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
        Composite comp1 = new Composite(parent, SWT.NONE);
        comp1.setLayoutData(GridDataFactory.fillDefaults().grab(true, true).create());
        /*
         * Create the left composite
         */
        createEmployeeViewer(comp1);
        /*
         * Create a second sash form
         */
        SashForm sash2 = createSashForm(
                comp1,
                SWT.VERTICAL,
                PlanimodPreferences.getPreferenceStore(),
                PREFERENCE_SASH2_WEIGHTS,
                PREFERENCE_SASH2_DEFAULT_WEIGHTS);
        // Create the sash
        SashFactory.createLeftPane(comp1, false, PlanimodPreferences.getPreferenceStore(), PREFERENCE_SASH1_WIDTH, PREFERENCE_SASH1_DEFAULT_WIDTH);
        /*
         * Create the right composite
         */
        createTaskProposedViewer(sash2);
        createTaskAssignedViewer(sash2);
        // Populate tool bar.
        fillToolBar();
        // Create binding
        bind();
    }

    /**
     * Binds the planner date selection to a shared state.
     */
    @Override
    protected void bindValues() {
        GeneratePlanifContext context = ((PlanifViewPart) getSite().getParentPart()).getContext();
        IObservableValue week = BeanProperties.value(GeneratePlanifContext.class, GeneratePlanifContext.WEEK).observe(context);
        /*
         * Bind date sate to date label
         */
        getDbc().bindValue(
                SWTObservables.observeText(this.dateLabel),
                BeanProperties.value(GeneratePlanifContext.class, GeneratePlanifContext.START).value(
                        BindingProperties.convertedValue(Converters.weekTitleConverter())).observe(context));
        /*
         * Bind the employee viewer to manager model
         */
        EmployeeForContextComputedSet employees = new EmployeeForContextComputedSet(context);
        IObservableMap employeeLabels = BindingProperties.convertedValue(EmployeeViewerUtils.fullnameConverter()).observeDetail(employees);
        IObservableSet employeeWithTaskProposed = new EmployeesWithTaskProposedForContextComputedSet(context);
        this.employeeViewer.setContentProvider(new ObservableSetContentProvider());
        this.employeeViewer.setLabelProvider(new EmployeeLabelWithAnnotation(employeeLabels, employeeWithTaskProposed));
        this.employeeViewer.setComparator(new PropertyViewerComparator(EmployeeComparators.bySeniority()));
        this.employeeViewer.setInput(employees);
        IObservableValue selectedEmployee = ViewersObservables.observeSingleSelection(this.employeeViewer);
        DateColorControlStyleSupport.create(this.employeeViewer.getTable(), week);
        /*
         * Bind offered position viewer input with employee selection.
         */
        TaskProposedByEmployeeForContextComputedSet taskProposed = new TaskProposedByEmployeeForContextComputedSet(context, selectedEmployee);
        this.taskProposedViewer.setContentProvider(new ObservableSetContentProvider());
        createTaskProposedColumns(this.taskProposedViewer, taskProposed);
        this.taskProposedViewer.setInput(taskProposed);
        // Bind background color
        DateColorControlStyleSupport.create(this.taskProposedViewer.getTable(), week);
        /*
         * Bind warnings
         */
        ProposedWarningComputedSet proposedWarning = new ProposedWarningComputedSet(context, selectedEmployee);
        IObservableValue warningsText = CollectionProperties.set().value(BindingProperties.convertedValue(new ProposedWarningsToStringConverter())).observe(
                proposedWarning);
        getDbc().bindValue(FormWidgetProperties.text(true, false).observe(this.warningsFormText), warningsText);
        ControlStyleSupport.create(this.warningsFormText, warningsText, new ControlStyleUpdater() {

            /**
             * This implementation update the background color of the widget based on the status.
             */
            @Override
            public void update(Control control, Object status) {
                if (!status.toString().isEmpty()) {
                    control.setLayoutData(GridDataFactory.fillDefaults().grab(true, false).create());
                    control.setVisible(true);
                } else {
                    control.setLayoutData(GridDataFactory.fillDefaults().grab(false, false).hint(0, 0).create());
                    control.setVisible(false);
                }
                control.getParent().layout();
            }
        });
        /*
         * Bind the current position viewer.
         */
        TaskByEmployeeForContextComputedSet taskAssigned = new TaskByEmployeeForContextComputedSet(context, selectedEmployee);
        this.taskAssignedViewer.setContentProvider(new ObservableSetContentProvider());
        createTaskAssignedViewerColumns(this.taskAssignedViewer, taskAssigned);
        this.taskAssignedViewer.setInput(taskAssigned);
        DateColorControlStyleSupport.create(this.taskAssignedViewer.getTable(), week);
        // Sets the action state
        getDbc().bindValue(JFaceProperties.value(ChangeDateAction.class, ChangeDateAction.DATE, ChangeDateAction.DATE).observe(this.previousAction), week);
        getDbc().bindValue(JFaceProperties.value(ChangeDateAction.class, ChangeDateAction.DATE, ChangeDateAction.DATE).observe(this.todayAction), week);
        getDbc().bindValue(JFaceProperties.value(ChangeDateAction.class, ChangeDateAction.DATE, ChangeDateAction.DATE).observe(this.nextAction), week);
    }

    /**
     * Create all the control to be displayed in the left side of the view. Mostly it's create the employee list.
     * 
     * @param parent
     * @return
     */
    protected Control createEmployeeViewer(Composite parent) {
        GridLayout layout = GridLayoutFactory.fillDefaults().create();
        Composite comp = new Composite(parent, SWT.NONE);
        comp.setLayout(layout);
        // Create a label for the list
        Label label = new Label(comp, SWT.NONE);
        label.setText(_("Liste des employés"));
        label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
        // Create the list
        this.employeeViewer = new TableViewer(comp);
        this.employeeViewer.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        return comp;
    }

    /**
     * Create the controls to display the current assignment for the selected employees.
     * 
     * @param parent
     * @return
     */
    protected void createTaskAssignedViewer(Composite parent) {
        GridLayout layout = GridLayoutFactory.fillDefaults().spacing(0, 0).create();
        Composite comp = new Composite(parent, SWT.NONE);
        comp.setLayout(layout);
        /*
         * Create a widget for the warnings
         */
        GridData gdata1 = GridDataFactory.fillDefaults().grab(true, false).create();
        this.warningsFormText = new FormText(comp, SWT.MULTI | SWT.WRAP | SWT.READ_ONLY | SWT.NO_FOCUS | SWT.BORDER);
        this.warningsFormText.marginWidth = 1;
        this.warningsFormText.setLayoutData(gdata1);
        this.warningsFormText.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_INFO_BACKGROUND));
        this.warningsFormText.setVisible(false);
        /*
         * Create label for the list
         */
        Label label = new Label(comp, SWT.NONE);
        label.setText(_("Postes actuels"));
        label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
        this.taskAssignedViewer = createTableViewer(comp);
        this.taskAssignedViewer.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        this.taskAssignedViewer.getTable().setLinesVisible(PlanimodPreferences.getPreferenceStore().getBoolean(PlanimodPreferences.SHOW_LINES));
    }

    protected void createTaskAssignedViewerColumns(TableViewer viewer, IObservableSet knownElements) {
        // Product name
        IValueProperty productLabel = BeanProperties.value(Task.class, Task.PRODUCTION_EVENT + DOT + ProductionEvent.PRODUCT + DOT + Product.NAME);
        ColumnSupport.create(viewer, _("Produit"), knownElements, productLabel).setWidth(200).addPropertySorting();
        // Section name
        IValueProperty sectionLabel = BeanProperties.value(Task.class, Task.POSITION + DOT + Position.SECTION + DOT + Section.NAME).value(
                BindingProperties.convertedValue(Converters.removeFrontNumber()));
        ColumnSupport.create(viewer, _("Section"), knownElements, sectionLabel).setWidth(100).addPropertySorting();
        // Position name
        IValueProperty positionLabel = BeanProperties.value(Task.class, Task.POSITION + DOT + Position.NAME);
        ColumnSupport.create(viewer, _("Poste"), knownElements, positionLabel).setWidth(250).addPropertySorting();
        // Employee
        IValueProperty employeeLabel = BeanProperties.value(Task.class, Task.EMPLOYEE).value(
                BindingProperties.convertedValue(EmployeeViewerUtils.fullnameConverter()));
        ColumnSupport.create(viewer, _("Employé"), knownElements, employeeLabel).setWidth(150);
        // Team
        IValueProperty teamLabel = BeanProperties
                .value(Task.class, Task.PRODUCTION_EVENT + DOT + ProductionEvent.SHIFT + DOT + Shift.TEAM + DOT + Team.NAME)
                .value(BindingProperties.convertedValue(Converters.removeFrontNumber()));
        ColumnSupport.create(viewer, _("Équipe"), knownElements, teamLabel).setWidth(150).addPropertySorting(
                new SelfValueProperty(Task.class),
                TaskComparators.byTeam()).activateSorting();
        // Time Range
        IValueProperty timeRangeProperty = DateFormatRangeValueProperty.createTimeMedium();
        IValueProperty startDateProperty = BeanProperties.value(Task.class, AbstractCalendarEvent.START_DATE);
        ColumnSupport.create(viewer, _("Début - Fin"), knownElements, timeRangeProperty).setWidth(90).setWidth(150).addPropertySorting(startDateProperty);
        // Locked
        IValueProperty lockedProperty = BeanProperties.value(Task.class, Task.LOCKED, Boolean.TYPE);
        IValueProperty lockedLabel = lockedProperty.value(BindingProperties.convertedValue(Converters.booleanConverter()));
        ColumnSupport.create(viewer, _("Verrouillé"), knownElements, lockedLabel).addPropertySorting().addCheckboxEditingSupport(
                getDbc(),
                lockedProperty,
                new ManagerUpdateValueStrategy(getSite().getService(PlanimodManagers.class), UpdateValueStrategy.POLICY_CONVERT),
                null);
        // Attach a column preference
        ColumnViewerPreferences.create(viewer, PlanimodPreferences.getPreferenceStore(), PREFERENCE_COLUMNS_CURRENT_TASK);
    }

    /**
     * Creates the columns to display the proposed position.
     * 
     * @param viewer
     * @param knownElements
     * @param selection
     */
    protected void createTaskProposedColumns(TableViewer viewer, IObservableSet knownElements) {
        // Create an observable for the current selection.
        final IObservableValue taskSelection = ViewersObservables.observeSingleSelection(this.taskProposedViewer);
        // Product name
        IValueProperty productLabel = BeanProperties.value(Task.class, Task.PRODUCTION_EVENT + DOT + ProductionEvent.PRODUCT + DOT + Product.NAME);
        ColumnSupport.create(viewer, _("Produit"), knownElements, productLabel).setWidth(200).addPropertySorting();
        // Section name
        IValueProperty sectionProperty = BeanProperties.value(Task.class, Task.POSITION + DOT + Position.SECTION);
        IValueProperty sectionLabel = BeanProperties.value(Task.class, Task.POSITION + DOT + Position.SECTION + DOT + Section.NAME).value(
                BindingProperties.convertedValue(Converters.removeFrontNumber()));
        ColumnSupport.create(viewer, _("Section"), knownElements, sectionLabel).setWidth(100).addPropertySorting(sectionProperty, SectionComparators.byName());
        // Position name
        IValueProperty positionLabel = BeanProperties.value(Task.class, Task.POSITION + DOT + Position.NAME);
        ColumnSupport.create(viewer, _("Poste"), knownElements, positionLabel).setWidth(250).addPropertySorting();
        // Employee -- with list of proposed employee, but not editing.
        IObservableSet proposedEmployees = new EmployeeByTaskProposedForContextComputedSet(
                ((PlanifViewPart) getSite().getParentPart()).getContext(),
                taskSelection);
        IObservableMap proposedEmployeeLabels = BindingProperties.convertedValue(EmployeeViewerUtils.fullnameConverter()).observeDetail(proposedEmployees);
        ComboBoxViewerCellEditor employeeCellEditor = new ComboBoxViewerCellEditor(viewer.getTable());
        ComboViewer comboViewer = employeeCellEditor.getViewer();
        comboViewer.setContentProvider(new ObservableSetContentProvider());
        comboViewer.setLabelProvider(new ObservableMapLabelProvider(proposedEmployeeLabels));
        comboViewer.setComparator(new PropertyViewerComparator(EmployeeComparators.bySeniority()));
        comboViewer.setInput(proposedEmployees);
        IValueProperty employeeProperty = BeanProperties.value(Task.class, Task.EMPLOYEE);
        IValueProperty employeeLabel = employeeProperty.value(BindingProperties.convertedValue(EmployeeViewerUtils.fullnameConverter()));
        ColumnSupport.create(viewer, _("Employé"), knownElements, employeeLabel).setWidth(150).addPropertySorting().addViewerEditingSupport(
                getDbc(),
                employeeProperty,
                employeeCellEditor,
                new UpdateValueStrategy(UpdateValueStrategy.POLICY_NEVER),
                null);
        // Team
        IValueProperty teamLabel = BeanProperties
                .value(Task.class, Task.PRODUCTION_EVENT + DOT + ProductionEvent.SHIFT + DOT + Shift.TEAM + DOT + Team.NAME)
                .value(BindingProperties.convertedValue(Converters.removeFrontNumber()));
        ColumnSupport.create(viewer, _("Équipe"), knownElements, teamLabel).setWidth(150).addPropertySorting(
                new SelfValueProperty(Task.class),
                TaskComparators.byTeam()).activateSorting();
        // Time Range
        IValueProperty timeRangeProperty = DateFormatRangeValueProperty.createTimeMedium();
        IValueProperty startDateProperty = BeanProperties.value(Task.class, AbstractCalendarEvent.START_DATE);
        ColumnSupport.create(viewer, _("Début - Fin"), knownElements, timeRangeProperty).setWidth(90).setWidth(150).addPropertySorting(startDateProperty);
        // Locked
        final IObservableValue selectedEmployee = ViewersObservables.observeSingleSelection(this.employeeViewer);
        IValueProperty lockedProperty = new TaskLockedValueProperty(selectedEmployee);
        IValueProperty lockedLabel = BeanProperties.value(Task.class, Task.LOCKED).value(BindingProperties.convertedValue(Converters.booleanConverter()));
        ColumnSupport.create(viewer, _("Verrouillé"), knownElements, lockedLabel).addPropertySorting().addCheckboxEditingSupport(
                getDbc(),
                lockedProperty,
                new ManagerUpdateValueStrategy(getSite().getService(PlanimodManagers.class), UpdateValueStrategy.POLICY_CONVERT),
                null);
        // Attach a column preference
        ColumnViewerPreferences.create(viewer, PlanimodPreferences.getPreferenceStore(), PREFERENCE_COLUMNS_OFFER_TASK);
    }

    /**
     * Create all the control to be displayed in the right side of the view.
     * 
     * @param parent
     * @return
     */
    protected void createTaskProposedViewer(Composite parent) {
        GridLayout layout = GridLayoutFactory.fillDefaults().create();
        Composite comp = new Composite(parent, SWT.NONE);
        comp.setLayout(layout);
        /*
         * Create label for the list
         */
        Label label = new Label(comp, SWT.NONE);
        label.setText(_("Postes à offrir"));
        label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
        this.taskProposedViewer = createTableViewer(comp);
        this.taskProposedViewer.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        this.taskProposedViewer.getTable().setLinesVisible(PlanimodPreferences.getPreferenceStore().getBoolean(PlanimodPreferences.SHOW_LINES));

    }

    /**
     * This implementation free allocated resources.
     */
    @Override
    public void deactivate() {
        try {
            if (this.generatePlanifAction != null) {
                this.generatePlanifAction.dispose();
                this.generatePlanifAction = null;
            }
            this.dateLabel = null;
            this.employeeViewer = null;
            this.nextAction = null;
            this.taskProposedViewer = null;
            this.previousAction = null;
            this.todayAction = null;
        } finally {
            super.deactivate();
        }
    }

    /**
     * Populate tool bar.
     */
    void fillToolBar() {
        PlanimodManagers managers = getSite().getService(PlanimodManagers.class);
        /*
         * Date navigation
         */
        getSite().getToolBarManager().add(this.previousAction = ChangeDateAction.previousAction(managers));
        getSite().getToolBarManager().add(this.todayAction = ChangeDateAction.todayAction(managers));
        getSite().getToolBarManager().add(this.nextAction = ChangeDateAction.nextAction(managers));
        /*
         * Separator
         */
        getSite().getToolBarManager().add(new Separator());
        /*
         * Generate planif action
         */
        ActionContributionItem aci;
        aci = new ActionContributionItem(this.generatePlanifAction = new GeneratePlanifAction(
                ((PlanifViewPart) getSite().getParentPart()).getContext(),
                getSite(),
                getSite().getMainWindow()));
        aci.setMode(ActionContributionItem.MODE_FORCE_TEXT);
        getSite().getToolBarManager().add(aci);
    }

    @Override
    public IPageSite getSite() {
        return (IPageSite) super.getSite();
    }
}
