/*******************************************************************************
 * Copyright (c) 2012, 2020 Original authors 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:
 *     Original authors and others - initial API and implementation
 ******************************************************************************/
package org.eclipse.nebula.widgets.nattable.ui.util;

import static org.eclipse.nebula.widgets.nattable.ui.util.CellEdgeEnum.BOTTOM;
import static org.eclipse.nebula.widgets.nattable.ui.util.CellEdgeEnum.LEFT;
import static org.eclipse.nebula.widgets.nattable.ui.util.CellEdgeEnum.NONE;
import static org.eclipse.nebula.widgets.nattable.ui.util.CellEdgeEnum.RIGHT;
import static org.eclipse.nebula.widgets.nattable.ui.util.CellEdgeEnum.TOP;
import static org.eclipse.nebula.widgets.nattable.util.GUIHelper.DEFAULT_RESIZE_HANDLE_SIZE;

import org.eclipse.nebula.widgets.nattable.layer.ILayer;
import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;

public final class CellEdgeDetectUtil {

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

    /**
     * Calculate the column position depending on the cursor's position on the
     * left/right edges of the cell.
     *
     * @param layer
     *            The layer that should be used to calculate the column
     *            position. Typically the NatTable itself.
     * @param clickPoint
     *            The coordinate on which an action happened.
     * @return the column position according to the cell edge.
     *
     * @since 1.6
     */
    public static int getColumnPosition(ILayer layer, Point clickPoint) {
        int columnPosition = layer.getColumnPositionByX(clickPoint.x);
        if (columnPosition >= 0) {
            CellEdgeEnum horizontalCellEdge = getHorizontalCellEdge(layer, clickPoint, DEFAULT_RESIZE_HANDLE_SIZE);
            if (horizontalCellEdge == CellEdgeEnum.LEFT) {
                return columnPosition - 1;
            } else if (horizontalCellEdge == CellEdgeEnum.RIGHT) {
                return columnPosition;
            }
        }
        return -1;
    }

    /**
     * Calculate the column position to resize depending on the cursor's
     * position on the left/right edges of the cell. Does <i>not</i> take into
     * account columns which are not allowed to be resized.
     *
     * @param layer
     *            The layer that should be used to calculate the column
     *            position. Typically the NatTable itself.
     * @param clickPoint
     *            The coordinate on which an action happened.
     * @return the column position according to the cell edge or -1 for an
     *         invalid column, e.g. the left edge of the first column, as a
     *         resize is not possible here.
     */
    public static int getColumnPositionToResize(ILayer layer, Point clickPoint) {
        int columnPosition = layer.getColumnPositionByX(clickPoint.x);
        if (columnPosition >= 0) {
            CellEdgeEnum horizontalCellEdge = getHorizontalCellEdge(layer, clickPoint, DEFAULT_RESIZE_HANDLE_SIZE);
            if (horizontalCellEdge == CellEdgeEnum.LEFT) {
                if (columnPosition == 1) {
                    // can't resize left edge of first column
                    return -1;
                }
                return columnPosition - 1;
            } else if (horizontalCellEdge == CellEdgeEnum.RIGHT) {
                return columnPosition;
            }
        }
        return -1;
    }

    /**
     * Calculate the row position to resize depending on the cursor's position
     * on the top/bottom edges of the cell.
     *
     * @param layer
     *            The layer that should be used to calculate the row position.
     *            Typically the NatTable itself.
     * @param clickPoint
     *            The coordinate on which an action happened.
     * @return the row position according to the cell edge.
     *
     * @since 1.6
     */
    public static int getRowPosition(ILayer layer, Point clickPoint) {
        int rowPosition = layer.getRowPositionByY(clickPoint.y);
        if (rowPosition >= 0) {
            CellEdgeEnum verticalCellEdge = getVerticalCellEdge(layer, clickPoint, DEFAULT_RESIZE_HANDLE_SIZE);
            if (verticalCellEdge == CellEdgeEnum.TOP) {
                return rowPosition - 1;
            } else if (verticalCellEdge == CellEdgeEnum.BOTTOM) {
                return rowPosition;
            }
        }
        return -1;
    }

    /**
     * Calculate the row position to resize depending on the cursor's position
     * on the top/bottom edges of the cell. Does not take into account rows
     * which are not allowed to be resized.
     *
     * @param layer
     *            The layer that should be used to calculate the row position.
     *            Typically the NatTable itself.
     * @param clickPoint
     *            The coordinate on which an action happened.
     * @return the row position according to the cell edge or -1 for an invalid
     *         row, e.g. the top edge of the first row, as a resize is not
     *         possible here.
     */
    public static int getRowPositionToResize(ILayer layer, Point clickPoint) {
        int rowPosition = layer.getRowPositionByY(clickPoint.y);
        if (rowPosition >= 0) {
            CellEdgeEnum verticalCellEdge = getVerticalCellEdge(layer, clickPoint, DEFAULT_RESIZE_HANDLE_SIZE);
            if (verticalCellEdge == CellEdgeEnum.TOP) {
                if (rowPosition == 1) {
                    // can't resize top edge of first row
                    return -1;
                }
                return rowPosition - 1;
            } else if (verticalCellEdge == CellEdgeEnum.BOTTOM) {
                return rowPosition;
            }
        }
        return -1;
    }

    /**
     * Gets the edge (left/right) of the cell which is closer to the click
     * point.
     *
     * @param cellBounds
     *            bounds of the cell containing the click
     * @param clickPt
     *            usually the coordinates of a mouse click
     */
    public static CellEdgeEnum getHorizontalCellEdge(Rectangle cellBounds, Point clickPt) {
        return getHorizontalCellEdge(cellBounds, clickPt, -1);
    }

    public static CellEdgeEnum getHorizontalCellEdge(ILayer layer, Point clickPt) {
        return getHorizontalCellEdge(layer, clickPt, -1);
    }

    public static CellEdgeEnum getHorizontalCellEdge(ILayer layer, Point clickPt, int handleWidth) {
        ILayerCell cell = layer.getCellByPosition(
                layer.getColumnPositionByX(clickPt.x),
                layer.getRowPositionByY(clickPt.y));
        if (cell != null) {
            return getHorizontalCellEdge(cell.getBounds(), clickPt, handleWidth);
        } else {
            return CellEdgeEnum.NONE;
        }
    }

    /**
     * Figure out if the click point is closer to the left/right edge of the
     * cell.
     *
     * @param cellBounds
     *            of the table cell containing the click
     * @param clickPt
     * @param distanceFromEdge
     *            distance from the edge to qualify as <i>close</i> to the cell
     *            edge
     */
    public static CellEdgeEnum getHorizontalCellEdge(Rectangle cellBounds, Point clickPt, int distanceFromEdge) {
        if (distanceFromEdge < 0) {
            distanceFromEdge = cellBounds.width / 2;
        }

        Rectangle left = new Rectangle(
                cellBounds.x,
                cellBounds.y,
                distanceFromEdge,
                cellBounds.height);
        Rectangle right = new Rectangle(
                cellBounds.x + cellBounds.width - distanceFromEdge,
                cellBounds.y,
                distanceFromEdge,
                cellBounds.height);

        if (left.contains(clickPt)) {
            return LEFT;
        } else if (right.contains(clickPt)) {
            return RIGHT;
        } else {
            return NONE;
        }
    }

    /**
     * Gets the edge (top/bottom) of the cell which is closer to the click
     * point.
     *
     * @param cellBounds
     *            bounds of the cell containing the click
     * @param clickPt
     *            usually the coordinates of a mouse click
     */
    public static CellEdgeEnum getVerticalCellEdge(Rectangle cellBounds, Point clickPt) {
        return getVerticalCellEdge(cellBounds, clickPt, -1);
    }

    public static CellEdgeEnum getVerticalCellEdge(ILayer layer, Point clickPt) {
        return getVerticalCellEdge(layer, clickPt, -1);
    }

    public static CellEdgeEnum getVerticalCellEdge(ILayer layer, Point clickPt, int handleHeight) {
        ILayerCell cell = layer.getCellByPosition(
                layer.getColumnPositionByX(clickPt.x),
                layer.getRowPositionByY(clickPt.y));

        if (cell != null) {
            return getVerticalCellEdge(cell.getBounds(), clickPt, handleHeight);
        } else {
            return CellEdgeEnum.NONE;
        }
    }

    /**
     * @see CellEdgeDetectUtil#getHorizontalCellEdge(Rectangle, Point, int)
     */
    private static CellEdgeEnum getVerticalCellEdge(Rectangle cellBounds, Point clickPt, int distanceFromEdge) {
        if (distanceFromEdge < 0) {
            distanceFromEdge = cellBounds.height / 2;
        }

        Rectangle top = new Rectangle(
                cellBounds.x,
                cellBounds.y,
                cellBounds.width,
                distanceFromEdge);
        Rectangle bottom = new Rectangle(
                cellBounds.x,
                cellBounds.y + cellBounds.height - distanceFromEdge,
                cellBounds.width,
                distanceFromEdge);

        if (top.contains(clickPt)) {
            return TOP;
        } else if (bottom.contains(clickPt)) {
            return BOTTOM;
        } else {
            return NONE;
        }
    }
}
