using System;
using OpenCvSharp;

namespace OpenCVStudio_v0
{
    /// <summary>
    /// Вычисляет преобразование между двумя изображениями
    /// методом ECC.
    /// </summary>
    internal sealed class FindTransformECCExecutor : IPipelineBlockExecutor
    {
        public string BlockType => "FindTransformECC";

        public void Execute(PipelineContext context, ProjectBlock block, ProjectManager projectManager)
        {
            ArgumentNullException.ThrowIfNull(context);
            ArgumentNullException.ThrowIfNull(block);
            ArgumentNullException.ThrowIfNull(projectManager);
            if (!context.HasImage || !context.HasSecondImage)
            {
                throw new InvalidOperationException($"Блок «{block.Name}» требует два изображения.");
            }
            using Mat template = ConvertToGray(context.Image);
            using Mat input = ConvertToGray(context.SecondImage);
            MotionTypes motionType = projectManager.GetBlockParameter(block.Id, "MotionType", MotionTypes.Affine);
            int iterations = projectManager.GetBlockParameter(block.Id, "Iterations", 500);
            double epsilon = projectManager.GetBlockParameter(block.Id, "Epsilon", 0.000001);
            Mat warpMatrix = CreateWarpMatrix(motionType);
            TermCriteria criteria = new TermCriteria(CriteriaTypes.Eps | CriteriaTypes.Count, iterations, epsilon);
            double score = Cv2.FindTransformECC(template, input, warpMatrix, motionType, criteria);
            ExtractTransform(warpMatrix, out double shiftX, out double shiftY, out double angle, out double scale);
            context.TransformMatrix = new TransformMatrixData(warpMatrix.Clone(), score, shiftX, shiftY, angle, scale);
            warpMatrix.Dispose();
            context.ResultText = $"ECC={score:F4}; X={shiftX:F2}; Y={shiftY:F2}; Angle={angle:F2}; Scale={scale:F4}";
        }

        private static Mat CreateWarpMatrix(MotionTypes motionType)
        {
            return motionType switch
            {
                MotionTypes.Homography => Mat.Eye(3, 3, MatType.CV_32F),
                _ => Mat.Eye(2, 3, MatType.CV_32F)
            };
        }

        private static Mat ConvertToGray(Mat source)
        {
            Mat gray = new Mat();
            if (source.Channels() == 1)
            {
                source.CopyTo(gray);
            }
            else
            {
                Cv2.CvtColor(source, gray, ColorConversionCodes.BGR2GRAY);
            }
            return gray;
        }

        private static void ExtractTransform(Mat matrix, out double shiftX, out double shiftY, out double angle, out double scale)
        {
            shiftX = matrix.At<float>(0, 2);
            shiftY = matrix.At<float>(1, 2);
            double a = matrix.At<float>(0, 0);
            double b = matrix.At<float>(1, 0);
            scale = Math.Sqrt(a * a + b * b);
            angle = Math.Atan2(b, a) * 180.0 / Math.PI;
        }
    }
}