/**
 * 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.conversion.Converter;
import org.eclipse.core.databinding.conversion.IConverter;
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.IToolBarManager;
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.ViewerProperties;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;

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.databinding.viewers.TextProposalViewerUpdater;
import com.patrikdufresne.jface.viewers.ColumnViewerPreferences;
import com.patrikdufresne.jface.viewers.TextProposalViewer;
import com.patrikdufresne.jface.viewers.TextProposalViewerCellEditor;
import com.patrikdufresne.managers.databinding.ManagerUpdateValueStrategy;
import com.patrikdufresne.managers.jface.RemoveAction;
import com.patrikdufresne.printing.PrintAction;
import com.patrikdufresne.ui.IPageSite;
import com.patrikdufresne.ui.IViewPartPage;
import com.patrikdufresne.util.DateFormatRegistry;
import com.planimod.core.AbstractCalendarEvent;
import com.planimod.core.Employee;
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.printing.PrintActionMenuManager;
import com.planimod.ui.theme.Resources;
import com.planimod.ui.viewers.EmployeeViewerUtils;
import com.planimod.ui.views.AbstractViewPart;
import com.planimod.ui.views.ChangeDateAction;
import com.planimod.ui.views.planif.printing.PlanifDaySummaryPrintAction;
import com.planimod.ui.views.planif.printing.PlanifWeekSummaryPrintAction;
import com.planimod.ui.views.planif.printing.PlanifWeekTransfertPrintAction;

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

    /**
     * Dot (.) string used as separator between field name
     */
    private static final String DOT = ".";

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

    /**
     * Preference key to access the column's width.
     */
    private static final String PREFERENCE_COLUMNS = "ListStyleViewPart.columns";

    /**
     * Sapce ( ) string used as separator between string for filtering.
     */
    private static final String SPACE = " ";

    /**
     * Action to create tasks.
     */
    private CreateTasksAction createTasksAction;

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

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

    private ChangeDateAction nextAction;

    private ChangeDateAction previousAction;

    private RemoveAction removeAction;

    private ChangeDateAction todayAction;

    private TableViewer viewer;

    private PlanifWeekTransfertPrintAction planifWeekTransfertWizardAction;

    private static final IConverter CONVERTER = new Converter(Task.class, String.class) {
        @Override
        public Object convert(Object fromObject) {
            Task event = (Task) fromObject;
            StringBuilder string = new StringBuilder();
            if (event.getEmployee() != null) {
                string.append(EmployeeViewerUtils.getName(event.getEmployee()));
                string.append(SPACE);
            }
            string.append(event.getPosition().getName());
            string.append(SPACE);
            string.append(event.getPosition().getSection().getName());
            string.append(SPACE);
            string.append(event.getProductionEvent().getProduct());
            string.append(SPACE);
            string.append(DateFormatRegistry.format(event.getStartDate(), DateFormatRegistry.DATETIME | DateFormatRegistry.MEDIUM));
            string.append(SPACE);
            string.append(DateFormatRegistry.format(event.getStartDate(), DateFormatRegistry.DATETIME | DateFormatRegistry.MEDIUM));
            string.append(SPACE);
            string.append(event.getProductionEvent().getShift().getTeam().getName());
            return string.toString();
        }
    };

    /**
     * Create a new view part.
     */
    public TaskViewPartPage() {
        super(ID);
        setTitle(_("Liste"));
    }

    @Override
    protected void bindValues() {

        GeneratePlanifContext context = ((PlanifViewPart) getSite().getParentPart()).getContext();

        /*
         * Get the states.
         */
        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 viewer content.
         */
        IObservableSet tasks = new TaskForContextComputedSet(context);
        this.viewer.setContentProvider(new ObservableSetContentProvider());
        createColumns(this.viewer, tasks);
        this.viewer.setInput(filter(tasks, CONVERTER));

        /*
         * Bind the planner background color.
         */
        DateColorControlStyleSupport.create(this.viewer.getTable(), week);

        /*
         * Bind the actions
         */
        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);
        getDbc().bindValue(
                JFaceProperties.value(PlanifWeekTransfertPrintAction.class, PlanifWeekTransfertPrintAction.DATE, PlanifWeekTransfertPrintAction.DATE).observe(
                        this.planifWeekTransfertWizardAction),
                week);

        /*
         * Bind remove action to production events
         */
        IObservableValue productionEvent = ViewerProperties.singleSelection().value(BeanProperties.value(Task.class, Task.PRODUCTION_EVENT)).observe(
                this.viewer);
        getDbc().bindValue(JFaceProperties.value(RemoveAction.class, RemoveAction.OBJECT, RemoveAction.OBJECT).observe(this.removeAction), productionEvent);

    }

    protected void createColumns(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 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 editing support
        GeneratePlanifContext context = ((PlanifViewPart) getSite().getParentPart()).getContext();
        IObservableSet proposedEmployees = new EmployeeForContextComputedSet(context);
        TextProposalViewerCellEditor employeeCellEditor = new TextProposalViewerCellEditor(viewer.getTable());
        TextProposalViewer textViewer = employeeCellEditor.getViewer();
        textViewer.setContentProvider(new ObservableSetContentProvider(new TextProposalViewerUpdater(textViewer)));
        textViewer.setLabelProvider(new ObservableMapLabelProvider(BindingProperties.convertedValue(EmployeeViewerUtils.fullnameConverter()).observeDetail(
                proposedEmployees)));
        textViewer.setComparator(new PropertyViewerComparator(new SelfValueProperty(Employee.class), EmployeeComparators.bySeniority()));
        textViewer.setInput(proposedEmployees);

        IValueProperty employeeProperty = BeanProperties.value(Task.class, Task.EMPLOYEE);
        IValueProperty employeeLabel = employeeProperty.value(BindingProperties.convertedValue(EmployeeViewerUtils.fullnameConverter()));
        ColumnSupport.create(viewer, _("Employé"), knownElements, employeeLabel).addPropertySorting().setWidth(150).addViewerEditingSupport(
                getDbc(),
                employeeProperty,
                employeeCellEditor,
                new ManagerUpdateValueStrategy(getSite().getService(PlanimodManagers.class), UpdateValueStrategy.POLICY_CONVERT),
                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());

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

    @Override
    public void activate(final Composite parent) {
        // 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));

        // Create the viewer
        this.viewer = createTableViewer(parent);
        this.viewer.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        this.viewer.getTable().setLinesVisible(PlanimodPreferences.getPreferenceStore().getBoolean(PlanimodPreferences.SHOW_LINES));

        // Populate the tool bar
        fillToolBar();

        // Create binding
        bind();

    }

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

    /**
     * Populate the tool bar.
     */
    void fillToolBar() {
        PlanimodManagers managers = getSite().getService(PlanimodManagers.class);
        IToolBarManager toolbar = getSite().getToolBarManager();

        GeneratePlanifContext context = ((PlanifViewPart) getSite().getParentPart()).getContext();

        // Date navigation
        toolbar.add(this.previousAction = ChangeDateAction.previousAction(managers));
        toolbar.add(this.todayAction = ChangeDateAction.todayAction(managers));
        toolbar.add(this.nextAction = ChangeDateAction.nextAction(managers));

        // Separator
        toolbar.add(new Separator());

        // Remove
        ActionContributionItem aci;
        toolbar.add(aci = new ActionContributionItem(this.removeAction = new RemoveAction(managers, getSite().getMainWindow())));
        aci.setMode(ActionContributionItem.MODE_FORCE_TEXT);

        // Create Tasks
        toolbar.add(aci = new ActionContributionItem(this.createTasksAction = new CreateTasksAction(context, getSite(), getSite().getMainWindow())));
        aci.setMode(ActionContributionItem.MODE_FORCE_TEXT);

        // Generate planif action
        toolbar.add(aci = new ActionContributionItem(this.generatePlanifAction = new GeneratePlanifAction(context, getSite(), getSite().getMainWindow())));
        aci.setMode(ActionContributionItem.MODE_FORCE_TEXT);

        // Separator
        toolbar.add(new Separator());

        /*
         * Print Menu
         */
        PrintActionMenuManager actionMenu = new PrintActionMenuManager("TaskViewPartPage.printMenu");

        // Print summary week
        actionMenu.add(new PlanifWeekSummaryPrintAction(context, getSite(), PrintAction.PRINT_ACTION, getSite().getMainWindow()));

        // Preview summary wekk
        actionMenu.add(new PlanifWeekSummaryPrintAction(context, getSite(), PrintAction.PREVIEW_ACTION, getSite().getMainWindow()));

        actionMenu.add(new Separator());

        // Print summary day
        actionMenu.add(new PlanifDaySummaryPrintAction(context, getSite(), PrintAction.PRINT_ACTION, getSite().getMainWindow()));

        // Preview summary day
        actionMenu.add(new PlanifDaySummaryPrintAction(context, getSite(), PrintAction.PREVIEW_ACTION, getSite().getMainWindow()));

        actionMenu.add(new Separator());

        // Print transfer week
        actionMenu.add(this.planifWeekTransfertWizardAction = new PlanifWeekTransfertPrintAction(managers, getSite()));

        toolbar.add(actionMenu);

        // Filter text
        fillToolbarWithTextFilter();
    }

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

}
