import { intersects1D } from "../../math/interval/intersects1D.js";
import { max2 } from "../../math/max2.js";
import { min2 } from "../../math/min2.js";
import Vector2 from "../Vector2.js";
import { aabb2_contains } from "./aabb/aabb2_contains.js";
import { aabb2_overlap_exists } from "./aabb/aabb2_overlap_exists.js";

class Rectangle {
    /**
     *
     * @param {number} x
     * @param {number} y
     * @param {number} width
     * @param {number} height
     * @constructor
     */
    constructor(x = 0, y = 0, width = 0, height = 0) {

        /**
         * @readonly
         * @type {Vector2}
         */
        this.position = new Vector2(x, y);

        /**
         * @readonly
         * @type {Vector2}
         */
        this.size = new Vector2(width, height);
    }

    /**
     *
     * @return {number}
     */
    get x0() {
        return this.position.x;
    }

    /**
     *
     * @return {number}
     */
    get x1() {
        return this.position.x + this.size.x;
    }

    /**
     *
     * @return {number}
     */
    get y0() {
        return this.position.y;
    }

    /**
     *
     * @return {number}
     */
    get y1() {
        return this.position.y + this.size.y;
    }

    /**
     *
     * @param {number} x
     * @param {number} y
     * @param {number} width
     * @param {number} height
     */
    set(x, y, width, height) {
        this.size.set(width, height);
        this.position.set(x, y);
    }

    /**
     *
     * @returns {Rectangle}
     */
    clone() {
        const position = this.position;
        const size = this.size;

        return new Rectangle(
            position.x, position.y,
            size.x, size.y
        );
    }

    /**
     *
     * @param {Rectangle} other
     */
    copy(other) {
        this.position.copy(other.position);
        this.size.copy(other.size);
    }

    /**
     *
     * @param {number} x0
     * @param {number} y0
     * @param {number} x1
     * @param {number} y1
     * @returns {boolean}
     */
    _intersects(x0, y0, x1, y1) {
        const p = this.position;
        const s = this.size;

        const _x0 = p.x;
        const _y0 = p.y;

        return intersects1D(x0, x1, _x0, s.x + _x0) && intersects1D(y0, y1, _y0, _y0 + s.y);
    }

    /**
     *
     * @param {Rectangle} other
     * @returns {boolean}
     */
    intersects(other) {
        const x0 = other.position.x;
        const y0 = other.position.y;
        const y1 = other.size.y + y0;
        const x1 = other.size.x + x0;
        return this._intersects(x0, y0, x1, y1);
    }

    /**
     *
     * @param {number}  x0
     * @param {number} y0
     * @param {number} x1
     * @param {number} y1
     * @returns {boolean}
     */
    _overlaps(x0, y0, x1, y1) {
        const p = this.position;
        const s = this.size;

        const _x0 = p.x;
        const _y0 = p.y;
        const _x1 = s.x + _x0;
        const _y1 = _y0 + s.y;

        return aabb2_overlap_exists(
            _x0, _y0, _x1, _y1,
            x0, y0, x1, y1
        );
    }

    /**
     *
     * @param {Rectangle} other
     * @returns {boolean}
     */
    overlaps(other) {
        const x0 = other.position.x;
        const y0 = other.position.y;
        const y1 = other.size.y + y0;
        const x1 = other.size.x + x0;

        return this._overlaps(x0, y0, x1, y1);
    }

    /**
     *
     * @param {number} x0
     * @param {number} y0
     * @param {number} x1
     * @param {number} y1
     */
    _resizeToFit(x0, y0, x1, y1) {
        const size = this.size;

        const _x0 = this.position.x;
        const _y0 = this.position.y;

        let _y1 = size.y + _y0;
        let _x1 = size.x + _x0;

        if (Number.isNaN(_x1)) {
            _x1 = -Infinity;
        }
        if (Number.isNaN(_y1)) {
            _y1 = -Infinity;
        }

        const nX0 = min2(x0, _x0);
        const nY0 = min2(y0, _y0);
        const nX1 = max2(x1, _x1);
        const nY1 = max2(y1, _y1);

        this.position.set(nX0, nY0);
        size.set(nX1 - nX0, nY1 - nY0);
    }

    /**
     *
     * @param {Rectangle} other
     */
    resizeToFit(other) {
        const x0 = other.position.x;
        const y0 = other.position.y;
        const y1 = other.size.y + y0;
        const x1 = other.size.x + x0;

        return this._resizeToFit(x0, y0, x1, y1);
    }

    /**
     *
     * @param {number} x0
     * @param {number} y0
     * @param {number} x1
     * @param {number} y1
     * @returns {boolean}
     */
    _contains(x0, y0, x1, y1) {
        const size = this.size;

        const position = this.position;

        const _x0 = position.x;
        const _y0 = position.y;

        const _y1 = size.y + _y0;
        const _x1 = size.x + _x0;

        return aabb2_contains(
            _x0, _y0, _x1, _y1,
            x0, y0, x1, y1
        );
    }

    /**
     *
     * @param {Rectangle} other
     * @returns {boolean}
     */
    contains(other) {
        const x0 = other.position.x;
        const y0 = other.position.y;

        const y1 = other.size.y + y0;
        const x1 = other.size.x + x0;

        return this._contains(x0, y0, x1, y1);
    }

    /**
     *
     * @param {Vector2} result
     */
    computeCenter(result) {
        const p = this.position;
        const s = this.size;

        const x = p.x + s.x / 2;
        const y = p.y + s.y / 2;

        result.set(x, y);
    }

    /**
     *
     * @return {number}
     */
    computeArea() {
        return this.size.x * this.size.y;
    }

    /**
     *
     * @param {Array.<number>|Float32Array|Float64Array|Uint8Array} target
     * @param {number} [targetOffset=0]
     */
    toArray(target, targetOffset = 0) {
        target[targetOffset] = this.position.x;
        target[targetOffset + 1] = this.position.y;
        target[targetOffset + 2] = this.size.x;
        target[targetOffset + 3] = this.size.y;
    }

    toJSON() {
        return {
            position: this.position.toJSON(),
            size: this.size.toJSON()
        };
    }

    fromJSON(json) {
        this.position.fromJSON(json.position);
        this.size.fromJSON(json.size);
    }
}

/**
 * @readonly
 * @type {boolean}
 */
Rectangle.prototype.isRectangle = true;

export default Rectangle;
