using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using OpenCvSharp;

namespace OpenCVStudio_v0
{
    /// <summary>
    /// Выполняет включённые блоки текущего проекта
    /// в порядке их расположения.
    /// </summary>
    public sealed class PipelineExecutor
    {
        private readonly ProjectManager projectManager;

        /*
         * Исполнители существующих блоков,
         * работающих по схеме Mat -> Mat.
         */
        private readonly Dictionary<string, IImageBlockExecutor> imageExecutors;

        /*
         * Исполнители новых блоков, работающих
         * с общим контекстом pipeline.
         *
         * Через контекст блоки смогут использовать:
         * - текущее изображение;
         * - найденные контуры;
         * - текстовые результаты вычислений.
         */
        private readonly Dictionary<string, IPipelineBlockExecutor> pipelineExecutors;

        private readonly Dictionary<string, ITwoOutputPipelineBlockExecutor> twoOutputExecutors;
        private readonly Dictionary<string, IChannelCollectionBlockExecutor> channelCollectionExecutors;

        public PipelineExecutor(ProjectManager projectManager)
        {
            this.projectManager = projectManager ?? throw new ArgumentNullException(nameof(projectManager));

            imageExecutors = new Dictionary<string, IImageBlockExecutor>(StringComparer.OrdinalIgnoreCase);
            pipelineExecutors = new Dictionary<string, IPipelineBlockExecutor>(StringComparer.OrdinalIgnoreCase);
            twoOutputExecutors =    new Dictionary<string, ITwoOutputPipelineBlockExecutor>( StringComparer.OrdinalIgnoreCase);
            channelCollectionExecutors = new Dictionary<string, IChannelCollectionBlockExecutor>(StringComparer.OrdinalIgnoreCase);



            RegisterImageExecutors();
            RegisterPipelineExecutors();
            RegisterTwoOutputExecutors();
            RegisterChannelCollectionExecutors();

        }

        /// <summary>
        /// Выполняет весь pipeline текущего проекта.
        /// </summary>
        public PipelineExecutionResult ExecuteAll() => ExecuteInternal(stopAfterBlockId: null);

        /// <summary>
        /// Выполняет pipeline до указанного блока включительно.
        /// Используется для предпросмотра результата блока.
        /// </summary>
        public PipelineExecutionResult ExecuteToBlock(Guid blockId)
        {
            if (blockId == Guid.Empty)
                throw new ArgumentException("Не указан идентификатор блока.", nameof(blockId));

            return ExecuteInternal(blockId);
        }

        private void RegisterTwoOutputExecutor(ITwoOutputPipelineBlockExecutor executor)
        {
            ArgumentNullException.ThrowIfNull(executor);
            if (string.IsNullOrWhiteSpace(executor.BlockType))
            {
                throw new ArgumentException("Исполнитель не указал тип блока.", nameof(executor));
            }
            if (!twoOutputExecutors.TryAdd(executor.BlockType, executor))
            {
                throw new InvalidOperationException($"Исполнитель блока «{executor.BlockType}» уже зарегистрирован.");
            }
        }

        /// <summary>
        /// Регистрирует существующие исполнители,
        /// работающие по схеме Mat -> Mat.
        /// </summary>
        private void RegisterImageExecutors()
        {
            RegisterImageExecutor(new ColorChannelExecutor());
            RegisterImageExecutor(new CvtColorExecutor());
            RegisterImageExecutor(new DemosaicingExecutor());
            RegisterImageExecutor(new ExtractChannelExecutor());
            RegisterImageExecutor(new ApplyColorMapExecutor());
            RegisterImageExecutor(new ConvertScaleAbsExecutor());
            RegisterImageExecutor(new BlurExecutor());
            RegisterImageExecutor(new BoxFilterExecutor());
            RegisterImageExecutor(new SimpleThresholdExecutor());
            RegisterImageExecutor( new InRangeExecutor());
            RegisterImageExecutor( new ErodeExecutor());
            RegisterImageExecutor(new DilateExecutor());

            RegisterImageExecutor(new AdaptiveThresholdExecutor());
            RegisterImageExecutor(new MorphologyExecutor());
            RegisterImageExecutor(new MorphologyBlockExecutorAdapter("MorphologyOpen", MorphologyOperationMode.Open));
            RegisterImageExecutor(new MorphologyBlockExecutorAdapter("MorphologyClose", MorphologyOperationMode.Close));
            RegisterImageExecutor(new MorphologyBlockExecutorAdapter("MorphologyGradient", MorphologyOperationMode.Gradient));
            RegisterImageExecutor(new ThinningExecutor());
            RegisterImageExecutor(new BitwiseNotExecutor());
            RegisterImageExecutor( new AddScalarExecutor());
            RegisterImageExecutor(new SubtractScalarExecutor());
            RegisterImageExecutor( new MultiplyScalarExecutor());
            RegisterImageExecutor( new DivideScalarExecutor());
            RegisterImageExecutor(new PowExecutor());
            RegisterImageExecutor(new SqrtExecutor());
            RegisterImageExecutor( new AbsExecutor());
            RegisterImageExecutor(new CannyExecutor());
            RegisterImageExecutor(new EqualizeHistExecutor());
            RegisterImageExecutor(new ClaheExecutor());
            RegisterImageExecutor(new BrightnessContrastExecutor());
            RegisterImageExecutor(new GammaCorrectionExecutor());
            RegisterImageExecutor(new NormalizeExecutor());
            RegisterImageExecutor(new LutExecutor());
            RegisterImageExecutor(new TransposeExecutor());
            RegisterImageExecutor(new InvertExecutor());
            RegisterImageExecutor(new ResizeExecutor());
            RegisterImageExecutor(new CropExecutor());
            RegisterImageExecutor(new FlipExecutor());
            RegisterImageExecutor(new RotateExecutor());
            RegisterImageExecutor(new TranslateExecutor());
            RegisterImageExecutor(new AddBorderExecutor());
            RegisterImageExecutor(new MedianBlurExecutor());
            RegisterImageExecutor(new GaussianBlurExecutor());
            RegisterImageExecutor(new BilateralFilterExecutor());
            RegisterImageExecutor(new SobelExecutor());
            RegisterImageExecutor(new ScharrExecutor());
            RegisterImageExecutor(new LaplacianExecutor());
            RegisterImageExecutor(new PyrDownExecutor());
            RegisterImageExecutor(new PyrUpExecutor());
            RegisterImageExecutor(new PyrMeanShiftFilteringExecutor());

            RegisterImageExecutor(new CopyMakeBorderExecutor());

            RegisterImageExecutor( new ImageSaveExecutor());

            RegisterImageExecutor(new GetRectSubPixExecutor());

            RegisterImageExecutor(new  Filter2DExecutor());

            RegisterImageExecutor(new   SepFilter2DExecutor());
            RegisterImageExecutor(new FastNlMeansDenoisingExecutor());
            RegisterImageExecutor(new FastNlMeansDenoisingColoredExecutor());

            RegisterImageExecutor(new MagnitudeExecutor());

            RegisterImageExecutor(new PhaseExecutor());
            RegisterImageExecutor(new CompareScalarExecutor());

 


        }

        /// <summary>
        /// Регистрирует исполнители, работающие
        /// с общим контекстом pipeline.
        /// </summary>
        private void RegisterPipelineExecutors()
        {
            /*
             * Контурные исполнители будут добавляться здесь.
             *
             * Например:
             *
             * RegisterPipelineExecutor(
             *     new FindContoursExecutor());
             */










            RegisterPipelineExecutor(   new ImageSourceExecutor());
            RegisterPipelineExecutor(   new SecondImageSourceExecutor());           
            RegisterPipelineExecutor(    new AbsDiffExecutor());
            RegisterPipelineExecutor(   new BitwiseAndExecutor());
            RegisterPipelineExecutor(    new BitwiseOrExecutor());
            RegisterPipelineExecutor(   new BitwiseXorExecutor());
            RegisterPipelineExecutor(    new AddWeightedExecutor());
            RegisterPipelineExecutor(    new AddExecutor());
            RegisterPipelineExecutor(    new SubtractExecutor());
            RegisterPipelineExecutor(    new MultiplyExecutor());
            RegisterPipelineExecutor(    new DivideExecutor());
            RegisterPipelineExecutor(    new FindContoursExecutor());
            RegisterPipelineExecutor(    new ContourFilterExecutor());
            RegisterPipelineExecutor(    new ApproxPolyDPExecutor());
            RegisterPipelineExecutor(    new ContourAreaExecutor());
            RegisterPipelineExecutor(     new ArcLengthExecutor());
            RegisterPipelineExecutor(       new BoundingRectExecutor());
            RegisterPipelineExecutor(    new MinAreaRectExecutor());
            RegisterPipelineExecutor(       new DrawContoursExecutor());

            RegisterPipelineExecutor(new MinEnclosingCircleExecutor());

            RegisterPipelineExecutor(new ConvexHullExecutor());
            RegisterPipelineExecutor( new FitEllipseExecutor());

            RegisterPipelineExecutor(  new FindTransformECCExecutor());

            RegisterPipelineExecutor(    new AlignImageExecutor());



        }


        private void RegisterTwoOutputExecutors()
        {

            RegisterTwoOutputExecutor(    new ChannelSelectorExecutor());

        }


        private void RegisterChannelCollectionExecutors()
        {
            RegisterChannelCollectionExecutor(   new SplitChannelsExecutor());
            RegisterChannelCollectionExecutor(   new MergeChannelsExecutor());
            RegisterChannelCollectionExecutor(   new SwapChannelsExecutor());
        }




        private void RegisterChannelCollectionExecutor(IChannelCollectionBlockExecutor executor)
        {
            ArgumentNullException.ThrowIfNull(executor);
            if (string.IsNullOrWhiteSpace(executor.BlockType))
            {
                throw new ArgumentException("Исполнитель не указал тип блока.", nameof(executor));
            }
            if (!channelCollectionExecutors.TryAdd(executor.BlockType, executor))
            {
                throw new InvalidOperationException($"Исполнитель блока «{executor.BlockType}» уже зарегистрирован.");
            }
        }
        /// <summary>
        /// Регистрирует исполнитель блока Mat -> Mat.
        /// </summary>
        private void RegisterImageExecutor(IImageBlockExecutor executor)
        {
            ArgumentNullException.ThrowIfNull(executor);

            if (string.IsNullOrWhiteSpace(executor.BlockType))
                throw new ArgumentException("Исполнитель не указал тип блока.", nameof(executor));

            if (!imageExecutors.TryAdd(executor.BlockType, executor))
                throw new InvalidOperationException($"Исполнитель блока «{executor.BlockType}» уже зарегистрирован.");
        }

        /// <summary>
        /// Регистрирует исполнитель блока,
        /// работающего с общим контекстом.
        /// </summary>
        private void RegisterPipelineExecutor(IPipelineBlockExecutor executor)
        {
            ArgumentNullException.ThrowIfNull(executor);

            if (string.IsNullOrWhiteSpace(executor.BlockType))
                throw new ArgumentException("Исполнитель не указал тип блока.", nameof(executor));

            if (!pipelineExecutors.TryAdd(executor.BlockType, executor))
                throw new InvalidOperationException($"Исполнитель блока «{executor.BlockType}» уже зарегистрирован.");
        }

        /// <summary>
        /// Загружает исходное изображение и выполняет
        /// включённые блоки проекта.
        /// </summary>
        private PipelineExecutionResult ExecuteInternal(Guid? stopAfterBlockId)
        {
            ValidateProject();
            using PipelineContext context = new PipelineContext();
            Guid? lastExecutedBlockId = null;
            string lastExecutedBlockName = "Pipeline не выполнялся";
            List<ProjectBlock> blocks = projectManager.CurrentProject!.Blocks.OrderBy(block => block.Order).ToList();
            foreach (ProjectBlock block in blocks)
            {
                if (!block.IsEnabled)
                {
                    continue;
                }
                ExecuteBlock(context, block);
                lastExecutedBlockId = block.Id;
                lastExecutedBlockName = block.Name;
                if (stopAfterBlockId.HasValue && block.Id == stopAfterBlockId.Value)
                {
                    break;
                }
            }

            if (!context.HasImage)
            {
                throw new InvalidOperationException( "Pipeline не создал основное изображение.\n\n" +   "Проверьте наличие и настройки блока " +   "«Исходное изображение».");
            }
            /*
             * Передаём владение итоговым Mat
             * из PipelineContext наружу.
             */
            Mat resultImage = context.DetachImage();
            try
            {
                /*
                 * После успешного создания результата
                 * владельцем resultImage становится
                 * PipelineExecutionResult.
                 */
                return new PipelineExecutionResult(resultImage, lastExecutedBlockId, lastExecutedBlockName, context.Contours, context.ContourAreas, context.ArcLengths, context.BoundingRects, context.MinAreaRects, context.FitEllipses, context.ResultText);
            }
            catch
            {
                /*
                 * Если конструктор результата неожиданно
                 * завершится ошибкой, освобождаем отделённый Mat.
                 */
                resultImage.Dispose();
                throw;
            }
        }
        /// <summary>
        /// Выполняет один блок pipeline.
        /// </summary>
        private void ExecuteBlock(PipelineContext context, ProjectBlock block)
        {
            ArgumentNullException.ThrowIfNull(context);
            ArgumentNullException.ThrowIfNull(block);

            if (string.IsNullOrWhiteSpace(block.BlockType))
                throw new InvalidOperationException($"У блока «{block.Name}» не указан программный тип.");

            /*
             * Сначала ищем новый исполнитель,
             * работающий с PipelineContext.
             */
            if (pipelineExecutors.TryGetValue(block.BlockType, out IPipelineBlockExecutor? pipelineExecutor))
            {
                pipelineExecutor.Execute(context, block, projectManager);
                ValidatePipelineBlockResult(context, block);
                return;
            }

            /*
             * Если нового исполнителя нет,
             * ищем существующий Mat -> Mat исполнитель.
             */
            if (imageExecutors.TryGetValue(block.BlockType, out IImageBlockExecutor? imageExecutor))
            {
                ExecuteImageBlock(context, block, imageExecutor);

                return;
            }

            if (twoOutputExecutors.TryGetValue(block.BlockType, out ITwoOutputPipelineBlockExecutor? twoOutputExecutor))
            {
                twoOutputExecutor.Execute(context, block, projectManager);
                ValidatePipelineBlockResult(context, block);
                return;
            }


            if (channelCollectionExecutors.TryGetValue(block.BlockType, out IChannelCollectionBlockExecutor? channelExecutor))
            {
                channelExecutor.Execute(context, block, projectManager);
                ValidatePipelineBlockResult(context, block);
                return;
            }


            /*
             * Пока сохраняем совместимость
             * с ранее сохранёнными проектами.
             *
             * Неизвестный или ещё не реализованный блок
             * пропускается, а текущее изображение
             * остаётся без изменений.
             *
             * Позднее это можно заменить на исключение
             * или запись предупреждения в журнал.
             */
        }

        /// <summary>
        /// Выполняет существующий блок Mat -> Mat
        /// и безопасно передаёт новый Mat контексту.
        /// </summary>
        private void ExecuteImageBlock(PipelineContext context, ProjectBlock block, IImageBlockExecutor executor)
        {
            Mat? nextImage = null;

            try
            {
                nextImage = executor.Execute(context.Image, block, projectManager);

                if (nextImage == null)
                    throw new InvalidOperationException($"Блок «{block.Name}» вернул null.");

                if (nextImage.Empty())
                    throw new InvalidOperationException($"Блок «{block.Name}» вернул пустое изображение.");

                /*
                 * ReplaceImage освобождает прежний Mat
                 * и принимает владение новым.
                 *
                 * Если исполнитель вернул тот же экземпляр,
                 * ReplaceImage не освобождает его.
                 */
                context.ReplaceImage(nextImage);

                /*
                 * Обнуляем локальную переменную,
                 * поскольку владельцем Mat теперь является
                 * PipelineContext.
                 */
                nextImage = null;
            }
            finally
            {
                /*
                 * Если ошибка произошла до успешной передачи
                 * изображения контексту, освобождаем Mat здесь.
                 */
                nextImage?.Dispose();
            }
        }

        /// <summary>
        /// Проверяет наличие открытого проекта.
        /// </summary>
        private void ValidateProject()
        {
            if (!projectManager.HasProject || projectManager.CurrentProject == null)
                throw new InvalidOperationException("Проект не открыт.");
        }



        private static void ValidatePipelineBlockResult(PipelineContext context, ProjectBlock block)
        {
            ArgumentNullException.ThrowIfNull(context);
            ArgumentNullException.ThrowIfNull(block);
            /*
             * ImageSource обязан создать
             * основное изображение.
             */
            if (string.Equals(block.BlockType, "ImageSource", StringComparison.OrdinalIgnoreCase))
            {
                if (!context.HasImage)
                {
                    throw new InvalidOperationException($"Блок «{block.Name}» не загрузил основное изображение.");
                }
                return;
            }
            /*
             * SecondImageSource обязан создать
             * второе изображение.
             */
            if (string.Equals(block.BlockType, "SecondImageSource", StringComparison.OrdinalIgnoreCase))
            {
                if (!context.HasSecondImage)
                {
                    throw new InvalidOperationException($"Блок «{block.Name}» не загрузил второе изображение.");
                }
                return;
            }
            /*
             * Все остальные pipeline-блоки сейчас
             * должны сохранять основное изображение.
             */
            if (string.Equals(block.BlockType, "ChannelSelector", StringComparison.OrdinalIgnoreCase))
            {
                if (!context.HasImage || !context.HasSecondImage)
                {
                    throw new InvalidOperationException($"Блок «{block.Name}» не создал два выходных изображения.");
                }
                return;
            }
        }
    }
}