using System;
using OpenCvSharp;

namespace OpenCVStudio_v0
{
    /// <summary>
    /// Создаёт бинарную маску пикселей,
    /// значения которых находятся
    /// внутри заданного диапазона.
    /// </summary>
    public sealed class InRangeExecutor : IImageBlockExecutor
    {
        public string BlockType => "InRange";

        public Mat Execute(Mat input, ProjectBlock block, ProjectManager projectManager)
        {
            ArgumentNullException.ThrowIfNull(input);
            ArgumentNullException.ThrowIfNull(block);
            ArgumentNullException.ThrowIfNull(projectManager);
            if (input.Empty())
            {
                throw new ArgumentException("В блок InRange передано пустое изображение.", nameof(input));
            }
            double lower0 = projectManager.GetBlockParameter(block.Id, "Lower0", 0.0);
            double lower1 = projectManager.GetBlockParameter(block.Id, "Lower1", 0.0);
            double lower2 = projectManager.GetBlockParameter(block.Id, "Lower2", 0.0);
            double lower3 = projectManager.GetBlockParameter(block.Id, "Lower3", 0.0);
            double upper0 = projectManager.GetBlockParameter(block.Id, "Upper0", 255.0);
            double upper1 = projectManager.GetBlockParameter(block.Id, "Upper1", 255.0);
            double upper2 = projectManager.GetBlockParameter(block.Id, "Upper2", 255.0);
            double upper3 = projectManager.GetBlockParameter(block.Id, "Upper3", 255.0);
            ValidateParameters(input, lower0, lower1, lower2, lower3, upper0, upper1, upper2, upper3, block);
            Scalar lower = new Scalar(lower0, lower1, lower2, lower3);
            Scalar upper = new Scalar(upper0, upper1, upper2, upper3);
            Mat output = new Mat();
            try
            {
                Cv2.InRange(input, lower, upper, output);
                if (output.Empty())
                {
                    throw new InvalidOperationException($"Блок «{block.Name}» вернул пустую маску.");
                }
                return output;
            }
            catch
            {
                output.Dispose();
                throw;
            }
        }

        private static void ValidateParameters(Mat input, double lower0, double lower1, double lower2, double lower3, double upper0, double upper1, double upper2, double upper3, ProjectBlock block)
        {
            int channelCount = input.Channels();
            if (channelCount < 1 || channelCount > 4)
            {
                throw new InvalidOperationException($"Блок «{block.Name}» поддерживает изображения от 1 до 4 каналов.\n\nПолучено каналов: {channelCount}");
            }
            ValidateRange(lower0, upper0, "канал 0", block);
            if (channelCount >= 2)
            {
                ValidateRange(lower1, upper1, "канал 1", block);
            }
            if (channelCount >= 3)
            {
                ValidateRange(lower2, upper2, "канал 2", block);
            }
            if (channelCount >= 4)
            {
                ValidateRange(lower3, upper3, "канал 3", block);
            }
        }

        private static void ValidateRange(double lower, double upper, string channelName, ProjectBlock block)
        {
            if (double.IsNaN(lower) || double.IsInfinity(lower))
            {
                throw new InvalidOperationException($"Блок «{block.Name}»: нижняя граница для {channelName} должна быть конечным числом.");
            }
            if (double.IsNaN(upper) || double.IsInfinity(upper))
            {
                throw new InvalidOperationException($"Блок «{block.Name}»: верхняя граница для {channelName} должна быть конечным числом.");
            }
            if (lower > upper)
            {
                throw new InvalidOperationException($"Блок «{block.Name}»: нижняя граница для {channelName} не может быть больше верхней.\n\nНижняя граница: {lower}\nВерхняя граница: {upper}");
            }
        }
    }
}