namespace game {
    export class Hitbox {
        hash: number;
        parent: Sprite;
        ox: Fx8;
        oy: Fx8;
        width: Fx8;
        height: Fx8;

        constructor(parent: Sprite, width: Fx8, height: Fx8, ox: Fx8, oy: Fx8) {
            this.hash = parent.calcDimensionalHash();
            this.parent = parent;
            this.width = width;
            this.height = height;
            this.ox = ox;
            this.oy = oy;
        }

        get left() {
            return Fx.add(this.ox, this.parent._x);
        }

        get top() {
            return Fx.add(this.oy, this.parent._y);
        }

        get right() {
            return Fx.sub(
                Fx.add(this.width, this.left),
                Fx.oneFx8
            );
        }

        get bottom() {
            return Fx.sub(
                Fx.add(this.height, this.top),
                Fx.oneFx8
            );
        }

        isValid() {
            return this.hash === this.parent.calcDimensionalHash();
        }

        contains(x: Fx8, y: Fx8): boolean {
            return (x >= this.left) && (x <= this.right) && (y >= this.top) && (y <= this.bottom);
        }

        updateIfInvalid() {
            if (this.isValid())
                return;

            const newHitBox = game.calculateHitBox(this.parent);

            const oMinX = this.ox;
            const oMinY = this.oy;
            const oMaxX = Fx.add(oMinX, this.width);
            const oMaxY = Fx.add(oMinY, this.height);

            const nMinX = newHitBox.ox;
            const nMinY = newHitBox.oy;
            const nMaxX = Fx.add(nMinX, newHitBox.width);
            const nMaxY = Fx.add(nMinY, newHitBox.height);

            // total diff in x / y corners between the two hitboxes
            const xDiff = Fx.add(
                Fx.abs(Fx.sub(oMinX, nMinX)),
                Fx.abs(Fx.sub(oMaxX, nMaxX))
            );
            const yDiff = Fx.add(
                Fx.abs(Fx.sub(oMinY, nMinY)),
                Fx.abs(Fx.sub(oMaxY, nMaxY))
            );

            // If it's just a small change to the hitbox on one axis,
            // don't change the dimensions to avoid random clipping
            if (xDiff > Fx.twoFx8) {
                this.ox = nMinX;
                this.width = newHitBox.width;
            }
            if (yDiff > Fx.twoFx8) {
                this.oy = nMinY;
                this.height = newHitBox.height;
            }
            this.hash = newHitBox.hash;
        }

        overlapsWith(other: Hitbox): boolean {
            this.updateIfInvalid();
            other.updateIfInvalid();
            if (
                this.left > other.right ||
                this.top > other.bottom ||
                this.right < other.left ||
                this.bottom < other.top
            ) {
                return false;
            }
            return true;
        }
    }


    export function calculateHitBox(s: Sprite): Hitbox {
        if (s._hitbox && s._hitbox.isValid())
            return s._hitbox;

        if (s._rotatedBBox) {
            return new Hitbox(s, Fx8(s._rotatedBBox.width), Fx8(s._rotatedBBox.height), Fx.zeroFx8, Fx.zeroFx8);
        }

        const i = s.image;
        let minX = Fx8(i.width);
        let minY = Fx8(i.height);
        let maxX = Fx.zeroFx8;
        let maxY = Fx.zeroFx8;

        for (let c = 0, fxc = Fx.zeroFx8; c < i.width; c++, fxc = Fx.add(fxc, Fx.oneFx8)) {
            for (let r = 0, fxr = Fx.zeroFx8; r < i.height; r++, fxr = Fx.add(fxr, Fx.oneFx8)) {
                if (i.getPixel(c, r)) {
                    minX = Fx.min(minX, fxc);
                    minY = Fx.min(minY, fxr);
                    maxX = Fx.max(maxX, fxc);
                    maxY = Fx.max(maxY, fxr);
                }
            }
        }

        minX = Fx.mul(minX, s._sx);
        minY = Fx.mul(minY, s._sy);
        maxX = Fx.mul(maxX, s._sx);
        maxY = Fx.mul(maxY, s._sy);
        const width  = Fx.add(Fx.sub(maxX, minX), s._sx);
        const height = Fx.add(Fx.sub(maxY, minY), s._sy);

        return new Hitbox(s, width, height, Fx.floor(minX), Fx.floor(minY));
    }
}