import { vec2 } from '../math/vec2';

var tmp = vec2.create();
/**
 * 左下角为坐标原点,向上向右为正
 * 最简单的无旋转矩形式碰撞检测;
 * Axis aligned bounding box class.
 * @class AABB
 * @constructor
 * @param {Object}  [options]
 * @param {Array}   [options.upperBound]
 * @param {Array}   [options.lowerBound]
 * @example
 *     var aabb = new AABB({
 *         upperBound: [1, 1],
 *         lowerBound: [-1, -1]
 *     });
 */
export class AABB {
    /**
     * 左下角点
     * The lower bound of the bounding box.
     * @property lowerBound
     * @type {Array}
     */
    lowerBound: Float32Array;
    /**
     * 右上角点
     * The upper bound of the bounding box.
     * @property upperBound
     * @type {Array}
     */
    upperBound: Float32Array;

    constructor(options?) {
        options = options || {};
        this.lowerBound = options.lowerBound ? vec2.clone(options.lowerBound) : vec2.create();

        this.upperBound = options.upperBound ? vec2.clone(options.upperBound) : vec2.create();
    }



    /**
     * 计算一组点的AABB,提供 点,位置,旋转
     * Set the AABB bounds from a set of points, transformed by the given position and angle.
     * @method setFromPoints
     * @param {Array} points An array of vec2's. vec2的数组.如[[0,1],[0,6],[2,1]]
     * @param {Array} position 偏移信息,所有点的
     * @param {number} [angle=0]  旋转信息,所有点的
     * @param {number} [skinSize=0] Some margin to be added to the AABB.
     */
    setFromPoints(points, position, angle = 0, skinSize = 0) {
        var l = this.lowerBound,
            u = this.upperBound;

        // Set to the first point
        if (angle !== 0) {
            vec2.rotate(l, points[0], angle);
        } else {
            vec2.copy(l, points[0]);
        }
        vec2.copy(u, l);

        // Compute cosines and sines just once
        var cosAngle = Math.cos(angle),
            sinAngle = Math.sin(angle);
        for (var i = 1; i < points.length; i++) {
            var p = points[i];

            if (angle !== 0) {
                var x = p[0],
                    y = p[1];
                tmp[0] = cosAngle * x - sinAngle * y;
                tmp[1] = sinAngle * x + cosAngle * y;
                p = tmp;
            }

            for (var j = 0; j < 2; j++) {
                if (p[j] > u[j]) {
                    u[j] = p[j];
                }
                if (p[j] < l[j]) {
                    l[j] = p[j];
                }
            }
        }

        // Add offset
        if (position) {
            vec2.add(l, l, position);
            vec2.add(u, u, position);
        }

        if (skinSize) {
            l[0] -= skinSize;
            l[1] -= skinSize;
            u[0] += skinSize;
            u[1] += skinSize;
        }
    };

    /**
     * 复制
     * Copy bounds from an AABB to this AABB
     * @method copy
     * @param  {AABB} aabb
     */
    copy(aabb) {
        vec2.copy(this.lowerBound, aabb.lowerBound);
        vec2.copy(this.upperBound, aabb.upperBound);
    };

    /**
     * 扩展AABB
     * Extend this AABB so that it covers the given AABB too.
     * @method extend
     * @param  {AABB} aabb
     */
    extend(aabb: AABB) {
        var lower = this.lowerBound,
            upper = this.upperBound;

        // Loop over x and y
        var i = 2;
        while (i--) {
            // Extend lower bound
            var l = aabb.lowerBound[i];
            if (lower[i] > l) {
                lower[i] = l;
            }

            // Upper
            var u = aabb.upperBound[i];
            if (upper[i] < u) {
                upper[i] = u;
            }
        }
    };

    /**
     * 判断与所给AABB是否重叠
     * Returns true if the given AABB overlaps this AABB.
     * @method overlaps
     * @param  {AABB} aabb
     * @return {Boolean}
     */
    overlaps(aabb: AABB): boolean {
        var l1 = this.lowerBound,
            u1 = this.upperBound,
            l2 = aabb.lowerBound,
            u2 = aabb.upperBound;

        //      l2        u2
        //      |---------|
        // |--------|
        // l1       u1

        return ((l2[0] <= u1[0] && u1[0] <= u2[0]) || (l1[0] <= u2[0] && u2[0] <= u1[0])) &&
            ((l2[1] <= u1[1] && u1[1] <= u2[1]) || (l1[1] <= u2[1] && u2[1] <= u1[1]));
    };

    /**
     * 判断是否包含某点
     * @method containsPoint
     * @param  {Array} point
     * @return {boolean}
     */
    containsPoint(point) {
        var l = this.lowerBound,
            u = this.upperBound;
        return l[0] <= point[0] && point[0] <= u[0] && l[1] <= point[1] && point[1] <= u[1];
    };

    /**
     * 判断射线碰撞
     * Check if the AABB is hit by a ray.
     * @method overlapsRay
     * @param  {Ray} ray
     * @return {number} -1 if no hit, a number between 0 and 1 if hit, indicating the position between the "from" and "to" points.
     * @example
     *     var aabb = new AABB({
     *         upperBound: [1, 1],
     *         lowerBound: [-1, -1]
     *     });
     *     var ray = new Ray({
     *         from: [-2, 0],
     *         to: [0, 0]
     *     });
     *     var fraction = aabb.overlapsRay(ray); // fraction == 0.5
     */
    overlapsRay(ray) {

        // ray.direction is unit direction vector of ray
        var dirFracX = 1 / ray.direction[0];
        var dirFracY = 1 / ray.direction[1];

        // this.lowerBound is the corner of AABB with minimal coordinates - left bottom, rt is maximal corner
        var from = ray.from;
        var lowerBound = this.lowerBound;
        var upperBound = this.upperBound;
        var t1 = (lowerBound[0] - from[0]) * dirFracX;
        var t2 = (upperBound[0] - from[0]) * dirFracX;
        var t3 = (lowerBound[1] - from[1]) * dirFracY;
        var t4 = (upperBound[1] - from[1]) * dirFracY;

        var tmin = Math.max(Math.max(Math.min(t1, t2), Math.min(t3, t4)));
        var tmax = Math.min(Math.min(Math.max(t1, t2), Math.max(t3, t4)));

        // if tmax < 0, ray (line) is intersecting AABB, but whole AABB is behing us
        if (tmax < 0) {
            //t = tmax;
            return -1;
        }

        // if tmin > tmax, ray doesn't intersect AABB
        if (tmin > tmax) {
            //t = tmax;
            return -1;
        }

        return tmin / ray.length;
    };
}