using OpenNest.Converters;
using OpenNest.Geometry;
using OpenNest.Math;
using System.Collections.Generic;
using System.Linq;

namespace OpenNest.Engine
{
    public enum PartType { Rectangle, Circle, Irregular }

    public struct ClassificationResult
    {
        public PartType Type;
        public double Rectangularity;
        public double Circularity;
        public double PerimeterRatio;
        public double PrimaryAngle;
    }

    public static class PartClassifier
    {
        public const double RectangularityThreshold = 0.92;
        public const double PerimeterRatioThreshold = 0.85;
        public const double CircularityThreshold = 0.95;

        public static ClassificationResult Classify(Drawing drawing)
        {
            var result = new ClassificationResult { Type = PartType.Irregular };

            var entities = ConvertProgram.ToGeometry(drawing.Program)
                .Where(e => e.Layer != SpecialLayers.Rapid);

            var shapes = ShapeBuilder.GetShapes(entities);

            if (shapes.Count == 0)
                return result;

            // Find the largest shape (outer perimeter).
            var perimeter = shapes[0];
            var perimeterArea = perimeter.Area();

            for (var i = 1; i < shapes.Count; i++)
            {
                var area = shapes[i].Area();
                if (area > perimeterArea)
                {
                    perimeter = shapes[i];
                    perimeterArea = area;
                }
            }

            // Convert to polygon for hull/MBR computation.
            var polygon = perimeter.ToPolygonWithTolerance(0.1);

            if (polygon == null || polygon.Vertices.Count < 3)
                return result;

            // Compute convex hull.
            var hull = ConvexHull.Compute(polygon.Vertices);
            var hullArea = hull.Area();

            // Compute MBR via rotating calipers.
            var mbr = RotatingCalipers.MinimumBoundingRectangle(hull);
            var mbrArea = mbr.Area;
            var mbrPerimeter = 2 * (mbr.Width + mbr.Height);

            // Share the single angle formula with CanonicalAngle (no duplicate MBR compute).
            result.PrimaryAngle = CanonicalAngle.FromMbr(mbr);

            // Drawing perimeter for circularity and perimeter ratio.
            var drawingPerimeter = polygon.Perimeter();

            // Circularity: 4*PI*area / perimeter^2. Circles ~ 1.0.
            if (drawingPerimeter > Tolerance.Epsilon)
                result.Circularity = 4 * System.Math.PI * perimeterArea / (drawingPerimeter * drawingPerimeter);

            // Check circle first (rotationally invariant).
            if (result.Circularity >= CircularityThreshold)
            {
                result.Type = PartType.Circle;
                return result;
            }

            // Rectangularity: hull area / MBR area.
            if (mbrArea > Tolerance.Epsilon)
                result.Rectangularity = hullArea / mbrArea;

            // Perimeter ratio: MBR perimeter / drawing perimeter.
            if (drawingPerimeter > Tolerance.Epsilon)
                result.PerimeterRatio = mbrPerimeter / drawingPerimeter;

            // Rectangle: both metrics pass thresholds.
            if (result.Rectangularity >= RectangularityThreshold
                && result.PerimeterRatio >= PerimeterRatioThreshold)
            {
                result.Type = PartType.Rectangle;
                return result;
            }

            result.Type = PartType.Irregular;
            return result;
        }
    }
}
