using System;
using OpenCvSharp;

namespace OpenCVStudio_v0
{
    public sealed class LaplacianExecutor : IImageBlockExecutor
    {
        private const double MinimumScale = 0.000001;
        private const double MaximumScale = 10000.0;

        private const double MinimumDelta = -1000000.0;
        private const double MaximumDelta = 1000000.0;

        public string BlockType => "Laplacian";

        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 kernelSize = projectManager.GetBlockParameter(block.Id, "KernelSize", 3);
            double scale = projectManager.GetBlockParameter(block.Id, "Scale", 1.0);
            double delta = projectManager.GetBlockParameter(block.Id, "Delta", 0.0);
            bool convertToGray = projectManager.GetBlockParameter(block.Id, "ConvertToGray", true);
            RotateBorderMode borderMode = projectManager.GetBlockParameter(block.Id, "BorderMode", RotateBorderMode.Reflect101);

            ValidateParameters(input, kernelSize, scale, delta, borderMode);

            using Mat preparedInput = PrepareInput(input, convertToGray);

            using Mat signedResult = new Mat();

            Mat output = new Mat();

            try
            {
                Cv2.Laplacian(preparedInput, signedResult, MatType.CV_16S, kernelSize, scale, delta, ConvertBorderMode(borderMode));

                Cv2.ConvertScaleAbs(signedResult, output);

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

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

        private static Mat PrepareInput(Mat input, bool convertToGray)
        {
            if (!convertToGray)
                return input.Clone();

            int channels = input.Channels();

            if (channels == 1)
                return input.Clone();

            Mat gray = new Mat();

            try
            {
                switch (channels)
                {
                    case 3:
                        Cv2.CvtColor(input, gray, ColorConversionCodes.BGR2GRAY);
                        break;

                    case 4:
                        Cv2.CvtColor(input, gray, ColorConversionCodes.BGRA2GRAY);
                        break;

                    default:
                        throw new ArgumentException($"Автоматическое преобразование в серый не поддерживает изображение с количеством каналов: {channels}.", nameof(input));
                }

                if (gray.Empty())
                    throw new InvalidOperationException("Не удалось преобразовать входное изображение в оттенки серого.");

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

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

        private static void ValidateParameters(Mat input, int kernelSize, double scale, double delta, RotateBorderMode borderMode)
        {
            ValidateKernelSize(kernelSize);
            ValidateScale(scale);
            ValidateDelta(delta);

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

            if (input.Width < kernelSize || input.Height < kernelSize)
                throw new ArgumentException($"Размер ядра {kernelSize} × {kernelSize} превышает размер изображения {input.Width} × {input.Height}.", nameof(input));
        }

        private static void ValidateKernelSize(int value)
        {
            if (value != 1 && value != 3 && value != 5 && value != 7)
                throw new ArgumentOutOfRangeException(nameof(value), value, "Размер ядра оператора Лапласа должен быть равен 1, 3, 5 или 7.");
        }

        private static void ValidateScale(double value)
        {
            if (double.IsNaN(value) || double.IsInfinity(value) || value < MinimumScale || value > MaximumScale)
                throw new ArgumentOutOfRangeException(nameof(value), value, $"Масштаб должен находиться в диапазоне от {MinimumScale} до {MaximumScale}.");
        }

        private static void ValidateDelta(double value)
        {
            if (double.IsNaN(value) || double.IsInfinity(value) || value < MinimumDelta || value > MaximumDelta)
                throw new ArgumentOutOfRangeException(nameof(value), value, $"Смещение должно находиться в диапазоне от {MinimumDelta} до {MaximumDelta}.");
        }
    }
}