package com.knecon.fforesight.service.layoutparser.processor.services.graphics;

import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.List;
import java.util.Optional;

public class Box {

    public double x1;
    public double y1;
    public double x2;
    public double y2;


    public Box(double x1, double y1, double x2, double y2) {

        this.x1 = x1;
        this.y1 = y1;
        this.x2 = x2;
        this.y2 = y2;
    }


    public Box(Rectangle2D rectangle2D) {

        this.x1 = rectangle2D.getMinX();
        this.y1 = rectangle2D.getMinY();
        this.x2 = rectangle2D.getMaxX();
        this.y2 = rectangle2D.getMaxY();
    }


    public double width() {

        return x2 - x1;
    }


    public double height() {

        return y2 - y1;
    }


    public double xCenter() {

        return (x2 + x1) / 2;
    }


    public double yCenter() {

        return (y2 + y1) / 2;
    }


    public double area() {

        return width() * height();
    }


    public Box scale(double scale) {

        return new Box(x1 * scale, y1 * scale, x2 * scale, y2 * scale);
    }


    public boolean horizontallyAligned(Box other, double tol) {

        return !(other.x1 - tol > x2 || other.x2 + tol < x1);
    }


    public double yDistanceTo(Box other) {

        return Math.min(Math.abs(other.y1 - y2), Math.abs(y2 - other.y1));
    }


    public boolean intersects(Box other, double tol) {

        return !((x2 < other.x1 - tol) || (x1 > other.x2 + tol) || (y2 < other.y1 - tol) || (y1 > other.y2 + tol));
    }


    public boolean intersectsAndOver(Box other, double tol) {

        return (!((x2 < other.x1 - tol) || (x1 > other.x2 + tol) || (y2 < other.y1 - tol) || (y1 > other.y2 + tol))) && other.y1 > y1;
    }


    public boolean intersectsCenter(Box other, double tol) {

        return !((x2 < other.xCenter() - tol) || (x1 > other.xCenter() + tol) || (y2 < other.yCenter() - tol) || (y1 > other.yCenter() + tol));
    }


    public Optional<Box> intersectRegion(Box other, double tol) {

        if (!intersects(other, tol)) {
            return Optional.empty();
        } else {
            var overlapX1 = Math.max(x1, other.x1);
            var overlapY1 = Math.max(y1, other.y1);
            var overlapX2 = Math.min(x2, other.x2);
            var overlapY2 = Math.min(y2, other.y2);
            return Optional.of(new Box(overlapX1, overlapY1, overlapX2, overlapY2));
        }
    }


    public double intersectArea(Box other, double tol) {

        return intersectRegion(other, tol).map(Box::area).orElse(0d);
    }


    public boolean intersectsAny(List<Box> others, double tol) {

        return others.stream().anyMatch(other -> intersects(other, tol));
    }


    public boolean intersectsAnyAndOver(List<Box> others, double tol) {

        return others.stream().anyMatch(other -> intersectsAndOver(other, tol));
    }


    public boolean intersectsCenter(List<Box> others, double tol) {

        return others.stream().anyMatch(other -> intersectsCenter(other, tol));
    }


    public boolean contains(Box other, double tol) {

        return (x1 <= other.x1 + tol) && (y1 <= other.y1 + tol) && (x2 >= other.x2 - tol) && (y2 >= other.y2 - tol);
    }


    public Box container(Box other) {

        var minX = Math.min(x1, other.x1);
        var minY = Math.min(y1, other.y1);
        var maxX = Math.max(x2, other.x2);
        var maxY = Math.max(y2, other.y2);
        return new Box(minX, minY, maxX, maxY);
    }


    public Box transform(AffineTransform affineTransform) {

        Point2D point = affineTransform.transform(new Point2D.Double(x1, y1), null);
        Point2D point2 = affineTransform.transform(new Point2D.Double(x2, y2), null);
        return new Box(Math.min(point.getX(), point2.getX()), Math.min(point.getY(), point2.getY()), Math.max(point.getX(), point2.getX()), Math.max(point.getY(), point2.getY()));
    }

}
