/*******************************************************************************
 * 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
 *******************************************************************************/
package org.eclipse.nebula.widgets.nattable.edit;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.eclipse.jface.window.Window;
import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
import org.eclipse.nebula.widgets.nattable.edit.command.UpdateDataCommand;
import org.eclipse.nebula.widgets.nattable.edit.editor.ICellEditor;
import org.eclipse.nebula.widgets.nattable.edit.gui.CellEditDialogFactory;
import org.eclipse.nebula.widgets.nattable.edit.gui.ICellEditDialog;
import org.eclipse.nebula.widgets.nattable.layer.ILayer;
import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
import org.eclipse.nebula.widgets.nattable.widget.EditModeEnum;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Controller to handle the activation of the edit mode of NatTable cells.
 */
public class EditController {

    private EditController() {
        // private default constructor for helper class
    }

    private static final Logger LOG = LoggerFactory.getLogger(EditController.class);

    /**
     * Activates the edit mode for the given cell. Will determine whether the
     * editor should be opened inline or in a subdialog.
     *
     * @param cell
     *            The cell that should be put into the edit mode.
     * @param parent
     *            The parent Composite, needed for the creation of the editor
     *            control.
     * @param initialCanonicalValue
     *            The value that should be put to the activated editor control.
     *            Usually this value should be the same as calling
     *            <code>cell.getDataValue()</code>, but for the special case
     *            that an editor should be activated pressing a letter or digit
     *            key on the current selection, the initial value should be the
     *            Character representing that key.
     * @param configRegistry
     *            The {@link IConfigRegistry} containing the configuration of
     *            the current NatTable instance the command should be executed
     *            for. This is necessary because the edit controllers in the
     *            current architecture are not aware of the instance they are
     *            running in.
     */
    public static void editCell(final ILayerCell cell, final Composite parent,
            Object initialCanonicalValue, final IConfigRegistry configRegistry) {

        if (cell == null) {
            LOG.error("Cell being edited is no longer available. Initial value: {}", initialCanonicalValue); //$NON-NLS-1$
            return;
        }

        try {
            // determine the position of the cell to put into edit mode
            Rectangle cellBounds = cell.getBounds();
            ILayer layer = cell.getLayer();

            int columnPosition = cell.getColumnPosition();
            int rowPosition = cell.getRowPosition();

            // read the configuration for the specified cell for
            // - which editor to use for that cell
            final List<String> configLabels = cell.getConfigLabels();

            // check which editor to use
            final ICellEditor cellEditor = configRegistry.getConfigAttribute(
                    EditConfigAttributes.CELL_EDITOR,
                    DisplayMode.EDIT,
                    configLabels);

            if (cellEditor.openInline(configRegistry, configLabels)) {
                // edit inline
                ICellEditHandler editHandler = new InlineEditHandler(layer, columnPosition, rowPosition);

                Rectangle editorBounds = layer.getLayerPainter().adjustCellBounds(
                        columnPosition,
                        rowPosition,
                        new Rectangle(cellBounds.x, cellBounds.y, cellBounds.width, cellBounds.height));

                cellEditor.activateCell(parent, initialCanonicalValue,
                        EditModeEnum.INLINE, editHandler, cell, configRegistry);

                final Control editorControl = cellEditor.getEditorControl();

                editorBounds = cellEditor.calculateControlBounds(editorBounds);

                // TODO introduce more generic way to identify the border width
                // the currently fixed border width of 1 is handled
                // because of the fixed border width possibly applied via
                // NatTableBorderOverlayPainter
                if (editorBounds.x == 0) {
                    editorBounds.x += 1;
                    editorBounds.width -= 1;
                }

                if (editorControl != null && !editorControl.isDisposed()) {
                    editorControl.setBounds(editorBounds);
                    // We need to add the control listeners after setting the
                    // bounds to it because of the strange behaviour on Mac OS
                    // where a control loses focus if its bounds are set
                    cellEditor.addEditorControlListeners();
                    layer.fireLayerEvent(new CellEditorCreatedEvent(cellEditor));
                }
            } else {
                List<ILayerCell> cells = new ArrayList<>();
                cells.add(cell);
                editCells(cells, parent, initialCanonicalValue, configRegistry);
            }
        } catch (Exception e) {
            LOG.error("Error while editing cell: Cell: {}; Initial value: {}", cell, initialCanonicalValue, e); //$NON-NLS-1$
        }
    }

    /**
     * This method is used to edit cells in a sub dialog. In every case this
     * method will open a dialog for editing, regardless if the list of cells to
     * edit contain several or only one value. Only if the given list of cells
     * to edit is <code>null</code> or empty, there is no action performed.
     *
     * @param cells
     *            The list of cells to edit.
     * @param parent
     *            The parent composite to access the parent shell, or
     *            <code>null</code> to create a top-level shell dialog. In the
     *            last case, the dialog will be opened as non modal.
     * @param initialCanonicalValue
     *            The value that should be propagated to the editor control.
     *            Needed because for multi cell editing or editor activation by
     *            letter/digit key will result in a different value to populate
     *            for some editors than populating the value out of the
     *            cell/data model directly.
     * @param configRegistry
     *            The {@link IConfigRegistry} containing the configuration of
     *            the current NatTable instance the command should be executed
     *            for. This is necessary because the edit controllers in the
     *            current architecture are not aware of the instance they are
     *            running in and therefore it is needed for activation of
     *            editors.
     */
    public static void editCells(final Collection<ILayerCell> cells,
            final Composite parent, Object initialCanonicalValue,
            final IConfigRegistry configRegistry) {

        if (cells != null && !cells.isEmpty()) {
            // get the editor to use, because the editor contains information if
            // it allows editing on a multi edit dialog
            // Note: this works because previous to calling this method it is
            // checked if all cells have the same editor configured. Otherwise
            // this method will have serious issues further on.
            ICellEditor cellEditor = configRegistry.getConfigAttribute(
                    EditConfigAttributes.CELL_EDITOR,
                    DisplayMode.EDIT,
                    cells.iterator().next().getConfigLabels());

            if (cells.size() == 1
                    || (cells.size() > 1 && supportMultiEdit(cells, cellEditor, configRegistry))) {

                if (cellEditor.openMultiEditDialog()) {
                    // as the EditSelectionCommandHandler already ensured that
                    // all cells have the same configuration, we can simply use
                    // any cell for multi cell edit handling
                    ICellEditDialog dialog = CellEditDialogFactory.createCellEditDialog(
                            parent != null ? parent.getShell() : null,
                            initialCanonicalValue,
                            cells.iterator().next(),
                            cellEditor,
                            configRegistry);

                    int returnValue = dialog.open();

                    if (returnValue == Window.OK) {
                        for (ILayerCell selectedCell : cells) {
                            Object editorValue = dialog.getCommittedValue();
                            if (dialog.getEditType() != EditTypeEnum.SET) {
                                editorValue = dialog.calculateValue(
                                        selectedCell.getDataValue(),
                                        editorValue);
                            }
                            ILayer layer = selectedCell.getLayer();

                            layer.doCommand(new UpdateDataCommand(
                                    layer,
                                    selectedCell.getColumnPosition(),
                                    selectedCell.getRowPosition(),
                                    editorValue));
                        }
                    }
                } else {
                    // if the editor is configured to do not open a multi edit
                    // dialog for multi editing, we simply activate all editors
                    // for cells that are selected for multi editing this only
                    // works for editors that have no interactive control for
                    // editing, like for example the CheckBoxCellEditor that
                    // directly changes the value and closes right away.
                    for (ILayerCell cell : cells) {
                        ICellEditHandler editHandler = new InlineEditHandler(
                                cell.getLayer(),
                                cell.getColumnPosition(),
                                cell.getRowPosition());

                        cellEditor.activateCell(parent, initialCanonicalValue,
                                EditModeEnum.INLINE, editHandler, cell, configRegistry);
                    }
                }
            }
        }
    }

    /**
     * Will check if multi editing is supported. Usually it should be enough
     * checking the editor once. But as this can be configured via configuration
     * attribute, and this can differ from cell to cell, all cells are checked
     * to be sure.
     *
     * @param cells
     *            The selected cells that should be multi edited.
     * @param cellEditor
     *            The cell editor that is the same for every cell.
     * @param configRegistry
     *            The {@link IConfigRegistry} containing the configuration of
     *            the current NatTable instance the command should be executed
     *            for. This is necessary because the edit controllers in the
     *            current architecture are not aware of the instance they are
     *            running in and therefore it is needed for activation of
     *            editors.
     * @return <code>true</code> if the editor supports multi edit for all
     *         selected cells, <code>false</code> if at least one cell does
     *         specify to not support multi edit.
     */
    private static boolean supportMultiEdit(
            Collection<ILayerCell> cells, ICellEditor cellEditor, IConfigRegistry configRegistry) {

        for (ILayerCell cell : cells) {
            if (!cellEditor.supportMultiEdit(configRegistry, cell.getConfigLabels())) {
                return false;
            }
        }
        return true;
    }
}
