using MaxwellFramework.Core.Attributes;
using System;
using MainShell.Common;
using MainShell.Recipe.Models;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Windows;

namespace MainShell.Process
{
    [Singleton]
    public class WaferScanPlanner : IWaferScanPlanner
    {
        private readonly IWaferMachineAdapter _waferMachineAdapter;

        public WaferScanPlanner(IWaferMachineAdapter waferMachineAdapter)
        {
            _waferMachineAdapter = waferMachineAdapter ?? throw new ArgumentNullException(nameof(waferMachineAdapter));
        }

        public WaferScanPlan CreatePlan(WaferDiePositionContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            WaferScanSettings scanSettings = context.ScanSettings ?? throw new InvalidOperationException("DiePosition: scan settings are required for scan planning.");
            Point cameraFieldOfView = _waferMachineAdapter.GetCameraFieldOfView(context);
            ValidateCameraFieldOfView(cameraFieldOfView);

            WaferScanArea scanArea = BuildScanArea(context, scanSettings, cameraFieldOfView);
            double stepX = CalculateStep(cameraFieldOfView.X, scanSettings.OverlapRateX);
            double stepY = CalculateStep(cameraFieldOfView.Y, scanSettings.OverlapRateY);
            int scanColumnCount = CalculateScanCount(scanArea.WaferWidth, cameraFieldOfView.X, stepX);
            int scanRowCount = CalculateScanCount(scanArea.WaferHeight, cameraFieldOfView.Y, stepY);
            List<Point> rawPathPoints = BuildRawPathPoints(scanArea, scanSettings, stepX, stepY, scanColumnCount, scanRowCount);
            List<WaferPlannerPointAdjustment> adjustments = new List<WaferPlannerPointAdjustment>();
            List<Point> finalPathPoints = BuildAdjustedPathPoints(rawPathPoints, context, scanSettings, adjustments);

            Point startPoint = finalPathPoints.Count > 0 ? finalPathPoints[0] : scanArea.StartPoint;
            Point endPoint = finalPathPoints.Count > 0 ? finalPathPoints[finalPathPoints.Count - 1] : scanArea.EndPoint;

            WaferScanPlan scanPlan = new WaferScanPlan();
            scanPlan.ScanRowCount = scanRowCount;
            scanPlan.ScanColumnCount = scanColumnCount;
            scanPlan.PathPoints = finalPathPoints;
            scanPlan.RawPathPoints = rawPathPoints;
            scanPlan.FinalPathPoints = finalPathPoints;
            scanPlan.ScanArea = scanArea;
            scanPlan.StartPoint = startPoint;
            scanPlan.EndPoint = endPoint;
            scanPlan.StepX = stepX;
            scanPlan.StepY = stepY;
            scanPlan.OverlapX = NormalizeOverlap(scanSettings.OverlapRateX);
            scanPlan.OverlapY = NormalizeOverlap(scanSettings.OverlapRateY);
            scanPlan.Adjustments = adjustments;
            scanPlan.PathGenerationMessage = string.Format(
                CultureInfo.InvariantCulture,
                "DiePosition: generated serpentine scan plan with {0} points ({1}x{2}), stepX={3:F3}, stepY={4:F3}, adjusted={5}.",
                scanPlan.PathPointCount,
                scanColumnCount,
                scanRowCount,
                stepX,
                stepY,
                adjustments.Count(x => x.WasAdjusted));
            scanPlan.SoftLimitAdjustedCount = adjustments.Count(x => x.WasAdjusted);
            scanPlan.AddedCoveragePointCount = 0;
            context.ScanArea = scanArea;
            return scanPlan;
        }

        private static void ValidateCameraFieldOfView(Point cameraFieldOfView)
        {
            if (cameraFieldOfView.X <= 0d || cameraFieldOfView.Y <= 0d)
            {
                throw new InvalidOperationException("DiePosition: camera field of view must be greater than zero.");
            }
        }

        private static WaferScanArea BuildScanArea(WaferDiePositionContext context, WaferScanSettings scanSettings, Point cameraFieldOfView)
        {
            double waferWidth = context.WaferRecipe != null && context.WaferRecipe.WaferInfo != null ? context.WaferRecipe.WaferInfo.WaferWidth : 0d;
            double waferHeight = context.WaferRecipe != null && context.WaferRecipe.WaferInfo != null ? context.WaferRecipe.WaferInfo.WaferHeight : 0d;
            double effectiveWaferWidth = Math.Max(cameraFieldOfView.X, waferWidth);
            double effectiveWaferHeight = Math.Max(cameraFieldOfView.Y, waferHeight);
            double endX = scanSettings.StartPointX + ResolveDirectionSign(scanSettings.ScanDirectionX) * Math.Max(0d, effectiveWaferWidth - cameraFieldOfView.X);
            double endY = scanSettings.StartPointY + ResolveDirectionSign(scanSettings.ScanDirectionY) * Math.Max(0d, effectiveWaferHeight - cameraFieldOfView.Y);

            WaferScanArea scanArea = new WaferScanArea();
            scanArea.StartPoint = new Point(scanSettings.StartPointX, scanSettings.StartPointY);
            scanArea.EndPoint = new Point(endX, endY);
            scanArea.MinX = Math.Min(scanArea.StartPoint.X, scanArea.EndPoint.X);
            scanArea.MaxX = Math.Max(scanArea.StartPoint.X, scanArea.EndPoint.X);
            scanArea.MinY = Math.Min(scanArea.StartPoint.Y, scanArea.EndPoint.Y);
            scanArea.MaxY = Math.Max(scanArea.StartPoint.Y, scanArea.EndPoint.Y);
            scanArea.WaferWidth = effectiveWaferWidth;
            scanArea.WaferHeight = effectiveWaferHeight;
            return scanArea;
        }

        private static double CalculateStep(double fov, double overlapRate)
        {
            double normalizedOverlap = NormalizeOverlap(overlapRate);
            double step = fov * (1d - normalizedOverlap);
            if (step <= 0d)
            {
                return fov;
            }

            return step;
        }

        private static double NormalizeOverlap(double overlapRate)
        {
            if (overlapRate < 0d)
            {
                return 0d;
            }

            if (overlapRate >= 1d)
            {
                return 0.95d;
            }

            return overlapRate;
        }

        private static int CalculateScanCount(double waferSpan, double fov, double step)
        {
            if (waferSpan <= fov)
            {
                return 1;
            }

            return (int)Math.Ceiling((waferSpan - fov) / step) + 1;
        }

        private static List<Point> BuildRawPathPoints(WaferScanArea scanArea, WaferScanSettings scanSettings, double stepX, double stepY, int scanColumnCount, int scanRowCount)
        {
            List<Point> rawPathPoints = new List<Point>();
            int directionX = ResolveDirectionSign(scanSettings.ScanDirectionX);
            int directionY = ResolveDirectionSign(scanSettings.ScanDirectionY);

            for (int rowIndex = 0; rowIndex < scanRowCount; rowIndex++)
            {
                double y = scanArea.StartPoint.Y + rowIndex * stepY * directionY;
                bool reverseColumns = rowIndex % 2 == 1;
                for (int columnIndex = 0; columnIndex < scanColumnCount; columnIndex++)
                {
                    int serpentineColumnIndex = reverseColumns ? (scanColumnCount - 1 - columnIndex) : columnIndex;
                    double x = scanArea.StartPoint.X + serpentineColumnIndex * stepX * directionX;
                    rawPathPoints.Add(new Point(x, y));
                }
            }

            return rawPathPoints;
        }

        private List<Point> BuildAdjustedPathPoints(List<Point> rawPathPoints, WaferDiePositionContext context, WaferScanSettings scanSettings, List<WaferPlannerPointAdjustment> adjustments)
        {
            List<Point> adjustedPathPoints = new List<Point>();
            foreach (Point rawPathPoint in rawPathPoints)
            {
                Point adjustedPoint = _waferMachineAdapter.AdjustPointWithinSoftLimit(
                    rawPathPoint,
                    context,
                    scanSettings.SoftLimitOffsetMm,
                    out bool adjusted,
                    out string reason);

                WaferPlannerPointAdjustment adjustment = new WaferPlannerPointAdjustment();
                adjustment.OriginalPoint = rawPathPoint;
                adjustment.AdjustedPoint = adjustedPoint;
                adjustment.WasAdjusted = adjusted;
                adjustment.Reason = reason;
                adjustments.Add(adjustment);
                adjustedPathPoints.Add(adjustedPoint);
            }

            return adjustedPathPoints;
        }

        private static int ResolveDirectionSign(TransPathDirection direction)
        {
            return direction == TransPathDirection.Negative ? -1 : 1;
        }
    }
}
