using MainShell.Common;
using MainShell.HeightMeasure.Model;
using MainShell.HeightMeasure.Service;
using MainShell.Log;
using MainShell.Models;
using MainShell.ProcessResult;
using MainShell.Recipe.Models;
using MainShell.Recipe.Models.SubstrateParameter;
using MW.WorkFlow;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace MainShell.Process
{
    public class SubstrateHeightMeasureService
    {
        private readonly IHeightMeasure _heightMeasure;
        private readonly HeightMeasureMotionService _heightMeasureMotionService;

        public SubstrateHeightMeasureService(IHeightMeasure heightMeasure, HeightMeasureMotionService heightMeasureMotionService)
        {
            _heightMeasure = heightMeasure ?? throw new ArgumentNullException(nameof(heightMeasure));
            _heightMeasureMotionService = heightMeasureMotionService ?? throw new ArgumentNullException(nameof(heightMeasureMotionService));
        }

        public async Task ExecuteAsync(WorkflowContext context, ActivityControl activityControl)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (activityControl == null)
            {
                throw new ArgumentNullException(nameof(activityControl));
            }

            RecipeManager recipeManager = context.GetData<RecipeManager>(WorkflowContextKeys.RecipeManager);
            ProcessResultManager processResultManager = context.GetData<ProcessResultManager>(WorkflowContextKeys.ProcessResultManager);
            SubstrateRecipe substrateRecipe = GetCurrentRecipe(recipeManager);
            IList<SubstrateHeightMeasurePoint> points = GetAvailablePoints(substrateRecipe);
            SubstrateHeightMeasureProcessResult processResult = InitializeProcessResult(processResultManager, substrateRecipe.RecipeName);

            try
            {
                LogManager.LogProcessInfo($"SubstrateHeightMeasure: start. Recipe={substrateRecipe.RecipeName}, PointCount={points.Count}");

                for (int index = 0; index < points.Count; index++)
                {
                    SubstrateHeightMeasurePoint point = points[index];
                    string pointName = GetPointName(point, index);

                    await CheckCancellationAndPauseAsync(activityControl).ConfigureAwait(false);

                    double positionX;
                    double positionY;
                    ResolveMeasurePosition(substrateRecipe, point, pointName, out positionX, out positionY);

                    LogManager.LogProcessDebug($"SubstrateHeightMeasure: moving to point[{index}] '{pointName}', X={positionX:F4}, Y={positionY:F4}");
                    _heightMeasureMotionService.MoveWaferMeasurePose(positionX, positionY);

                    double heightValue = _heightMeasure.GetDistance();
                    processResult.PointResults.Add(new SubstrateHeightMeasurePointResult
                    {
                        PointName = pointName,
                        RowIndex = point.RowIndex,
                        ColumnIndex = point.ColumnIndex,
                        PositionX = positionX,
                        PositionY = positionY,
                        HeightValue = heightValue
                    });

                    LogManager.LogProcessInfo($"SubstrateHeightMeasure: point[{index}] '{pointName}' measured height={heightValue:F4}");
                }

                processResult.IsSuccess = true;
                processResult.ErrorMessage = null;
                processResult.ErrorMessageKey = MessageKey.None;
                processResult.ErrorMessageArguments = Array.Empty<string>();
                LogManager.LogProcessInfo($"SubstrateHeightMeasure: completed. Recipe={substrateRecipe.RecipeName}, PointCount={processResult.PointResults.Count}");
            }
            catch (OperationCanceledException)
            {
                processResult.IsSuccess = false;
                processResult.ErrorMessage = null;
                processResult.ErrorMessageKey = MessageKey.None;
                processResult.ErrorMessageArguments = Array.Empty<string>();
                LogManager.LogProcessInfo("SubstrateHeightMeasure: operation canceled.");
                throw;
            }
            catch (LocalizedProcessException ex)
            {
                processResult.IsSuccess = false;
                processResult.ErrorMessageKey = ex.FailureMessageKey;
                processResult.ErrorMessageArguments = ex.FailureMessageArguments;
                processResult.ErrorMessage = ex.Message;
                LogManager.LogProcessError($"SubstrateHeightMeasure: {ex.Message}");
            }
            catch (Exception ex)
            {
                processResult.IsSuccess = false;
                processResult.ErrorMessageKey = MessageKey.ProcessSubstrateHeightMeasureFailedWithReason;
                processResult.ErrorMessageArguments = new string[] { ex.Message ?? string.Empty };
                processResult.ErrorMessage = LanguageResourceHelper.Format(
                    processResult.ErrorMessageKey,
                    LocalizedProcessException.ConvertArguments(processResult.ErrorMessageArguments));
                LogManager.LogProcessError($"SubstrateHeightMeasure: {processResult.ErrorMessage}");
            }
            finally
            {
                WriteResultToContext(context, processResultManager, processResult);
            }
        }

        private static SubstrateRecipe GetCurrentRecipe(RecipeManager recipeManager)
        {
            if (recipeManager == null)
            {
                throw new ArgumentNullException(nameof(recipeManager));
            }

            if (recipeManager.CurrentSubstrateRecipe == null)
            {
                throw new LocalizedProcessException(
                    MessageKey.ProcessSubstrateHeightMeasureRecipeNotLoaded,
                    Array.Empty<string>());
            }

            return recipeManager.CurrentSubstrateRecipe;
        }

        private static IList<SubstrateHeightMeasurePoint> GetAvailablePoints(SubstrateRecipe substrateRecipe)
        {
            if (substrateRecipe == null)
            {
                throw new ArgumentNullException(nameof(substrateRecipe));
            }

            if (substrateRecipe.HeightMeasureSetting == null || substrateRecipe.HeightMeasureSetting.Points == null)
            {
                throw new LocalizedProcessException(
                    MessageKey.ProcessSubstrateHeightMeasureSettingMissing,
                    Array.Empty<string>());
            }

            List<SubstrateHeightMeasurePoint> points = new List<SubstrateHeightMeasurePoint>();
            foreach (SubstrateHeightMeasurePoint point in substrateRecipe.HeightMeasureSetting.Points)
            {
                if (point != null)
                {
                    points.Add(point);
                }
            }

            if (points.Count == 0)
            {
                throw new LocalizedProcessException(
                    MessageKey.ProcessSubstrateHeightMeasureNoPoints,
                    Array.Empty<string>());
            }

            return points;
        }

        private static SubstrateHeightMeasureProcessResult InitializeProcessResult(ProcessResultManager processResultManager, string recipeName)
        {
            if (processResultManager == null)
            {
                throw new ArgumentNullException(nameof(processResultManager));
            }

            SubstrateHeightMeasureProcessResult processResult = processResultManager.SubstrateHeightMeasureResult;
            processResult.IsSuccess = false;
            processResult.RecipeName = recipeName;
            processResult.ErrorMessage = null;
            processResult.ErrorMessageKey = MessageKey.None;
            processResult.ErrorMessageArguments = Array.Empty<string>();
            processResult.PointResults.Clear();
            return processResult;
        }

        private static async Task CheckCancellationAndPauseAsync(ActivityControl activityControl)
        {
            activityControl.ThrowIfCancellationRequested();
            await activityControl.CheckPauseAsync().ConfigureAwait(false);
        }

        private static void ResolveMeasurePosition(SubstrateRecipe substrateRecipe, SubstrateHeightMeasurePoint point, string pointName, out double positionX, out double positionY)
        {
            if (substrateRecipe == null)
            {
                throw new ArgumentNullException(nameof(substrateRecipe));
            }

            if (point == null)
            {
                throw new ArgumentNullException(nameof(point));
            }

            if (substrateRecipe.HeightMeasureSetting != null && substrateRecipe.HeightMeasureSetting.Mode == SubstrateHeightMeasureMode.RowColumnOffset)
            {
                if (substrateRecipe.SubstrateInfo == null || substrateRecipe.SubstrateInfo.PitchX <= 0 || substrateRecipe.SubstrateInfo.PitchY <= 0)
                {
                    throw new LocalizedProcessException(
                        MessageKey.ProcessSubstrateHeightMeasurePointPositionInvalid,
                        pointName);
                }

                if (point.RowIndex <= 0 || point.ColumnIndex <= 0)
                {
                    throw new LocalizedProcessException(
                        MessageKey.ProcessSubstrateHeightMeasurePointPositionInvalid,
                        pointName);
                }

                MPoint commonOffset = substrateRecipe.HeightMeasureSetting.CommonOffsetCompensation ?? new MPoint();
                MPoint pointOffset = point.OffsetCompensation ?? new MPoint();
                positionX = (point.ColumnIndex - 1) * substrateRecipe.SubstrateInfo.PitchX + commonOffset.X + pointOffset.X;
                positionY = (point.RowIndex - 1) * substrateRecipe.SubstrateInfo.PitchY + commonOffset.Y + pointOffset.Y;
                return;
            }

            if (point.TeachPosition == null)
            {
                throw new LocalizedProcessException(
                    MessageKey.ProcessSubstrateHeightMeasurePointPositionInvalid,
                    pointName);
            }

            positionX = point.TeachPosition.X;
            positionY = point.TeachPosition.Y;
        }

        private static string GetPointName(SubstrateHeightMeasurePoint point, int index)
        {
            if (point == null || string.IsNullOrWhiteSpace(point.PointName))
            {
                return $"Point{index + 1}";
            }

            return point.PointName;
        }

        private static void WriteResultToContext(
            WorkflowContext context,
            ProcessResultManager processResultManager,
            SubstrateHeightMeasureProcessResult processResult)
        {
            context.SetData(WorkflowContextKeys.SubstrateHeightMeasureResult, processResult);
            processResultManager.SaveSubstrateHeightMeasureResult();
        }
    }
}
