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

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

import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.ObservablesManager;
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.observable.value.WritableValue;
import org.eclipse.jface.action.ControlContribution;
import org.eclipse.jface.action.IContributionItem;
import org.eclipse.jface.databinding.swt.WidgetProperties;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.viewers.ColumnViewerEditor;
import org.eclipse.jface.viewers.ColumnViewerEditorActivationEvent;
import org.eclipse.jface.viewers.ColumnViewerEditorActivationStrategy;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerEditor;
import org.eclipse.jface.viewers.Viewer;
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.layout.RowData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Text;

import com.patrikdufresne.jface.databinding.collections.FilteredObservableSet;
import com.patrikdufresne.jface.preference.PreferenceConverter;
import com.patrikdufresne.ui.ViewPart;

/**
 * TODO
 * 
 * @author Patrik Dufresne
 * 
 */
public abstract class AbstractViewPart extends ViewPart {

    private DataBindingContext dbc;
    /**
     * Define a label provider used by the text filter.
     */
    // private IConverter filterConverter;
    /**
     * Observable manager.
     */
    private ObservablesManager om;

    /**
     * Pattern used for filtering.
     */
    IObservableValue filterPattern;

    /**
     * The viewer
     */
    // private Viewer viewer;

    /**
     * Create a new view-part.
     * 
     * @param id
     *            the part id
     */
    public AbstractViewPart(String id) {
        super(id);
    }

    protected void bindValues() {
        // Nothing to do
    }

    protected DataBindingContext getDbc() {
        return this.dbc;
    }

    /**
     * Create controls composing the central area. The default implementation create a viewer by calling the
     * {@link #createViewer(Composite)}. function.
     * 
     * @param parent
     *            the composite parent
     * @return the control
     */
    // protected void createContentArea(Composite parent) {
    // GridLayout layout = new GridLayout(1, false);
    // layout.horizontalSpacing = 0;
    // layout.verticalSpacing = 0;
    // layout.marginHeight = 0;
    // layout.marginWidth = 0;
    // parent.setLayout(layout);
    //
    // // Create the viewer
    // createViewer(parent);
    // if (getViewer() != null) {
    // getViewer().getControl().setLayoutData(
    // new GridData(SWT.FILL, SWT.FILL, true, true));
    // }
    //
    // }

    // /**
    // * TODO
    // */
    // @Override
    // public void activate(Composite parent) {
    //
    // }

    /**
     * 
     */
    protected void bind() {
        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);
    }

    /**
     * Create the controls used for filtering. They are usually placed at the top-right corner of the view.
     * 
     * @param parent
     *            the composite parent
     * @return the control
     */
    // protected Control createTextFilterControl(Composite parent) {
    // Composite comp = new Composite(parent, SWT.NONE);
    // GridLayout layout = new GridLayout(2, false);
    // layout.horizontalSpacing = 5;
    // layout.verticalSpacing = 0;
    // layout.marginHeight = 0;
    // layout.marginWidth = 0;
    // comp.setLayout(layout);
    //
    // // Create the label
    // // Label label = new Label(comp, SWT.NONE);
    // // label.setText(Localized.get(AbstractViewPart.class,
    // // "AbstractViewPart.filter"));
    // // label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false,
    // // false));
    //
    // // Create the text area
    // GridData layoutdata = new GridData(SWT.FILL, SWT.CENTER, true, false);
    // layoutdata.widthHint = 300;
    //
    // Text textFilter = new Text(comp, SWT.BORDER);
    // textFilter.setToolTipText(Localized.get(AbstractViewPart.class,
    // "AbstractViewPart.filter"));
    // textFilter.setLayoutData(layoutdata);
    //
    // this.dbc.bindValue(WidgetProperties.text(SWT.Modify)
    // .observe(textFilter), this.filterPattern);
    //
    // return comp;
    // }

    /**
     * Sub-classes may implement this function to create a specific viewer. After calling this function, the viewer should
     * be accessible by calling {@link AbstractViewPart#getViewer()}. To do so, the sub-classes may call
     * {@link AbstractViewPart#setViewer(Viewer)}
     * 
     * @param parent
     *            the composite parent
     * @return the control
     */
    // protected void createViewer(Composite parent) {
    // // Sub-class may implements
    // }

    @Override
    public void deactivate() {
        try {
            if (this.om != null) {
                this.om.dispose();
                this.om = null;
            }
            if (this.dbc != null) {
                this.dbc.dispose();
                this.dbc = null;
            }
            if (this.filterPattern != null) {
                this.filterPattern.dispose();
                this.filterPattern = null;
            }
        } finally {
            super.deactivate();
        }
    }

    /**
     * This function may be called by subclasses to add a text filter to the toolbar.
     */
    protected void fillToolbarWithTextFilter() {
        // Set a value to pattern.
        if (this.filterPattern == null) {
            this.filterPattern = new WritableValue(null, String.class);

            // Add text filter to cool bar.
            getSite().getToolBarManager().add(createTextFilterContributionItem());
        }

    }

    /**
     * Create a new contribution item to display a text filter.
     * 
     * @return
     */
    protected IContributionItem createTextFilterContributionItem() {
        // Create a custom implementation of Control Contribution to contribute
        // to a CoolBar.
        return new ControlContribution("AbstractViewPart.filter") {

            @Override
            protected Control createControl(Composite parent) {

                // Create a composite to adapt the size of the text zone.
                Composite comp = new Composite(parent, SWT.NONE);
                comp.setLayoutData(new RowData());
                GridLayout layout = new GridLayout(1, false);
                layout.marginHeight = 0;
                layout.marginWidth = 5;
                comp.setLayout(layout);

                // Create the widget.
                Text textFilter = new Text(comp, SWT.SINGLE | SWT.BORDER);
                textFilter.setToolTipText(_("Permet de filtrer le contenu de la vue"));
                textFilter.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true));

                // Bind the widget with the pattern.
                getDbc().bindValue(WidgetProperties.text(SWT.Modify).observe(textFilter), AbstractViewPart.this.filterPattern);

                return comp;

            }
        };
    }

    /**
     * Create a filtered observable set with the filter pattern.
     * 
     * @param set
     * 
     * @return
     */
    protected IObservableSet filter(IObservableSet set, IConverter converter) {
        // Add a text filter
        if (this.filterPattern == null) {
            throw new RuntimeException("filterPattern is not created.");
        }

        // Use the created pattern to create a filtered observable set.
        return new FilteredObservableSet(set, this.filterPattern, converter);
    }

    /**
     * Return the text filter label provider used to filter the elements.
     * 
     * @return
     */
    // protected IConverter getFilterConverter() {
    // if (this.filterConverter == null) {
    // return new Converter(Object.class, String.class) {
    // @Override
    // public Object convert(Object fromObject) {
    // return fromObject.toString();
    // }
    // };
    // }
    // return this.filterConverter;
    // }

    /**
     * Return the viewer previously create by calling the <code>createViewer</code>.
     * 
     * @return the viewer or null.
     */
    // protected Viewer getViewer() {
    // return this.viewer;
    // }

    /**
     * Sets the text filter label provider.
     */
    // protected void setFilterConverter(IConverter converter) {
    // this.filterConverter = converter;
    // }

    /**
     * Function that may be used to sets the viewer.
     * 
     * @param prefKey
     * @param iPreferenceStore
     * @param i
     * @param parent
     * 
     * @param viewer
     */
    // protected void setViewer(Viewer viewer) {
    // this.viewer = viewer;
    // }

    public static TableViewer createTableViewer(Composite parent) {
        return createTableViewer(parent, SWT.NONE);
    }

    public static TableViewer createTableViewer(Composite parent, int style) {
        // Create TableViewer
        TableViewer viewer = new TableViewer(parent, style | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION);
        viewer.getTable().setHeaderVisible(true);

        // Change the editor strategy
        ColumnViewerEditorActivationStrategy actSupport = new ColumnViewerEditorActivationStrategy(viewer) {
            @Override
            protected boolean isEditorActivationEvent(ColumnViewerEditorActivationEvent event) {
                return event.eventType == ColumnViewerEditorActivationEvent.TRAVERSAL
                        || event.eventType == ColumnViewerEditorActivationEvent.MOUSE_DOUBLE_CLICK_SELECTION
                        || (event.eventType == ColumnViewerEditorActivationEvent.KEY_PRESSED && event.keyCode == SWT.CR)
                        || event.eventType == ColumnViewerEditorActivationEvent.PROGRAMMATIC;
            }
        };
        TableViewerEditor
                .create(
                        viewer,
                        actSupport,
                        ColumnViewerEditor.TABBING_HORIZONTAL
                                | ColumnViewerEditor.TABBING_MOVE_TO_ROW_NEIGHBOR
                                | ColumnViewerEditor.TABBING_VERTICAL
                                | ColumnViewerEditor.KEYBOARD_ACTIVATION);

        return viewer;
    }

    /**
     * Create a new SashForm handling the restore and save process of the preferred weight.
     * 
     * @param parent
     *            the parent composite
     * @param style
     *            the sash form style. Either SWT.VERTICAL or SWT.HORIZONTAL
     * @param preferenceStore
     *            the preference store. Can't be null.
     * @param prefKey
     *            the preference key. Can't be null
     * @param defaultWeights
     *            the default weights
     * @return the sash form
     */
    protected static SashForm createSashForm(
            Composite parent,
            int style,
            final IPreferenceStore preferenceStore,
            final String prefKey,
            final int[] defaultWeights) {
        if (preferenceStore == null || prefKey == null) {
            throw new NullPointerException();
        }
        final SashForm sash = new SashForm(parent, style);

        // Restore weights from preferences
        sash.addListener(SWT.Resize, new Listener() {
            @Override
            public void handleEvent(Event event) {
                PreferenceConverter.setDefault(preferenceStore, prefKey, defaultWeights);
                int[] weights = PreferenceConverter.getIntArray(preferenceStore, prefKey);
                if (weights.length == sash.getWeights().length) {
                    sash.setWeights(weights);
                }

                sash.removeListener(SWT.Resize, this);
            }
        });

        // Save sash weights
        sash.addListener(SWT.Dispose, new Listener() {
            @Override
            public void handleEvent(Event event) {
                PreferenceConverter.setValue(preferenceStore, prefKey, ((SashForm) event.widget).getWeights());
            }
        });

        return sash;

    }
}