using System;
using OpenCvSharp;

namespace OpenCVStudio_v0
{
    /// <summary>
    /// Преобразует изображение
    /// между цветовыми пространствами.
    /// </summary>
    public sealed class CvtColorExecutor : IImageBlockExecutor
    {
        public string BlockType => "CvtColor";

        public Mat Execute(Mat input, ProjectBlock block, ProjectManager projectManager)
        {
            ArgumentNullException.ThrowIfNull(input);
            ArgumentNullException.ThrowIfNull(block);
            ArgumentNullException.ThrowIfNull(projectManager);
            if (input.Empty())
            {
                throw new ArgumentException("В блок CvtColor передано пустое изображение.", nameof(input));
            }
            ColorConversionMode mode = projectManager.GetBlockParameter(block.Id, "ConversionMode", ColorConversionMode.BgrToGray);
            ValidateInput(input, mode, block);
            ColorConversionCodes conversionCode = GetConversionCode(mode);
            Mat output = new Mat();
            try
            {
                Cv2.CvtColor(input, output, conversionCode);
                if (output.Empty())
                {
                    throw new InvalidOperationException($"Блок «{block.Name}» вернул пустое изображение.");
                }
                return output;
            }
            catch
            {
                output.Dispose();
                throw;
            }
        }

        private static void ValidateInput(Mat input, ColorConversionMode mode, ProjectBlock block)
        {
            int requiredChannels = GetRequiredInputChannels(mode);
            int actualChannels = input.Channels();
            if (actualChannels != requiredChannels)
            {
                throw new InvalidOperationException($"Блок «{block.Name}» не может выполнить выбранное преобразование.\n\nРежим: {GetDisplayName(mode)}\nТребуется каналов: {requiredChannels}\nПолучено каналов: {actualChannels}");
            }
            /*
             * Для стандартных изображений нашей
             * программы поддерживаем глубину CV_8U.
             */
            if (input.Depth() != MatType.CV_8U)
            {
                throw new InvalidOperationException($"Блок «{block.Name}» сейчас поддерживает только изображения с глубиной CV_8U.\n\nПолучено: {input.Type()}");
            }
        }

        private static int GetRequiredInputChannels(ColorConversionMode mode)
        {
            return mode switch
            {
                ColorConversionMode.GrayToBgr => 1,
                ColorConversionMode.HsvToBgr => 3,
                ColorConversionMode.HlsToBgr => 3,
                ColorConversionMode.LabToBgr => 3,
                ColorConversionMode.YCrCbToBgr => 3,
                ColorConversionMode.RgbToBgr => 3,
                ColorConversionMode.BgraToBgr => 4,
                ColorConversionMode.BgrToGray => 3,
                ColorConversionMode.BgrToHsv => 3,
                ColorConversionMode.BgrToHls => 3,
                ColorConversionMode.BgrToLab => 3,
                ColorConversionMode.BgrToYCrCb => 3,
                ColorConversionMode.BgrToRgb => 3,
                ColorConversionMode.BgrToBgra => 3,
                _ => throw new ArgumentOutOfRangeException(nameof(mode), mode, "Неизвестный режим преобразования.")
            };
        }

        private static ColorConversionCodes GetConversionCode(ColorConversionMode mode)
        {
            return mode switch
            {
                ColorConversionMode.BgrToGray => ColorConversionCodes.BGR2GRAY,
                ColorConversionMode.GrayToBgr => ColorConversionCodes.GRAY2BGR,
                ColorConversionMode.BgrToHsv => ColorConversionCodes.BGR2HSV,
                ColorConversionMode.HsvToBgr => ColorConversionCodes.HSV2BGR,
                ColorConversionMode.BgrToHls => ColorConversionCodes.BGR2HLS,
                ColorConversionMode.HlsToBgr => ColorConversionCodes.HLS2BGR,
                ColorConversionMode.BgrToLab => ColorConversionCodes.BGR2Lab,
                ColorConversionMode.LabToBgr => ColorConversionCodes.Lab2BGR,
                ColorConversionMode.BgrToYCrCb => ColorConversionCodes.BGR2YCrCb,
                ColorConversionMode.YCrCbToBgr => ColorConversionCodes.YCrCb2BGR,
                ColorConversionMode.BgrToRgb => ColorConversionCodes.BGR2RGB,
                ColorConversionMode.RgbToBgr => ColorConversionCodes.RGB2BGR,
                ColorConversionMode.BgrToBgra => ColorConversionCodes.BGR2BGRA,
                ColorConversionMode.BgraToBgr => ColorConversionCodes.BGRA2BGR,
                _ => throw new ArgumentOutOfRangeException(nameof(mode), mode, "Неизвестный режим преобразования.")
            };
        }

        private static string GetDisplayName(ColorConversionMode mode)
        {
            return mode switch
            {
                ColorConversionMode.BgrToGray => "BGR → Gray",
                ColorConversionMode.GrayToBgr => "Gray → BGR",
                ColorConversionMode.BgrToHsv => "BGR → HSV",
                ColorConversionMode.HsvToBgr => "HSV → BGR",
                ColorConversionMode.BgrToHls => "BGR → HLS",
                ColorConversionMode.HlsToBgr => "HLS → BGR",
                ColorConversionMode.BgrToLab => "BGR → Lab",
                ColorConversionMode.LabToBgr => "Lab → BGR",
                ColorConversionMode.BgrToYCrCb => "BGR → YCrCb",
                ColorConversionMode.YCrCbToBgr => "YCrCb → BGR",
                ColorConversionMode.BgrToRgb => "BGR → RGB",
                ColorConversionMode.RgbToBgr => "RGB → BGR",
                ColorConversionMode.BgrToBgra => "BGR → BGRA",
                ColorConversionMode.BgraToBgr => "BGRA → BGR",
                _ => mode.ToString()
            };
        }
    }
}