using MainShell.Recipe.Models;
using MainShell.Recipe.Models.SubstrateParameter;
using MaxwellFramework.Core.Attributes;
using System;
using System.Linq;

namespace MainShell.Models
{
    [Singleton]
    public sealed class CurrentSubstrateTheoryCoordinateProvider
    {
        private readonly RecipeManager _recipeManager;

        public CurrentSubstrateTheoryCoordinateProvider(RecipeManager recipeManager)
        {
            _recipeManager = recipeManager ?? throw new ArgumentNullException(nameof(recipeManager));
        }

        public SubstratePoint GetCoordinate(int row, int column)
        {
            SubstratePoint substratePoint;
            if (!TryGetCoordinate(row, column, out substratePoint))
            {
                throw new InvalidOperationException(
                    $"Current substrate theory coordinate was not found for row {row}, column {column}.");
            }

            return substratePoint;
        }

        public SubstratePoint[,] GetCoordinateArray()
        {
            SubstratePoint[,] substratePoints;
            if (!TryGetCoordinateArray(out substratePoints))
            {
                throw new InvalidOperationException("Current substrate theory coordinate array was not found.");
            }

            return substratePoints;
        }

        public bool TryGetCoordinate(int row, int column, out SubstratePoint substratePoint)
        {
            substratePoint = default(SubstratePoint);
            if (row <= 0 || column <= 0)
            {
                return false;
            }

            SubstrateRecipe currentSubstrateRecipe = _recipeManager.CurrentSubstrateRecipe;
            if (currentSubstrateRecipe == null)
            {
                return false;
            }

            MarkCoordinatePoint theoryPoint = GetTheoryPoint(currentSubstrateRecipe, row, column);
            if (theoryPoint == null)
            {
                return false;
            }

            double offsetX;
            double offsetY;
            GetCoordinateOffsets(currentSubstrateRecipe, out offsetX, out offsetY);

            substratePoint = CreateSubstratePoint(theoryPoint, offsetX, offsetY);
            return true;
        }

        public bool TryGetCoordinateArray(out SubstratePoint[,] substratePoints)
        {
            substratePoints = null;

            SubstrateRecipe currentSubstrateRecipe = _recipeManager.CurrentSubstrateRecipe;
            if (currentSubstrateRecipe == null)
            {
                return false;
            }

            MarkCoordinateGenerationState coordinateGenerationState = GetCoordinateGenerationState(currentSubstrateRecipe);
            if (coordinateGenerationState == null ||
                coordinateGenerationState.Rows <= 0 ||
                coordinateGenerationState.Cols <= 0)
            {
                return false;
            }

            double offsetX;
            double offsetY;
            GetCoordinateOffsets(currentSubstrateRecipe, out offsetX, out offsetY);

            SubstratePoint[,] currentSubstratePoints = new SubstratePoint[coordinateGenerationState.Rows, coordinateGenerationState.Cols];
            for (int row = 1; row <= coordinateGenerationState.Rows; row++)
            {
                for (int column = 1; column <= coordinateGenerationState.Cols; column++)
                {
                    MarkCoordinatePoint theoryPoint = GetTheoryPoint(currentSubstrateRecipe, row, column);
                    if (theoryPoint == null)
                    {
                        return false;
                    }

                    currentSubstratePoints[row - 1, column - 1] = CreateSubstratePoint(theoryPoint, offsetX, offsetY);
                }
            }

            substratePoints = currentSubstratePoints;
            return true;
        }

        private static MarkCoordinatePoint GetTheoryPoint(SubstrateRecipe currentSubstrateRecipe, int row, int column)
        {
            MarkCoordinateGenerationState coordinateGenerationState = GetCoordinateGenerationState(currentSubstrateRecipe);
            if (coordinateGenerationState == null || coordinateGenerationState.Points == null)
            {
                return null;
            }

            return coordinateGenerationState.Points
                .FirstOrDefault(point => point != null && point.Row == row && point.Col == column);
        }

        private static MarkCoordinateGenerationState GetCoordinateGenerationState(SubstrateRecipe currentSubstrateRecipe)
        {
            if (currentSubstrateRecipe == null || currentSubstrateRecipe.SubtrateMarkParameterInfo == null)
            {
                return null;
            }

            return currentSubstrateRecipe.SubtrateMarkParameterInfo.CoordinateGenerationState;
        }

        private static SubstratePoint CreateSubstratePoint(MarkCoordinatePoint theoryPoint, double offsetX, double offsetY)
        {
            return new SubstratePoint(
                theoryPoint.TheoryX + offsetX,
                theoryPoint.TheoryY + offsetY);
        }

        private void GetCoordinateOffsets(SubstrateRecipe currentSubstrateRecipe, out double offsetX, out double offsetY)
        {
            WaferSelectInfo currentWaferSelectInfo = GetCurrentWaferSelectInfo(currentSubstrateRecipe);
            offsetX = currentWaferSelectInfo != null ? currentWaferSelectInfo.OffsetX : 0d;
            offsetY = currentWaferSelectInfo != null ? currentWaferSelectInfo.OffsetY : 0d;
        }

        private WaferSelectInfo GetCurrentWaferSelectInfo(SubstrateRecipe currentSubstrateRecipe)
        {
            if (currentSubstrateRecipe.WaferSelectInfos == null || currentSubstrateRecipe.WaferSelectInfos.Count == 0)
            {
                return null;
            }

            string currentWaferRecipeName = _recipeManager.CurrentWaferRecipe != null
                ? _recipeManager.CurrentWaferRecipe.RecipeName
                : null;

            if (!string.IsNullOrWhiteSpace(currentWaferRecipeName))
            {
                WaferSelectInfo matchedWaferSelectInfo = currentSubstrateRecipe.WaferSelectInfos
                    .FirstOrDefault(info =>
                        info != null &&
                        string.Equals(info.WaferRecipeName, currentWaferRecipeName, StringComparison.Ordinal));
                if (matchedWaferSelectInfo != null)
                {
                    return matchedWaferSelectInfo;
                }
            }

            WaferSelectInfo inUseWaferSelectInfo = currentSubstrateRecipe.WaferSelectInfos
                .FirstOrDefault(info => info != null && info.IsUse);
            if (inUseWaferSelectInfo != null)
            {
                return inUseWaferSelectInfo;
            }

            return currentSubstrateRecipe.WaferSelectInfos
                .FirstOrDefault(info => info != null && !string.IsNullOrWhiteSpace(info.WaferRecipeName));
        }
    }
}
