/*******************************************************************************
 * Copyright (c) 2016, 2020 Dirk Fauth and others.
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *    Dirk Fauth <dirk.fauth@googlemail.com> - initial API and implementation
 *******************************************************************************/
package org.eclipse.nebula.widgets.nattable.extension.nebula.cdatetime;

import java.util.Calendar;
import java.util.Date;

import org.eclipse.nebula.widgets.cdatetime.CDT;
import org.eclipse.nebula.widgets.cdatetime.CDateTime;
import org.eclipse.nebula.widgets.nattable.edit.editor.AbstractCellEditor;
import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer.MoveDirectionEnum;
import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes;
import org.eclipse.nebula.widgets.nattable.widget.EditModeEnum;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.ShellAdapter;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;

/**
 * ICellEditor implementation that uses the Nebula {@link CDateTime} control for
 * editing. It supports objects of type Date and Calendar aswell.
 * <p>
 * Introduces the contract that the editor control value is of type Calendar.
 * Therefore the methods to deal with the canonical values need to be overriden
 * too, to avoid conversion of the canonical value to display value by using the
 * IDisplayConverter that is registered together with this editor.
 *
 * @since 1.1
 */
public class CDateTimeCellEditor extends AbstractCellEditor {

    /**
     * The DateTime control which is the editor wrapped by this DateCellEditor.
     */
    private CDateTime dateTime;

    /**
     * Flag to configure whether the selection should move after a value was
     * committed after pressing enter.
     */
    private final boolean moveSelectionOnEnter;

    /**
     * The style bits that should be used to create the {@link CDateTime}
     * control of the editor.
     */
    private int style;

    /**
     * Flag to configure whether the editor should provide a {@link Calendar}
     * object on editing or a {@link Date}. By default a {@link Date} is
     * returned as {@link CDateTime} returns {@link Date} objects.
     */
    private boolean provideCalendar = false;

    /**
     * Creates the default DateCellEditor that does not move the selection on
     * committing the value by pressing enter.
     */
    public CDateTimeCellEditor() {
        this(false);
    }

    /**
     * Creates a DateCellEditor.
     *
     * @param moveSelectionOnEnter
     *            Flag to configure whether the selection should move after a
     *            value was committed after pressing enter.
     */
    public CDateTimeCellEditor(boolean moveSelectionOnEnter) {
        this(moveSelectionOnEnter, CDT.DROP_DOWN | CDT.DATE_SHORT | CDT.TIME_SHORT);
    }

    /**
     * Creates a DateCellEditor.
     *
     * @param moveSelectionOnEnter
     *            Flag to configure whether the selection should move after a
     *            value was committed after pressing enter.
     * @param style
     *            The style bits that should be used to create the
     *            {@link CDateTime} control of the editor.
     */
    public CDateTimeCellEditor(boolean moveSelectionOnEnter, int style) {
        this.moveSelectionOnEnter = moveSelectionOnEnter;
        this.style = style;
    }

    @Override
    public Object getEditorValue() {
        if (this.dateTime.getSelection() != null) {
            return this.dateTime.getSelection();
        }
        return null;
    }

    @Override
    public void setEditorValue(Object value) {
        if (value instanceof Calendar) {
            Calendar cal = (Calendar) value;
            this.dateTime.setSelection(cal.getTime());
        } else if (value instanceof Date) {
            this.dateTime.setSelection((Date) value);
        }
    }

    @Override
    public Object getCanonicalValue() {
        // there is no need for conversion because the CDateTime control
        // already returns a Date
        if (this.provideCalendar) {
            Calendar cal = Calendar.getInstance();
            cal.setTime((Date) getEditorValue());
            return cal;
        }
        return getEditorValue();
    }

    @Override
    public void setCanonicalValue(Object canonicalValue) {
        Date editorValue = null;
        if (canonicalValue instanceof Calendar) {
            editorValue = ((Calendar) canonicalValue).getTime();
        } else if (canonicalValue instanceof Date) {
            editorValue = (Date) canonicalValue;
        }

        if (editorValue != null) {
            setEditorValue(editorValue);
        }
    }

    @Override
    public CDateTime getEditorControl() {
        return this.dateTime;
    }

    @Override
    public CDateTime createEditorControl(final Composite parent) {
        final CDateTime dateControl = new CDateTime(parent, this.style) {
            @Override
            protected Shell getContentShell() {
                Shell shell = super.getContentShell();
                shell.addShellListener(new ShellAdapter() {

                    @Override
                    public void shellActivated(ShellEvent e) {
                        if (CDateTimeCellEditor.this.focusListener instanceof InlineFocusListener) {
                            ((InlineFocusListener) CDateTimeCellEditor.this.focusListener).handleFocusChanges = false;
                        }
                    }

                    @Override
                    public void shellClosed(ShellEvent e) {
                        if (CDateTimeCellEditor.this.focusListener instanceof InlineFocusListener) {
                            ((InlineFocusListener) CDateTimeCellEditor.this.focusListener).handleFocusChanges = true;
                        }
                    }
                });
                return shell;
            }

            @Override
            protected void addTextListener() {
                super.addTextListener();
                this.text.getControl().addTraverseListener(event -> {
                    boolean committed = false;
                    if (event.keyCode == SWT.TAB && event.stateMask == SWT.MOD2) {
                        committed = commit(MoveDirectionEnum.LEFT);
                    } else if (event.keyCode == SWT.TAB && event.stateMask == 0) {
                        committed = commit(MoveDirectionEnum.RIGHT);
                    } else if (event.detail == SWT.TRAVERSE_ESCAPE) {
                        close();
                    }
                    if (!committed) {
                        event.doit = false;
                    }
                });
            }
        };

        // set style information configured in the associated cell style
        dateControl.setBackground(this.cellStyle.getAttributeValue(CellStyleAttributes.BACKGROUND_COLOR));
        dateControl.setForeground(this.cellStyle.getAttributeValue(CellStyleAttributes.FOREGROUND_COLOR));
        // when trying to set a font in simple mode (embedded GUI) we will get a
        // StackOverflowError
        if ((this.style & CDT.SIMPLE) == 0) {
            dateControl.setFont(this.cellStyle.getAttributeValue(CellStyleAttributes.FONT));
        }

        dateControl.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetDefaultSelected(SelectionEvent event) {
                boolean commit = (event.stateMask != SWT.MOD3);
                MoveDirectionEnum move = MoveDirectionEnum.NONE;
                if (CDateTimeCellEditor.this.moveSelectionOnEnter
                        && CDateTimeCellEditor.this.editMode == EditModeEnum.INLINE) {
                    if (event.stateMask == 0) {
                        move = MoveDirectionEnum.DOWN;
                    } else if (event.stateMask == SWT.MOD2) {
                        move = MoveDirectionEnum.UP;
                    }
                }

                if (commit) {
                    commit(move);
                }

                if (CDateTimeCellEditor.this.editMode == EditModeEnum.DIALOG) {
                    parent.forceFocus();
                }
            }
        });

        return dateControl;
    }

    @Override
    protected Control activateCell(Composite parent, Object originalCanonicalValue) {
        this.dateTime = createEditorControl(parent);
        setCanonicalValue(originalCanonicalValue);

        // this is necessary so the control gets the focus
        // but this also causing some issues as focusing the DateTime control
        // programmatically does some strange things with showing the editable
        // data also it seems to be not possible to open the dropdown
        // programmatically
        this.dateTime.forceFocus();

        return this.dateTime;
    }

    /**
     *
     * @param provideCalendar
     *            <code>true</code> if this editor should provide a
     *            {@link Calendar} object on editing, <code>false</code> if it
     *            should provide a {@link Date} object.
     */
    public void setProvideCalendar(boolean provideCalendar) {
        this.provideCalendar = provideCalendar;
    }
}
