using OpenCvSharp;

namespace OpenCVStudio_v0
{
    /// <summary>
    /// Формирует коллекцию каналов,
    /// выбирая каждый канал из первого
    /// или второго входного изображения.
    ///
    /// Вход:
    /// context.Image;
    /// context.SecondImage.
    ///
    /// Выход:
    /// context.Channels.
    ///
    /// Если параметр канала выключен,
    /// канал берётся из первого изображения.
    ///
    /// Если параметр канала включён,
    /// канал берётся из второго изображения.
    ///
    /// Пример:
    ///
    /// Первое изображение:
    /// [A0, A1, A2]
    ///
    /// Второе изображение:
    /// [B0, B1, B2]
    ///
    /// SwapChannel0 = true
    /// SwapChannel1 = false
    /// SwapChannel2 = true
    ///
    /// Результат:
    /// [B0, A1, B2]
    /// </summary>
    internal sealed class SwapChannelsExecutor : IChannelCollectionBlockExecutor
    {
        public string BlockType => "SwapChannels";

        public void Execute(PipelineContext context, ProjectBlock block, ProjectManager projectManager)
        {
            ArgumentNullException.ThrowIfNull(context);
            ArgumentNullException.ThrowIfNull(block);
            ArgumentNullException.ThrowIfNull(projectManager);
            ValidateInputs(context, block);
            bool swapChannel0 = projectManager.GetBlockParameter(block.Id, "SwapChannel0", false);
            bool swapChannel1 = projectManager.GetBlockParameter(block.Id, "SwapChannel1", false);
            bool swapChannel2 = projectManager.GetBlockParameter(block.Id, "SwapChannel2", false);
            Mat[]? firstChannels = null;
            Mat[]? secondChannels = null;
            Mat[]? resultChannels = null;
            try
            {
                firstChannels = Cv2.Split(context.Image);
                secondChannels = Cv2.Split(context.SecondImage);
                ValidateChannels(firstChannels, secondChannels, block);
                resultChannels = new Mat[]
                {
                    swapChannel0 ? secondChannels[0] : firstChannels[0],
                    swapChannel1 ? secondChannels[1] : firstChannels[1],
                    swapChannel2 ? secondChannels[2] : firstChannels[2]
                };
                DisposeUnusedChannels(firstChannels, secondChannels, swapChannel0, swapChannel1, swapChannel2);
                context.ReplaceChannels(resultChannels);
                resultChannels = null;
                firstChannels = null;
                secondChannels = null;
                context.ResultText = CreateResultText(swapChannel0, swapChannel1, swapChannel2);
            }
            finally
            {
                DisposeChannels(firstChannels);
                DisposeChannels(secondChannels);
                DisposeChannels(resultChannels);
            }
        }

        private static void ValidateInputs(PipelineContext context, ProjectBlock block)
        {
            if (!context.HasImage)
            {
                throw new InvalidOperationException($"Блок «{block.Name}» не получил изображение на первом входе.");
            }
            if (!context.HasSecondImage)
            {
                throw new InvalidOperationException($"Блок «{block.Name}» не получил изображение на втором входе.");
            }
            Mat firstImage = context.Image;
            Mat secondImage = context.SecondImage;
            if (firstImage.Channels() != 3)
            {
                throw new InvalidOperationException($"Первое изображение блока «{block.Name}» должно содержать три канала.\r\n\r\nПолучено каналов: {firstImage.Channels()}.");
            }
            if (secondImage.Channels() != 3)
            {
                throw new InvalidOperationException($"Второе изображение блока «{block.Name}» должно содержать три канала.\r\n\r\nПолучено каналов: {secondImage.Channels()}.");
            }
            if (firstImage.Size() != secondImage.Size())
            {
                throw new InvalidOperationException($"Блок «{block.Name}» требует изображения одинакового размера.\r\n\r\nПервое: {firstImage.Width} × {firstImage.Height}\r\nВторое: {secondImage.Width} × {secondImage.Height}");
            }
            if (firstImage.Type() != secondImage.Type())
            {
                throw new InvalidOperationException($"Блок «{block.Name}» требует изображения одинакового типа.\r\n\r\nПервое: {firstImage.Type()}\r\nВторое: {secondImage.Type()}");
            }
        }

        private static void ValidateChannels(Mat[] firstChannels, Mat[] secondChannels, ProjectBlock block)
        {
            if (firstChannels.Length != 3)
            {
                throw new InvalidOperationException($"Блок «{block.Name}» ожидал три канала первого изображения, но получил {firstChannels.Length}.");
            }
            if (secondChannels.Length != 3)
            {
                throw new InvalidOperationException($"Блок «{block.Name}» ожидал три канала второго изображения, но получил {secondChannels.Length}.");
            }
        }

        private static void DisposeUnusedChannels(Mat[] firstChannels, Mat[] secondChannels, bool swapChannel0, bool swapChannel1, bool swapChannel2)
        {
            DisposeUnusedChannel(firstChannels, secondChannels, 0, swapChannel0);
            DisposeUnusedChannel(firstChannels, secondChannels, 1, swapChannel1);
            DisposeUnusedChannel(firstChannels, secondChannels, 2, swapChannel2);
        }

        private static void DisposeUnusedChannel(Mat[] firstChannels, Mat[] secondChannels, int channelIndex, bool takeFromSecondImage)
        {
            if (takeFromSecondImage)
            {
                firstChannels[channelIndex].Dispose();
            }
            else
            {
                secondChannels[channelIndex].Dispose();
            }
        }

        private static void DisposeChannels(Mat[]? channels)
        {
            if (channels == null)
            {
                return;
            }
            foreach (Mat channel in channels)
            {
                channel.Dispose();
            }
        }

        private static string CreateResultText(bool swapChannel0, bool swapChannel1, bool swapChannel2)
        {
            string channel0Source = swapChannel0 ? "второе изображение" : "первое изображение";
            string channel1Source = swapChannel1 ? "второе изображение" : "первое изображение";
            string channel2Source = swapChannel2 ? "второе изображение" : "первое изображение";
            return $"Сформирована коллекция каналов.\r\nКанал 0: {channel0Source}.\r\nКанал 1: {channel1Source}.\r\nКанал 2: {channel2Source}.";
        }
    }
}