using System;
using OpenCvSharp;

namespace OpenCVStudio_v0
{
    public sealed class PyrMeanShiftFilteringExecutor : IImageBlockExecutor
    {
        public string BlockType => "PyrMeanShiftFiltering";

        public Mat Execute(Mat input, ProjectBlock block, ProjectManager projectManager)
        {
            ArgumentNullException.ThrowIfNull(input);
            ArgumentNullException.ThrowIfNull(block);
            ArgumentNullException.ThrowIfNull(projectManager);

            if (input.Empty())
                throw new ArgumentException("В блок PyrMeanShiftFiltering передано пустое изображение.", nameof(input));

            double spatialRadius = projectManager.GetBlockParameter(block.Id, "SpatialRadius", 10.0);
            double colorRadius = projectManager.GetBlockParameter(block.Id, "ColorRadius", 20.0);
            int maxLevel = projectManager.GetBlockParameter(block.Id, "MaxLevel", 1);
            int maxIterations = projectManager.GetBlockParameter(block.Id, "MaxIterations", 5);
            double epsilon = projectManager.GetBlockParameter(block.Id, "Epsilon", 1.0);

            ValidateParameters(spatialRadius, colorRadius, maxLevel, maxIterations, epsilon);

            Mat source = PrepareInput(input);

            try
            {
                Mat result = new Mat();

                try
                {
                    Cv2.PyrMeanShiftFiltering(source, result, spatialRadius, colorRadius, maxLevel, new TermCriteria(CriteriaTypes.Eps | CriteriaTypes.MaxIter, maxIterations, epsilon));

                    if (result.Empty())
                        throw new InvalidOperationException("PyrMeanShiftFiltering вернул пустое изображение.");

                    return result;
                }
                catch
                {
                    result.Dispose();
                    throw;
                }
            }
            finally
            {
                source.Dispose();
            }
        }

        private static Mat PrepareInput(Mat input)
        {
            if (input.Channels() == 3)
                return input.Clone();

            Mat color = new Mat();

            if (input.Channels() == 1)
                Cv2.CvtColor(input, color, ColorConversionCodes.GRAY2BGR);
            else if (input.Channels() == 4)
                Cv2.CvtColor(input, color, ColorConversionCodes.BGRA2BGR);
            else
            {
                color.Dispose();
                throw new NotSupportedException($"Изображение с количеством каналов {input.Channels()} не поддерживается.");
            }

            return color;
        }

        private static void ValidateParameters(double spatialRadius, double colorRadius, int maxLevel, int maxIterations, double epsilon)
        {
            if (spatialRadius <= 0)
                throw new ArgumentOutOfRangeException(nameof(spatialRadius));

            if (colorRadius <= 0)
                throw new ArgumentOutOfRangeException(nameof(colorRadius));

            if (maxLevel < 0)
                throw new ArgumentOutOfRangeException(nameof(maxLevel));

            if (maxIterations <= 0)
                throw new ArgumentOutOfRangeException(nameof(maxIterations));

            if (epsilon < 0)
                throw new ArgumentOutOfRangeException(nameof(epsilon));
        }
    }
}