const INSIDE = 0; // 0000
const LEFT = 1;   // 0001
const RIGHT = 2;  // 0010
const BOTTOM = 4; // 0100
const TOP = 8;    // 1000

/**
 * Compute the bit code for a point (x, y) using the clip rectangle
 * @param {number} box_x0
 * @param {number} box_y0
 * @param {number} box_x1
 * @param {number} box_y1
 * @param {number} x
 * @param {number} y
 * @return {number}
 */
function compute_out_code(
    box_x0, box_y0,
    box_x1, box_y1,
    x, y
) {
    let code = INSIDE;  // initialised as being inside of clip window

    if (x < box_x0) {
        // to the left of clip window
        code |= LEFT;
    } else if (x > box_x1) {
        // to the right of clip window
        code |= RIGHT;
    }

    if (y < box_y0) {
        // below the clip window
        code |= BOTTOM;
    } else if (y > box_y1) {
        // above the clip window
        code |= TOP;
    }

    return code;
}

/**
 * Implements Cohen–Sutherland algorithm
 * @param {number[]|Float32Array|Float64Array} result
 * @param {number} result_offset
 * @param {number} box_x0
 * @param {number} box_y0
 * @param {number} box_x1
 * @param {number} box_y1
 * @param {number} line_x0
 * @param {number} line_y0
 * @param {number} line_x1
 * @param {number} line_y1
 * @returns {boolean}
 */
export function aabb2_clip_line_cohen_sutherland(
    result, result_offset,
    box_x0, box_y0,
    box_x1, box_y1,
    line_x0, line_y0,
    line_x1, line_y1
) {
    let x0 = line_x0;
    let y0 = line_y0;
    let x1 = line_x1;
    let y1 = line_y1;

    // compute out codes for P0, P1, and whatever point lies outside the clip rectangle
    let out_code_0 = compute_out_code(
        box_x0, box_y0,
        box_x1, box_y1,
        x0, y0
    );
    let out_code_1 = compute_out_code(
        box_x0, box_y0,
        box_x1, box_y1,
        x1, y1
    );

    let x, y;

    while (true) {
        if ((out_code_0 | out_code_1) === 0) {
            // bitwise OR is 0: both points inside window; trivially accept and exit loop

            result[result_offset] = x0;
            result[result_offset + 1] = y0;
            result[result_offset + 2] = x1;
            result[result_offset + 3] = y1;

            return true;
        } else if ((out_code_0 & out_code_1) !== 0) {
            // bitwise AND is not 0: both points share an outside zone (LEFT, RIGHT, TOP,
            // or BOTTOM), so both must be outside window; exit loop (accept is false)
            return false;
        } else {
            // failed both tests, so calculate the line segment to clip
            // from an outside point to an intersection with clip edge

            // At least one endpoint is outside the clip rectangle; pick it.
            const outcodeOut = out_code_1 > out_code_0 ? out_code_1 : out_code_0;

            // Now find the intersection point;
            // use formulas:
            //   slope = (y1 - y0) / (x1 - x0)
            //   x = x0 + (1 / slope) * (ym - y0), where ym is ymin or ymax
            //   y = y0 + slope * (xm - x0), where xm is xmin or xmax
            // No need to worry about divide-by-zero because, in each case, the
            // outcode bit being tested guarantees the denominator is non-zero
            if (outcodeOut & TOP) {
                // point is above the clip window
                x = x0 + (x1 - x0) * (box_y1 - y0) / (y1 - y0);
                y = box_y1;
            } else if (outcodeOut & BOTTOM) {
                // point is below the clip window
                x = x0 + (x1 - x0) * (box_y0 - y0) / (y1 - y0);
                y = box_y0;
            } else if (outcodeOut & RIGHT) {
                // point is to the right of clip window
                y = y0 + (y1 - y0) * (box_x1 - x0) / (x1 - x0);
                x = box_x1;
            } else if (outcodeOut & LEFT) {
                // point is to the left of clip window
                y = y0 + (y1 - y0) * (box_x0 - x0) / (x1 - x0);
                x = box_x0;
            }

            // Now we move outside point to intersection point to clip
            // and get ready for next pass.
            if (outcodeOut === out_code_0) {
                x0 = x;
                y0 = y;
                out_code_0 = compute_out_code(
                    box_x0, box_y0,
                    box_x1, box_y1, x0, y0
                );
            } else {
                x1 = x;
                y1 = y;
                out_code_1 = compute_out_code(
                    box_x0, box_y0,
                    box_x1, box_y1,
                    x1, y1
                );
            }
        }
    }
}