/*******************************************************************************
 * Copyright (c) 2018 Ericsson
 *
 * All rights reserved. This program and the accompanying materials are
 * made available under the terms of the Eclipse Public License 2.0 which
 * accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *******************************************************************************/

package org.eclipse.tracecompass.internal.tmf.ui.util;

import org.eclipse.swt.graphics.Rectangle;

/**
 * Cohen Sutherland line clipping algorithm, developed in 1967, public domain,
 * clean room implemented.
 *
 * @see <a href=
 *      "https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm">Cohen
 *      Sutherland algorithm (Wikipedia)</a>
 *
 * @author Matthew Khouzam
 *
 */
public final class LineClipper {

    /**
     * Quadrant bitmasks for Cohen Sutherland
     */
    private static final int DOWN = 1 << 3;
    private static final int UP = 1 << 2;
    private static final int RIGHT = 1 << 1;
    private static final int LEFT = 1 << 0;
    private static final int INSIDE = 0;

    private LineClipper() {
        // do nothing
    }

    /**
     * Computer graphics algorithm used for line clipping, workaround for Cairo bug
     *
     * https://bugs.eclipse.org/bugs/show_bug.cgi?id=470115
     *
     * https://bugs.freedesktop.org/show_bug.cgi?id=103840
     *
     * @param bounds
     *            the clipping rectangle
     * @param x0
     *            point 0 x
     * @param y0
     *            point 0 y
     * @param x1
     *            point 1 x
     * @param y1
     *            point 1 y
     * @return the clipped rectangle of the arrow
     */
    public static Rectangle clip(Rectangle bounds, int x0, int y0, int x1, int y1) {
        int maxLoops = 3;
        int quadrant0 = computeQuadrant(x0, y0, bounds);
        int quadrant1 = computeQuadrant(x1, y1, bounds);
        if (quadrant0 == 0 && quadrant1 == 0) {
            return new Rectangle(x0, y0, x1 - x0, y1 - y0);
        }
        if (guaranteedOut(quadrant0, quadrant1) || bounds.width == 0 || bounds.height == 0) {
            return null;
        }
        long deltaY = (long) y1 - (long) y0;
        long deltaX = (long) x1 - (long) x0;
        double slope = deltaY / (double) deltaX;
        double antiSlope = deltaX / (double) deltaY;
        int newX0 = x0;
        int newY0 = y0;
        int newX1 = x1;
        int newY1 = y1;
        // log n iterations
        for (int i = 0; i < maxLoops && quadrant0 != 0; i++) {
            if (guaranteedOut(quadrant0, quadrant1)) {
                return null;
            }
            if ((quadrant0 & UP) != 0) {
                newX0 = (int) (newX0 + antiSlope * (bounds.y + bounds.height - newY0));
                newY0 = bounds.height + bounds.y;
            } else if ((quadrant0 & DOWN) != 0) {
                newX0 = (int) (newX0 + antiSlope * (bounds.y - newY0));
                newY0 = bounds.y;
            } else if ((quadrant0 & RIGHT) != 0) {
                newY0 = (int) (newY0 + slope * (bounds.x + bounds.width - newX0));
                newX0 = bounds.x + bounds.width;
            } else {
                newY0 = (int) (newY0 + slope * (bounds.x - newX0));
                newX0 = bounds.x;
            }
            quadrant0 = computeQuadrant(newX0, newY0, bounds);
        }
        // ditto
        for (int i = 0; i < maxLoops && quadrant1 != 0; i++) {
            if (guaranteedOut(quadrant0, quadrant1)) {
                return null;
            }
            if ((quadrant1 & UP) != 0) {
                newX1 = (int) (newX1 + antiSlope * (bounds.y + bounds.height - newY1));
                newY1 = bounds.height + bounds.y;
            } else if ((quadrant1 & DOWN) != 0) {
                newX1 = (int) (newX1 + antiSlope * (bounds.y - newY1));
                newY1 = bounds.y;
            } else if ((quadrant1 & RIGHT) != 0) {
                newY1 = (int) (newY1 + slope * (bounds.x + bounds.width - newX1));
                newX1 = bounds.x + bounds.width;
            } else {
                newY1 = (int) (newY1 + slope * (bounds.x - newX1));
                newX1 = bounds.x;
            }
            quadrant1 = computeQuadrant(newX1, newY1, bounds);
        }
        return (guaranteedOut(quadrant0, quadrant1)) ? null : new Rectangle(newX0, newY0, newX1 - newX0, newY1 - newY0);
    }

    private static boolean guaranteedOut(int quadrant0, int quadrant1) {
        return (quadrant0 & quadrant1) != 0;
    }

    /**
     * Cohen Sutherland compute algorithm
     * <table>
     * <tr>
     * <th>0101</th>
     * <th>0100</th>
     * <th>0110</th>
     * </tr>
     * <tr>
     * <th>0001</th>
     * <th>0000</th>
     * <th>0010</th>
     * </tr>
     * <tr>
     * <th>1001</th>
     * <th>1000</th>
     * <th>1010</th>
     * </tr>
     * </table>
     *
     * @param x
     *            point x
     * @param y
     *            point y
     * @param bounds
     *            bounds rectangle
     * @return a quadrant
     */
    private static int computeQuadrant(int x, int y, Rectangle bounds) {
        int retCode = INSIDE;
        if (x < bounds.x) {
            retCode |= LEFT;
        } else if (x > (bounds.x + bounds.width)) {
            retCode |= RIGHT;
        }
        if (y < bounds.y) {
            retCode |= DOWN;
        } else if (y > (bounds.y + bounds.height)) {
            retCode |= UP;
        }
        return retCode;
    }

}
