using System;
using System.Collections.Generic;
using System.Text;

using OpenCvSharp;
namespace OpenCVStudio_v0
{
    public sealed class AdaptiveThresholdExecutor : IImageBlockExecutor
    {
        public string BlockType => "AdaptiveThreshold";
        public Mat Execute(Mat input, ProjectBlock block, ProjectManager projectManager)
        {
            ArgumentNullException.ThrowIfNull(input);
            ArgumentNullException.ThrowIfNull(block);
            ArgumentNullException.ThrowIfNull(projectManager);
            if (input.Empty()) throw new ArgumentException("В AdaptiveThreshold передано пустое изображение.", nameof(input));
            AdaptiveThresholdMethodMode method = projectManager.GetBlockParameter(block.Id, "Method", AdaptiveThresholdMethodMode.Gaussian);
            AdaptiveThresholdTypeMode thresholdType = projectManager.GetBlockParameter(block.Id, "ThresholdType", AdaptiveThresholdTypeMode.Binary);
            double maxValue = projectManager.GetBlockParameter(block.Id, "MaxValue", 255.0);
            int blockSize = projectManager.GetBlockParameter(block.Id, "BlockSize", 11);
            double c = projectManager.GetBlockParameter(block.Id, "C", 2.0);
            ValidateParameters(maxValue, blockSize, c);
            using Mat grayInput = PrepareGrayInput(input);
            AdaptiveThresholdTypes openCvMethod = ConvertMethod(method);
            ThresholdTypes openCvThresholdType = ConvertThresholdType(thresholdType);
            Mat output = new Mat();
            try
            {
                Cv2.AdaptiveThreshold(grayInput, output, maxValue, openCvMethod, openCvThresholdType, blockSize, c);
                if (output.Empty()) throw new InvalidOperationException("AdaptiveThreshold вернул пустое изображение.");
                return output;
            }
            catch { output.Dispose(); throw; }
        }
        private static Mat PrepareGrayInput(Mat input)
        {
            if (input.Channels() == 1) return input.Clone();
            Mat gray = new Mat();
            try
            {
                if (input.Channels() == 3)
                {
                    Cv2.CvtColor(input, gray, ColorConversionCodes.BGR2GRAY);
                    return gray;
                }
                if (input.Channels() == 4)
                {
                    Cv2.CvtColor(input, gray, ColorConversionCodes.BGRA2GRAY);
                    return gray;
                }
                throw new NotSupportedException($"AdaptiveThreshold не поддерживает изображение с числом каналов {input.Channels()}.");
            }
            catch { gray.Dispose(); throw; }
        }
        private static AdaptiveThresholdTypes ConvertMethod(AdaptiveThresholdMethodMode method)
        {
            return method switch
            {
                AdaptiveThresholdMethodMode.Mean => AdaptiveThresholdTypes.MeanC,
                AdaptiveThresholdMethodMode.Gaussian => AdaptiveThresholdTypes.GaussianC,
                _ => throw new ArgumentOutOfRangeException(nameof(method), method, "Неизвестный метод адаптивного порога.")
            };
        }
        private static ThresholdTypes ConvertThresholdType(AdaptiveThresholdTypeMode thresholdType)
        {
            return thresholdType switch
            {
                AdaptiveThresholdTypeMode.Binary => ThresholdTypes.Binary,
                AdaptiveThresholdTypeMode.BinaryInv => ThresholdTypes.BinaryInv,
                _ => throw new ArgumentOutOfRangeException(nameof(thresholdType), thresholdType, "Неизвестный тип адаптивного порога.")
            };
        }
        private static void ValidateParameters(double maxValue, int blockSize, double c)
        {
            if (double.IsNaN(maxValue) || double.IsInfinity(maxValue) || maxValue < 0 || maxValue > 255)
                throw new ArgumentException("MaxValue должен быть от 0 до 255.");
            if (blockSize < 3 || blockSize > 255 || blockSize % 2 == 0)
                throw new ArgumentException("BlockSize должен быть нечётным числом от 3 до 255.");
            if (double.IsNaN(c) || double.IsInfinity(c) || c < -255 || c > 255)
                throw new ArgumentException("Поправка C должна быть от -255 до 255.");
        }
    }
}