using MainShell.Common;
using MainShell.Log;
using MainShell.Models;
using MaxwellFramework.Core.Attributes;
using SemiconductorVisionAlgorithm.SemiParams;
using SemiconductorVisionAlgorithm.SemiWaferRecip;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace MainShell.Vision
{
    /// <summary>
    /// ?????????????
    /// </summary>
    [Singleton]
    public class FindTemplateService : IFindTemplateService
    {
        private readonly IImageCaptureService _imageCaptureService;

        public FindTemplateService(IImageCaptureService imageCaptureService)
        {
            _imageCaptureService = imageCaptureService ?? throw new ArgumentNullException(nameof(imageCaptureService));
        }

        public async Task<VisionProcessResult<FindTemplateResult>> ProcessAsync(
            FindTemplateRequest request,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }

            FindTemplateImageRequest imageRequest = CreateImageRequest(request);
            return await ExecuteWithCaptureAsync(
                request.CameraSource,
                request.CaptureOptions,
                imageRequest,
                ProcessImageAsync,
                cancellationToken).ConfigureAwait(false);
        }

        public Task<VisionProcessResult<FindTemplateResult>> ProcessImageAsync(
            MxImage image,
            FindTemplateImageRequest request,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            if (image == null)
            {
                throw new ArgumentNullException(nameof(image));
            }

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

            FindTemplateImageRequest normalizedRequest = NormalizeRequest(request);
            VisionProcessResult validationResult = ValidateRequest(normalizedRequest);
            if (validationResult != null)
            {
                return Task.FromResult(CreateFailureResult<FindTemplateResult>(validationResult));
            }

            try
            {
                cancellationToken.ThrowIfCancellationRequested();
                VisionProcessResult<FindTemplateResult> result = ExecuteSingleMatch(image, normalizedRequest);
                if (!result.Succeeded)
                {
                    result.Message.LogSysError();
                }

                return Task.FromResult(result);
            }
            catch (OperationCanceledException ex)
            {
                string message = $"FindTemplateService: process image was cancelled. Timeout={normalizedRequest.TimeoutMilliseconds} ms.";
                message.LogInfo();
                return Task.FromResult(VisionProcessResult<FindTemplateResult>.Failure(
                    VisionFailureCategory.Cancelled,
                    VisionErrorCode.OperationCancelled,
                    null,
                    MessageKey.VisionOperationCancelled,
                    message,
                    ex));
            }
            catch (Exception ex)
            {
                string message = $"FindTemplateService: process image failed. {ex.Message}";
                message.LogSysError();
                return Task.FromResult(VisionProcessResult<FindTemplateResult>.Failure(
                    VisionFailureCategory.Algorithm,
                    VisionErrorCode.TemplateMatchFailed,
                    VisionAlarmIds.TemplateMatchFailed,
                    MessageKey.VisionTemplateMatchFailed,
                    message,
                    ex));
            }
        }

        public async Task<VisionProcessResult<FindTemplatesResult>> ProcessMultipleAsync(
            FindTemplateRequest request,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }

            FindTemplateImageRequest imageRequest = CreateImageRequest(request);
            return await ExecuteWithCaptureAsync(
                request.CameraSource,
                request.CaptureOptions,
                imageRequest,
                ProcessMultipleImageAsync,
                cancellationToken).ConfigureAwait(false);
        }

        public Task<VisionProcessResult<FindTemplatesResult>> ProcessMultipleImageAsync(
            MxImage image,
            FindTemplateImageRequest request,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            if (image == null)
            {
                throw new ArgumentNullException(nameof(image));
            }

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

            FindTemplateImageRequest normalizedRequest = NormalizeRequest(request);
            VisionProcessResult validationResult = ValidateRequest(normalizedRequest);
            if (validationResult != null)
            {
                return Task.FromResult(CreateFailureResult<FindTemplatesResult>(validationResult));
            }

            try
            {
                cancellationToken.ThrowIfCancellationRequested();

                VisionProcessResult<FindTemplatesResult> result = ExecuteMultipleMatch(image, normalizedRequest);
                if (!result.Succeeded)
                {
                    result.Message.LogSysError();
                }

                return Task.FromResult(result);
            }
            catch (OperationCanceledException ex)
            {
                string message = $"FindTemplateService: multiple template process image was cancelled. Timeout={normalizedRequest.TimeoutMilliseconds} ms.";
                message.LogInfo();
                return Task.FromResult(VisionProcessResult<FindTemplatesResult>.Failure(
                    VisionFailureCategory.Cancelled,
                    VisionErrorCode.OperationCancelled,
                    null,
                    MessageKey.VisionOperationCancelled,
                    message,
                    ex));
            }
            catch (Exception ex)
            {
                string message = $"FindTemplateService: multiple template process image failed. {ex.Message}";
                message.LogSysError();
                return Task.FromResult(VisionProcessResult<FindTemplatesResult>.Failure(
                    VisionFailureCategory.Algorithm,
                    VisionErrorCode.TemplateMatchFailed,
                    VisionAlarmIds.TemplateMatchFailed,
                    MessageKey.VisionTemplateMatchFailed,
                    message,
                    ex));
            }
        }

        private static FindTemplateImageRequest NormalizeRequest(FindTemplateImageRequest request)
        {
            FindTemplateImageRequest normalizedRequest = new FindTemplateImageRequest();
            normalizedRequest.TimeoutMilliseconds = request.TimeoutMilliseconds > 0 ? request.TimeoutMilliseconds : 3000;
            normalizedRequest.Parameters = CloneParameters(request.Parameters);
            return normalizedRequest;
        }

        private static VisionProcessResult ValidateRequest(FindTemplateImageRequest request)
        {
            if (request.Parameters == null)
            {
                return VisionProcessResult.Failure(
                    VisionFailureCategory.Validation,
                    VisionErrorCode.RequestInvalid,
                    VisionAlarmIds.RequestInvalid,
                    MessageKey.VisionRequestInvalid,
                    "FindTemplateService: parameters cannot be null.");
            }

            if (request.TimeoutMilliseconds <= 0 || request.TimeoutMilliseconds > 600000)
            {
                return VisionProcessResult.Failure(
                    VisionFailureCategory.Validation,
                    VisionErrorCode.TimeoutInvalid,
                    VisionAlarmIds.RequestInvalid,
                    MessageKey.VisionTimeoutInvalid,
                    string.Format("FindTemplateService: TimeoutMilliseconds {0} is invalid. Expected range is [1, 60000].", request.TimeoutMilliseconds));
            }

            if (request.Parameters.MinScore <= 0d || request.Parameters.MinScore > 1d)
            {
                return VisionProcessResult.Failure(
                    VisionFailureCategory.Validation,
                    VisionErrorCode.MinScoreInvalid,
                    VisionAlarmIds.TemplateMinScoreInvalid,
                    MessageKey.VisionTemplateMinScoreInvalid,
                    string.Format("FindTemplateService: MinScore {0} is invalid. Expected range is (0, 1].", request.Parameters.MinScore));
            }

            if (string.IsNullOrWhiteSpace(request.Parameters.TemplatePath))
            {
                return VisionProcessResult.Failure(
                    VisionFailureCategory.Validation,
                    VisionErrorCode.TemplatePathEmpty,
                    VisionAlarmIds.TemplatePathEmpty,
                    MessageKey.VisionTemplatePathEmpty,
                    "FindTemplateService: TemplatePath cannot be empty.");
            }

            if (request.Parameters.UseRoi && string.IsNullOrWhiteSpace(request.Parameters.RoiName))
            {
                return VisionProcessResult.Failure(
                    VisionFailureCategory.Validation,
                    VisionErrorCode.RoiNameEmpty,
                    VisionAlarmIds.TemplateRoiInvalid,
                    MessageKey.VisionTemplateRoiInvalid,
                    "FindTemplateService: RoiName cannot be empty when UseRoi is true.");
            }

            return null;
        }

        private static FindTemplateParameters CloneParameters(FindTemplateParameters parameters)
        {
            if (parameters == null)
            {
                return new FindTemplateParameters();
            }

            FindTemplateParameters clone = new FindTemplateParameters();
            clone.UseRoi = parameters.UseRoi;
            clone.RoiName = parameters.RoiName;
            clone.MinScore = parameters.MinScore;
            clone.TemplatePath = parameters.TemplatePath;
            return clone;
        }

        private static FindTemplatesResult CreateMultipleResult(IList<FindTemplateResult> matches)
        {
            FindTemplatesResult result = new FindTemplatesResult();
            if (matches == null)
            {
                return result;
            }

            foreach (FindTemplateResult match in matches)
            {
                result.Items.Add(match);
            }

            return result;
        }

        private static FindTemplateImageRequest CreateImageRequest(FindTemplateRequest request)
        {
            FindTemplateImageRequest imageRequest = new FindTemplateImageRequest();
            imageRequest.TimeoutMilliseconds = request.TimeoutMilliseconds;
            imageRequest.Parameters = CloneParameters(request.Parameters);
            return imageRequest;
        }

        private static VisionProcessResult<FindTemplateResult> ExecuteSingleMatch(
            MxImage image,
            FindTemplateImageRequest request)
        {
            Camera camera = image.Image;
            if (camera == null)
            {
                return VisionProcessResult<FindTemplateResult>.Failure(
                    VisionFailureCategory.Algorithm,
                    VisionErrorCode.ImageNull,
                    VisionAlarmIds.ImageNull,
                    MessageKey.VisionImageIsNull,
                    "FindTemplateService: input image does not contain a valid camera object.");
            }

            Template template = null;
            double score = 0d;
            Rectangle1 contour = null;
            List<Circle> circles = null;
            List<LineSeg> lines = null;
            List<Rectangle2> rectangles = null;

            int matchCode = WaferRecips.Instance.match_image(
                camera,
                request.Parameters.TemplatePath,
                out template,
                out score,
                out contour,
                out circles,
                out lines,
                out rectangles);

            if (matchCode != 0)
            {
                return VisionProcessResult<FindTemplateResult>.Failure(
                    VisionFailureCategory.Algorithm,
                    VisionErrorCode.TemplateMatchFailed,
                    VisionAlarmIds.TemplateMatchFailed,
                    MessageKey.VisionTemplateMatchFailed,
                    $"FindTemplateService: single template match failed. Code={matchCode}, TemplatePath={request.Parameters.TemplatePath}.");
            }

            if (template == null || double.IsNaN(score) || double.IsInfinity(score))
            {
                return VisionProcessResult<FindTemplateResult>.Failure(
                    VisionFailureCategory.Algorithm,
                    VisionErrorCode.TemplateResultInvalid,
                    VisionAlarmIds.TemplateResultInvalid,
                    MessageKey.VisionTemplateMatchFailed,
                    $"FindTemplateService: single template match returned an invalid result. TemplatePath={request.Parameters.TemplatePath}, Score={score}.");
            }

            if (score < request.Parameters.MinScore)
            {
                return VisionProcessResult<FindTemplateResult>.Failure(
                    VisionFailureCategory.Algorithm,
                    VisionErrorCode.TemplateMatchFailed,
                    VisionAlarmIds.TemplateMatchFailed,
                    MessageKey.VisionTemplateMatchFailed,
                    $"FindTemplateService: single template match score {score} is lower than required {request.Parameters.MinScore}. TemplatePath={request.Parameters.TemplatePath}.");
            }

            FindTemplateResult result = CreateSingleResult(template, score, contour, circles, lines, rectangles);
            return VisionProcessResult<FindTemplateResult>.Success(result);
        }

        private static VisionProcessResult<FindTemplatesResult> ExecuteMultipleMatch(
            MxImage image,
            FindTemplateImageRequest request)
        {
            Camera camera = image.Image;
            if (camera == null)
            {
                return VisionProcessResult<FindTemplatesResult>.Failure(
                    VisionFailureCategory.Algorithm,
                    VisionErrorCode.ImageNull,
                    VisionAlarmIds.ImageNull,
                    MessageKey.VisionImageIsNull,
                    "FindTemplateService: input image does not contain a valid camera object.");
            }

            double[] rows = null;
            double[] cols = null;
            double[] angles = null;
            double[] scores = null;
            List<Rectangle1> contours = null;

            bool succeeded = WaferRecips.Instance.match_multi_0bject(
                camera,
                request.Parameters.TemplatePath,
                out rows,
                out cols,
                out angles,
                out scores,
                out contours);

            if (!succeeded)
            {
                return VisionProcessResult<FindTemplatesResult>.Failure(
                    VisionFailureCategory.Algorithm,
                    VisionErrorCode.TemplateMatchFailed,
                    VisionAlarmIds.TemplateMatchFailed,
                    MessageKey.VisionTemplateMatchFailed,
                    $"FindTemplateService: multiple template match failed. TemplatePath={request.Parameters.TemplatePath}.");
            }

            if (!HasConsistentMultipleResult(rows, cols, angles, scores, contours))
            {
                return VisionProcessResult<FindTemplatesResult>.Failure(
                    VisionFailureCategory.Algorithm,
                    VisionErrorCode.TemplateResultInvalid,
                    VisionAlarmIds.TemplateResultInvalid,
                    MessageKey.VisionTemplateMatchFailed,
                    $"FindTemplateService: multiple template match returned inconsistent result data. TemplatePath={request.Parameters.TemplatePath}.");
            }

            FindTemplatesResult result = CreateMultipleResult(rows, cols, angles, scores, contours);
            return VisionProcessResult<FindTemplatesResult>.Success(result);
        }

        private static FindTemplateResult CreateSingleResult(
            Template template,
            double score,
            Rectangle1 contour,
            IList<Circle> circles,
            IList<LineSeg> lines,
            IList<Rectangle2> rectangles)
        {
            FindTemplateResult result = new FindTemplateResult();
            result.CenterX = template.X;
            result.CenterY = template.Y;
            result.Score = score;
            result.Match = CreateMatchResult(template.Phi, score, contour, circles, lines, rectangles);
            result.Contour = CreateContourResult(contour);
            AppendCircleResults(result.Circles, circles);
            AppendLineResults(result.Lines, lines);
            AppendRectangleResults(result.Rectangles, rectangles);
            return result;
        }

        private static FindTemplatesResult CreateMultipleResult(
            double[] rows,
            double[] cols,
            double[] angles,
            double[] scores,
            IList<Rectangle1> contours)
        {
            FindTemplatesResult result = new FindTemplatesResult();

            for (int i = 0; i < rows.Length; i++)
            {
                FindTemplateResult item = new FindTemplateResult();
                item.CenterX = cols[i];
                item.CenterY = rows[i];
                item.Score = scores[i];
                item.Match = CreateMatchResult(angles[i], scores[i], contours[i], null, null, null);
                item.Contour = CreateContourResult(contours[i]);
                result.Items.Add(item);
            }

            return result;
        }

        private static VisionMatchResult CreateMatchResult(
            double angle,
            double score,
            Rectangle1 contour,
            IList<Circle> circles,
            IList<LineSeg> lines,
            IList<Rectangle2> rectangles)
        {
            VisionMatchResult match = new VisionMatchResult();
            match.Angle = angle;
            match.Score = score;
            match.Tag = CreateMatchTag(contour, circles, lines, rectangles);
            return match;
        }

        private static FindTemplateContourResult CreateContourResult(Rectangle1 contour)
        {
            if (contour == null)
            {
                return null;
            }

            FindTemplateContourResult result = new FindTemplateContourResult();
            result.StartX = contour.Start_X;
            result.StartY = contour.Start_Y;
            result.EndX = contour.End_X;
            result.EndY = contour.End_Y;
            return result;
        }

        private static void AppendCircleResults(IList<FindTemplateCircleResult> destination, IList<Circle> circles)
        {
            if (destination == null || circles == null)
            {
                return;
            }

            foreach (Circle circle in circles)
            {
                if (circle == null)
                {
                    continue;
                }

                FindTemplateCircleResult item = new FindTemplateCircleResult();
                item.CenterX = circle.X;
                item.CenterY = circle.Y;
                item.Radius = circle.R;
                destination.Add(item);
            }
        }

        private static void AppendLineResults(IList<FindTemplateLineResult> destination, IList<LineSeg> lines)
        {
            if (destination == null || lines == null)
            {
                return;
            }

            foreach (LineSeg line in lines)
            {
                if (line == null)
                {
                    continue;
                }

                FindTemplateLineResult item = new FindTemplateLineResult();
                item.StartX = line.Start_X;
                item.StartY = line.Start_Y;
                item.EndX = line.End_X;
                item.EndY = line.End_Y;
                destination.Add(item);
            }
        }

        private static void AppendRectangleResults(IList<FindTemplateRectangleResult> destination, IList<Rectangle2> rectangles)
        {
            if (destination == null || rectangles == null)
            {
                return;
            }

            foreach (Rectangle2 rectangle in rectangles)
            {
                if (rectangle == null)
                {
                    continue;
                }

                FindTemplateRectangleResult item = new FindTemplateRectangleResult();
                item.StartX = rectangle.XStart;
                item.StartY = rectangle.YStart;
                item.Width = rectangle.Width;
                item.Height = rectangle.Height;
                item.Angle = rectangle.Phi;
                destination.Add(item);
            }
        }

        private static string CreateMatchTag(
            Rectangle1 contour,
            IList<Circle> circles,
            IList<LineSeg> lines,
            IList<Rectangle2> rectangles)
        {
            if (contour == null && circles == null && lines == null && rectangles == null)
            {
                return null;
            }

            int circleCount = circles != null ? circles.Count : 0;
            int lineCount = lines != null ? lines.Count : 0;
            int rectangleCount = rectangles != null ? rectangles.Count : 0;

            if (contour == null)
            {
                return $"Circles={circleCount};Lines={lineCount};Rects={rectangleCount}";
            }

            return $"Contour=({contour.Start_X},{contour.Start_Y})-({contour.End_X},{contour.End_Y});Circles={circleCount};Lines={lineCount};Rects={rectangleCount}";
        }

        private static bool HasConsistentMultipleResult(
            double[] rows,
            double[] cols,
            double[] angles,
            double[] scores,
            IList<Rectangle1> contours)
        {
            if (rows == null || cols == null || angles == null || scores == null || contours == null)
            {
                return false;
            }

            if (rows.Length == 0)
            {
                return false;
            }

            if (rows.Length != cols.Length || rows.Length != angles.Length || rows.Length != scores.Length)
            {
                return false;
            }

            if (rows.Length != contours.Count)
            {
                return false;
            }

            for (int i = 0; i < rows.Length; i++)
            {
                if (double.IsNaN(rows[i]) || double.IsInfinity(rows[i]) ||
                    double.IsNaN(cols[i]) || double.IsInfinity(cols[i]) ||
                    double.IsNaN(angles[i]) || double.IsInfinity(angles[i]) ||
                    double.IsNaN(scores[i]) || double.IsInfinity(scores[i]))
                {
                    return false;
                }
            }

            return true;
        }

        private async Task<VisionProcessResult<TResult>> ExecuteWithCaptureAsync<TResult>(
            MainShell.Common.CameraType cameraSource,
            CameraCaptureOptions captureOptions,
            FindTemplateImageRequest imageRequest,
            Func<MxImage, FindTemplateImageRequest, CancellationToken, Task<VisionProcessResult<TResult>>> imageProcessor,
            CancellationToken cancellationToken)
        {
            ImageCaptureResult captureResult = null;
            MxImage image = null;

            try
            {
                captureResult = await _imageCaptureService.CaptureAsync(
                    cameraSource,
                    captureOptions,
                    cancellationToken).ConfigureAwait(false);

                if (captureResult == null)
                {
                    return VisionProcessResult<TResult>.Failure(
                        VisionFailureCategory.Capture,
                        VisionErrorCode.NoFrame,
                        VisionAlarmIds.NoFrame,
                        MessageKey.VisionNoFrameReturned,
                        string.Format(
                            "FindTemplateService: image capture returned null result for camera '{0}'.",
                            cameraSource));
                }

                if (!captureResult.Succeeded)
                {
                    return CreateFailureResult<TResult>(VisionResultMapper.CreateCaptureFailure(captureResult));
                }

                image = captureResult.Image;
                if (image == null)
                {
                    string message = string.Format(
                        "FindTemplateService: image capture succeeded but returned null image for camera '{0}'.",
                        cameraSource);
                    message.LogSysError();
                    return VisionProcessResult<TResult>.Failure(
                        VisionFailureCategory.Capture,
                        VisionErrorCode.ImageNull,
                        VisionAlarmIds.ImageNull,
                        MessageKey.VisionImageIsNull,
                        message);
                }

                return await imageProcessor(image, imageRequest, cancellationToken).ConfigureAwait(false);
            }
            catch (OperationCanceledException ex)
            {
                string message = $"FindTemplateService: capture orchestration was cancelled for camera '{cameraSource}'.";
                message.LogInfo();
                return VisionProcessResult<TResult>.Failure(
                    VisionFailureCategory.Cancelled,
                    VisionErrorCode.OperationCancelled,
                    null,
                    MessageKey.VisionOperationCancelled,
                    message,
                    ex);
            }
            catch (Exception ex)
            {
                string message = $"FindTemplateService: capture orchestration failed for camera '{cameraSource}'. {ex.Message}";
                message.LogSysError();
                return VisionProcessResult<TResult>.Failure(
                    VisionFailureCategory.Algorithm,
                    VisionErrorCode.TemplateMatchFailed,
                    VisionAlarmIds.TemplateMatchFailed,
                    MessageKey.VisionTemplateMatchFailed,
                    message,
                    ex);
            }
            finally
            {
                if (image != null)
                {
                    image.Dispose();
                }
            }
        }

        private static VisionProcessResult<TData> CreateFailureResult<TData>(VisionProcessResult result)
        {
            return VisionProcessResult<TData>.Failure(
                result.FailureCategory,
                result.ErrorCode,
                result.AlarmId,
                result.UserMessageKey,
                result.Message,
                result.Exception);
        }
    }
}
