
import { twoProduct } from "../basic/two-product.js";
import { eDiff } from "../double-expansion/e-diff.js";
import { eEstimate } from "../double-expansion/e-estimate.js";
import { twoDiff } from "../basic/two-diff.js";
import { fastExpansionSum } from "../double-expansion/fast-expansion-sum.js";
import { eCompress } from "../double-expansion/e-compress.js";

const ccwerrboundA = 3.330669073875472e-16;
const ccwerrboundB = 2.220446049250315e-16;
const ccwerrboundC = 1.109335647967049e-31;
const resulterrbound = 3.330669073875471e-16;


/**
 * * Ported from [Shewchuk](http://docs.ros.org/kinetic/api/asr_approx_mvbb/html/Predicates_8cpp_source.html)
 * * see also https://people.eecs.berkeley.edu/~jrs/papers/robustr.pdf
 *
 * * Adaptive exact 2d orientation test.  
 * 
 * * Robust.
 *
 * Return a positive value if the points pa, pb, and pc occur in 
 * counterclockwise order; a negative value if they occur in clockwise order; 
 * and zero if they are collinear.  The result is also a rough approximation of 
 * twice the signed area of the triangle defined by the three points.
 *
 * The result returned is the determinant of a matrix. This determinant is 
 * computed adaptively, in the sense that exact arithmetic is used only to the 
 * degree it is needed to ensure that the returned value has the correct sign.  
 * Hence, orient2d() is usually quite fast, but will run more slowly when the 
 * input points are collinear or nearly so.
 */
function orient2d(A: number[], B: number[], C: number[]) {
    const detleft  = (A[0] - C[0]) * (B[1] - C[1]);
    const detright = (A[1] - C[1]) * (B[0] - C[0]);

    const det = detleft - detright;
 
    let detsum: number;

    if (detleft > 0) {
        if (detright <= 0) {
            // Anti-clockwise
            return det;
        } else {
            detsum = detleft + detright;
        }
    } else if (detleft < 0) {
        if (detright >= 0) {
            // Clockwise
            return det;
        } else {
            detsum = -detleft - detright;
        }
    } else {
        // Anti-clockwise, clockwise or straight
        return det;
    }
 
    if (Math.abs(det) >= ccwerrboundA * detsum) {
        // Anti-clockwise or clockwise
        return det;
    }

    return orient2dAdapt(A, B, C, detsum);
}


function orient2dAdapt(A: number[], B: number[], C: number[], detsum: number) {
    const acx = A[0] - C[0];
    const bcx = B[0] - C[0];
    const acy = A[1] - C[1];
    const bcy = B[1] - C[1];

    const b = eDiff(
        twoProduct(acx, bcy),
        twoProduct(acy, bcx)
    );

    let det = eEstimate(b);

    if (Math.abs(det) >= ccwerrboundB * detsum) {
        // Anti-clockwise or clockwise
        return det;
    }

    const acxtail = twoDiff(A[0], C[0])[0];
    const bcxtail = twoDiff(B[0], C[0])[0];
    const acytail = twoDiff(A[1], C[1])[0];
    const bcytail = twoDiff(B[1], C[1])[0];

    if (acxtail === 0 && acytail === 0 && 
        bcxtail === 0 && bcytail === 0) {

        // Straight
        return det;
    }

    const errbound = ccwerrboundC*detsum + resulterrbound*Math.abs(det);
    det += (acx*bcytail + bcy*acxtail) - (acy*bcxtail + bcx*acytail);
    if (Math.abs(det) >= errbound) {
        return det;
    }

    const a = eDiff(
        twoProduct(acxtail, bcy),
        twoProduct(acytail, bcx)
    );
    const c = fastExpansionSum(b, a);
    
    const d = eDiff(
        twoProduct(acx, bcytail),
        twoProduct(acy, bcxtail)
    );
    const e = fastExpansionSum(c, d);

    const f = eDiff(
        twoProduct(acxtail, bcytail), 
        twoProduct(acytail, bcxtail)
    );

    let D = fastExpansionSum(e, f);
  
    D = eCompress(D);

    return D[D.length - 1];
}


export { orient2d }
