/*******************************************************************************
 * Copyright (c) 2013, 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
 *    Wippermueller, Frank <wippermueller.frank@guj.de> - Bug 561850
 *******************************************************************************/
package org.eclipse.nebula.widgets.nattable.edit.editor;

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

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.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.DateTime;

/**
 * ICellEditor implementation that uses a DateTime 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.
 * <p>
 * Note: This is an example implementation for a Date editor. As the SWT
 * DateTime control has some serious issues like it is not nullable, has issues
 * on setting the focus programmatically and it is not possible to open the
 * dropdown programmatically, we suggest to rather use some Nebula widget or a
 * custom widget for date editing.
 * </p>
 */
public class DateCellEditor extends AbstractCellEditor {

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

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

    /**
     * The current editor value, needed to avoid changes in time fields where
     * only date fields should be edited.
     *
     * @since 1.6
     */
    private Calendar currentEditorValue;

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

    /**
     * Creates a DateCellEditor.
     *
     * @param moveSelectionOnEnter
     *            Flag to configure whether the selection should move after a
     *            value was committed after pressing enter.
     */
    public DateCellEditor(boolean moveSelectionOnEnter) {
        this.moveSelectionOnEnter = moveSelectionOnEnter;
    }

    @Override
    public Object getEditorValue() {
        this.currentEditorValue.set(this.dateTime.getYear(), this.dateTime.getMonth(), this.dateTime.getDay());
        return this.currentEditorValue;
    }

    @Override
    public void setEditorValue(Object value) {
        // in setCanonicalValue() we ensure that the value is of type Calendar
        // but an additional check to ensure type safety doesn't hurt
        if (value instanceof Calendar) {
            this.currentEditorValue = (Calendar) value;
            this.dateTime.setDate(
                    this.currentEditorValue.get(Calendar.YEAR),
                    this.currentEditorValue.get(Calendar.MONTH),
                    this.currentEditorValue.get(Calendar.DATE));
        }
    }

    @Override
    public Object getCanonicalValue() {
        if (this.layerCell.getDataValue() instanceof Calendar) {
            return getEditorValue();
        } else if (this.layerCell.getDataValue() instanceof Date) {
            return ((Calendar) getEditorValue()).getTime();
        }
        return null;
    }

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

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

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

    @Override
    public DateTime createEditorControl(final Composite parent) {
        final DateTime dateControl = new DateTime(parent, SWT.DATE | SWT.DROP_DOWN);

        // 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));
        dateControl.setFont(this.cellStyle.getAttributeValue(CellStyleAttributes.FONT));

        // add a key listener that will commit or close the editor for special
        // key strokes
        dateControl.addKeyListener(new KeyAdapter() {

            @Override
            public void keyPressed(KeyEvent event) {
                if (event.keyCode == SWT.CR || event.keyCode == SWT.KEYPAD_CR) {

                    boolean commit = (event.stateMask == SWT.MOD3) ? false : true;
                    MoveDirectionEnum move = MoveDirectionEnum.NONE;
                    if (DateCellEditor.this.moveSelectionOnEnter
                            && DateCellEditor.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 (DateCellEditor.this.editMode == EditModeEnum.DIALOG) {
                        parent.forceFocus();
                    }
                } else if (event.keyCode == SWT.ESC && event.stateMask == 0) {
                    close();
                }
            }
        });

        return dateControl;
    }

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

        // set focus asynchronously in order to prevent display glitches
        // it seems to be not possible to open the dropdown programmatically
        parent.getDisplay().asyncExec(() -> {
            if (!DateCellEditor.this.dateTime.isDisposed()) {
                DateCellEditor.this.dateTime.forceFocus();
            }
        });

        return this.dateTime;
    }

    @Override
    public void close() {
        super.close();
        this.currentEditorValue = null;
    }
}
