/*****************************************************************************
 * Copyright (c) 2015, 2020 CEA LIST.
 *
 * 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.copy.command;

import org.eclipse.nebula.widgets.nattable.command.AbstractLayerCommandHandler;
import org.eclipse.nebula.widgets.nattable.command.ILayerCommandHandler;
import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
import org.eclipse.nebula.widgets.nattable.coordinate.PositionCoordinate;
import org.eclipse.nebula.widgets.nattable.copy.InternalCellClipboard;
import org.eclipse.nebula.widgets.nattable.edit.command.EditUtils;
import org.eclipse.nebula.widgets.nattable.edit.command.UpdateDataCommand;
import org.eclipse.nebula.widgets.nattable.layer.IUniqueIndexLayer;
import org.eclipse.nebula.widgets.nattable.layer.LayerUtil;
import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;

/**
 * {@link ILayerCommandHandler} for handling {@link PasteDataCommand}s using the
 * {@link InternalCellClipboard}.
 *
 * @since 1.4
 */
public class InternalPasteDataCommandHandler extends AbstractLayerCommandHandler<PasteDataCommand> {

    protected SelectionLayer selectionLayer;
    protected InternalCellClipboard clipboard;

    /**
     *
     * @param selectionLayer
     *            {@link SelectionLayer} that is needed to determine the
     *            position to paste the values to.
     * @param clipboard
     *            The {@link InternalCellClipboard} that contains the values
     *            that should be pasted.
     */
    public InternalPasteDataCommandHandler(
            SelectionLayer selectionLayer,
            InternalCellClipboard clipboard) {

        this.selectionLayer = selectionLayer;
        this.clipboard = clipboard;
    }

    @Override
    protected boolean doCommand(PasteDataCommand command) {
        if (this.clipboard.getCopiedCells() != null) {

            preInternalPaste();

            PositionCoordinate coord = this.selectionLayer.getSelectionAnchor();
            int pasteColumn = coord.getColumnPosition();
            int pasteRow = coord.getRowPosition();

            IUniqueIndexLayer pasteLayer = getPasteLayer(this.clipboard.getCopiedCells());
            if (pasteLayer != this.selectionLayer) {
                // if the paste layer is not the SelectionLayer we need to
                // perform a conversion
                pasteColumn = LayerUtil.convertColumnPosition(this.selectionLayer, pasteColumn, pasteLayer);
                pasteRow = LayerUtil.convertRowPosition(this.selectionLayer, pasteRow, pasteLayer);
            }

            for (ILayerCell[] cells : this.clipboard.getCopiedCells()) {
                for (ILayerCell cell : cells) {
                    ILayerCell targetCell = pasteLayer.getCellByPosition(pasteColumn, pasteRow);

                    if (isPasteAllowed(cell, targetCell, command.configRegistry)) {
                        pasteLayer.doCommand(
                                new UpdateDataCommand(
                                        pasteLayer,
                                        pasteColumn,
                                        pasteRow,
                                        getPasteValue(cell, pasteColumn, pasteRow)));
                    }

                    pasteColumn++;

                    if (pasteColumn >= pasteLayer.getColumnCount()) {
                        break;
                    }
                }
                pasteRow++;
                pasteColumn = coord.getColumnPosition();
            }

            postInternalPaste();
        }
        return true;
    }

    /**
     * Checks if the cell at the target coordinates supports the paste operation
     * or not.
     * <p>
     * Note: The coordinates need to be related to the SelectionLayer, otherwise
     * the wrong cell will be used for the check.
     * </p>
     *
     * @param sourceCell
     *            The {@link ILayerCell} that is copied and should be pasted to
     *            the target cell.
     * @param targetCell
     *            The {@link ILayerCell} to which the content of the source cell
     *            should be pasted to.
     * @param configRegistry
     *            The {@link IConfigRegistry} needed to access the configuration
     *            values.
     * @return <code>true</code> if the cell supports the paste operation,
     *         <code>false</code> if not
     * @since 1.6
     */
    protected boolean isPasteAllowed(ILayerCell sourceCell, ILayerCell targetCell, IConfigRegistry configRegistry) {
        return EditUtils.isCellEditable(
                new PositionCoordinate(targetCell.getLayer(), targetCell.getColumnPosition(), targetCell.getRowPosition()),
                configRegistry);
    }

    /**
     * Returns the value that should be pasted.
     *
     * @param cell
     *            The {@link ILayerCell} from which to retrieve the value to
     *            paste from.
     * @param pasteColumn
     *            The column position of the cell to paste to.
     * @param pasteRow
     *            The row position of the cell to paste to.
     * @return The value that should be pasted.
     */
    protected Object getPasteValue(ILayerCell cell, int pasteColumn, int pasteRow) {
        return cell != null ? cell.getDataValue() : null;
    }

    /**
     * Identifies the {@link IUniqueIndexLayer} from which the cells are copied.
     *
     * @param copiedCells
     *            The copied cells from the internal clipboard.
     * @return The {@link IUniqueIndexLayer} if the copied cells are collected
     *         from a different layer, or the locally configured
     *         {@link SelectionLayer} in case there are no copied cells in the
     *         internal cell clipboard.
     *
     * @since 1.6
     */
    public IUniqueIndexLayer getPasteLayer(ILayerCell[][] copiedCells) {
        if (copiedCells != null && copiedCells.length > 0 && copiedCells[0].length > 0) {
            for (ILayerCell[] cells : this.clipboard.getCopiedCells()) {
                for (ILayerCell cell : cells) {
                    if (cell != null && cell.getLayer() instanceof IUniqueIndexLayer) {
                        return (IUniqueIndexLayer) cell.getLayer();
                    }
                }
            }
        }
        return this.selectionLayer;
    }

    /**
     * Perform actions prior pasting values from the internal clipboard. E.g.
     * disabling formula evaluation.
     */
    protected void preInternalPaste() {
        // no specific action needed to be performed by default
    }

    /**
     * Perform actions after pasting values from the internal clipboard. E.g.
     * enabling formula evaluation.
     */
    protected void postInternalPaste() {
        // no specific action needed to be performed by default
    }

    @Override
    public Class<PasteDataCommand> getCommandClass() {
        return PasteDataCommand.class;
    }

}
