import GeometryPoint from "./GeometryPoint.js";
import GeometryStroke from "./GeometryStroke.js";
import GeometryPointCollection from "./GeometryPointCollection.js";

export default class GeometryRect
{
    /**
     * @param x: float
     * @param y: float
     * @param width: float
     * @param height: float
     */
    constructor(x, y, width, height)
    {
        this.position = new GeometryPoint(x, y);
        this.width = width;
        this.height = height;
    }

    /**
     * Returns if the given point is inside this rect.
     *
     * @param geometryPoint
     * @returns {boolean|boolean}
     */
    isContainingPoint(geometryPoint)
    {
        let containsHorizontally = geometryPoint.x >= this.position.x && geometryPoint.x < this.position.x + this.width;
        let containsVertically = geometryPoint.y >= this.position.y && geometryPoint.y < this.position.y + this.height;

        return containsHorizontally && containsVertically;
    }

    isEqual(rect)
    {
        return rect.x === this.x && rect.y === this.y && rect.width === this.width && rect.height === this.height;
    }

    hasIntersectionWithRect(rect)
    {
        return this.getBorderIntersectonsWithRect(rect).getLength() > 0;
    }

    getBorderTop()
    {
        return new GeometryStroke(
            new GeometryPoint(this.position.x, this.position.y),
            new GeometryPoint(this.position.x + this.width, this.position.y)
        );
    }

    getBorderLeft()
    {
        return new GeometryStroke(
            new GeometryPoint(this.position.x, this.position.y),
            new GeometryPoint(this.position.x, this.position.y + this.height)
        );
    }

    getBorderRight()
    {
        return new GeometryStroke(
            new GeometryPoint(this.position.x + this.width, this.position.y),
            new GeometryPoint(this.position.x + this.width, this.position.y + this.height)
        );
    }

    getBorderBottom() {
        return new GeometryStroke(
            new GeometryPoint(this.position.x, this.position.y + this.height),
            new GeometryPoint(this.position.x + this.width, this.position.y + this.height)
        );
    }

    /**
     * Returns all intersection points between this rect and a line.
     *
     * @param geometryLine
     * @returns {GeometryPointCollection}
     */
    getBorderIntersectionsWithLine(geometryLine)
    {
        let borders = [
            this.getBorderTop(),
            this.getBorderLeft(),
            this.getBorderRight(),
            this.getBorderBottom()
        ];

        let intersections = new GeometryPointCollection();

        borders.forEach(
            (border) => {
                let intersection = border.getIntersectionWithLine(geometryLine);

                if (intersection !== null && this.isContainingPoint(intersection)) {
                    intersections.addGeometryPoint(intersection);
                }
            }
        );

        return intersections;
    }

    /**
     * Returns the intersection points between the border lines and the given stroke.
     *
     * @param stroke
     * @returns {GeometryPointCollection}
     */
    getBorderIntersectionsWithStroke(stroke)
    {
        let borders = [
            this.getBorderTop(),
            this.getBorderLeft(),
            this.getBorderRight(),
            this.getBorderBottom()
        ];

        let intersections = new GeometryPointCollection();

        borders.forEach(
            (border) => {
                let intersection = border.getIntersectionWithStroke(stroke);

                if (intersection !== null && this.isContainingPoint(intersection)) {
                    intersections.addGeometryPoint(intersection);
                }
            }
        );

        return intersections;
    }

    /**
     * Get all intersections between this and another rect as point collection.
     *
     * @param rect GeometryRect
     * @returns {GeometryPointCollection}
     */
    getBorderIntersectonsWithRect(rect)
    {
        let intersections = new GeometryPointCollection();

        let strokes = [
            rect.getBorderTop(),
            rect.getBorderLeft(),
            rect.getBorderRight(),
            rect.getBorderBottom(),

        ];

        strokes.forEach(
            (stroke) => {
                this.getBorderIntersectionsWithStroke(stroke).forEach(
                    (point) => {
                        intersections.addGeometryPoint(point);
                    }
                )
            }
        );

        return intersections;
    }

    /**
     * Returns the four points that define the edges of this rect.
     *
     * @returns {GeometryPointCollection}
     */
    getRectanglePoints()
    {
        let collection = new GeometryPointCollection();

        collection.addGeometryPoint(this.position);
        collection.addGeometryPoint(new GeometryPoint(this.position.x + this.width, this.position.y));
        collection.addGeometryPoint(new GeometryPoint(this.position.x, this.position.y + this.height - 1));
        collection.addGeometryPoint(new GeometryPoint(this.position.x + this.width, this.position.y + this.height - 1));

        return collection;
    }

    /**
     * Returns the intersection area between this and another rect as new rect or null.
     *
     * @param rect GeometryRect
     * @returns {null|GeometryRect}
     */
    getRectIntersection(rect)
    {
        let distanceToOrigin = this.getCenter().getDistanceToPoint(rect.getCenter());

        if (distanceToOrigin > this.getDiagonal() * 0.5 + rect.getDiagonal() * 0.5) {
            return null;
        }

        let intersectionPoints = new GeometryPointCollection();

        this.getRectanglePoints().forEach(
            (point) => {
                if (rect.isContainingPoint(point)) {
                    intersectionPoints.addGeometryPoint(point);
                }
            }
        );

        if (intersectionPoints.getLength() >= 4) {
            return intersectionPoints.getRect();
        }

        rect.getRectanglePoints().forEach(
            (point) => {
                if (this.isContainingPoint(point)) {
                    intersectionPoints.addGeometryPoint(point);
                }
            }
        );

        if (intersectionPoints.getLength() >= 4) {
            return intersectionPoints.getRect();
        }

        this.getBorderIntersectonsWithRect(rect).forEach(
            (point) => {
                intersectionPoints.addGeometryPoint(point);
            }
        );

        if (intersectionPoints.getLength() >= 2) {
            return intersectionPoints.getRect();
        }

        return null;
    }

    getCenter()
    {
        return new GeometryPoint(
            this.position.x + this.width * 0.5,
            this.position.y + this.height * 0.5
        );
    }

    getDiagonal()
    {
        return new GeometryStroke(
            this.position,
            new GeometryPoint(this.position.x + this.width, this.position.y + this.height)
        );
    }

    /**
     * Visualizes the rect.
     *
     * @param context Context
     * @param color string
     */
    draw(context, color = 'red')
    {
        context.fillStyle = color;
        context.fillRect(this.position.x, this.position.y, this.width, this.height);
        this.getBorderTop().draw(context);
        this.getBorderLeft().draw(context);
        this.getBorderRight().draw(context);
        this.getBorderBottom().draw(context);
    }

}