using System;
using OpenCvSharp;

namespace OpenCVStudio_v0
{
    public sealed class GaussianBlurExecutor : IImageBlockExecutor
    {
        private const int MinimumKernelSize = 1;
        private const int MaximumKernelSize = 999;

        public string BlockType => "GaussianBlur";

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

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

            int kernelWidth = projectManager.GetBlockParameter(block.Id, "KernelWidth", 5);
            int kernelHeight = projectManager.GetBlockParameter(block.Id, "KernelHeight", 5);
            double sigmaX = projectManager.GetBlockParameter(block.Id, "SigmaX", 0.0);
            double sigmaY = projectManager.GetBlockParameter(block.Id, "SigmaY", 0.0);
            RotateBorderMode borderMode = projectManager.GetBlockParameter(block.Id, "BorderMode", RotateBorderMode.Reflect101);

            ValidateParameters(kernelWidth, kernelHeight, sigmaX, sigmaY, borderMode);

            Mat output = new Mat();

            try
            {
                Cv2.GaussianBlur(input, output, new OpenCvSharp. Size(kernelWidth, kernelHeight), sigmaX, sigmaY, ConvertBorderMode(borderMode));

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

                return output;
            }
            catch
            {
                output.Dispose();
                throw;
            }
        }

        private static BorderTypes ConvertBorderMode(RotateBorderMode borderMode)
        {
            return borderMode switch
            {
                RotateBorderMode.Constant => BorderTypes.Constant,
                RotateBorderMode.Replicate => BorderTypes.Replicate,
                RotateBorderMode.Reflect => BorderTypes.Reflect,
                RotateBorderMode.Reflect101 => BorderTypes.Reflect101,
                _ => throw new ArgumentOutOfRangeException(nameof(borderMode), borderMode, "Неизвестный режим обработки границ.")
            };
        }

        private static void ValidateParameters(int kernelWidth, int kernelHeight, double sigmaX, double sigmaY, RotateBorderMode borderMode)
        {
            ValidateKernelSize(kernelWidth, nameof(kernelWidth));
            ValidateKernelSize(kernelHeight, nameof(kernelHeight));
            ValidateSigma(sigmaX, nameof(sigmaX));
            ValidateSigma(sigmaY, nameof(sigmaY));

            if (!Enum.IsDefined(borderMode))
                throw new ArgumentOutOfRangeException(nameof(borderMode), borderMode, "Неизвестный режим обработки границ.");
        }

        private static void ValidateKernelSize(int value, string parameterName)
        {
            if (value < MinimumKernelSize || value > MaximumKernelSize)
                throw new ArgumentOutOfRangeException(parameterName, value, $"Размер ядра должен быть от {MinimumKernelSize} до {MaximumKernelSize}.");

            if (value % 2 == 0)
                throw new ArgumentException("Размер ядра должен быть нечётным.", parameterName);
        }

        private static void ValidateSigma(double value, string parameterName)
        {
            if (double.IsNaN(value) || double.IsInfinity(value) || value < 0.0 || value > 10000.0)
                throw new ArgumentOutOfRangeException(parameterName, value, "Sigma должна быть от 0 до 10000.");
        }
    }
}