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

namespace OpenCVStudio_v0
{
    /// <summary>
    /// Хранит текущее состояние данных
    /// во время выполнения одного pipeline.
    ///
    /// PipelineContext владеет:
    /// - текущим основным изображением;
    /// - вторым изображением;
    /// - коллекцией каналов изображения;
    /// - найденными контурами;
    /// - вычисленными геометрическими данными.
    /// </summary>
    internal sealed class PipelineContext : IDisposable
    {
        private Mat? image;
        private Mat? secondImage;
        private List<Mat>? channels;
        private bool disposed;


        /// <summary>
        /// Текущее основное изображение pipeline.
        ///
        /// Все обычные блоки Mat -> Mat
        /// работают именно с этим изображением.
        /// </summary>
        public Mat Image
        {
            get
            {
                ThrowIfDisposed();
                return image ?? throw new InvalidOperationException("Основное изображение pipeline ещё не загружено.");
            }
        }

        /// <summary>
        /// Показывает, загружено ли основное изображение.
        /// </summary>
        public bool HasImage
        {
            get
            {
                ThrowIfDisposed();
                return image != null && !image.Empty();
            }
        }

        /// <summary>
        /// Второе изображение pipeline.
        ///
        /// Используется блоками,
        /// принимающими два изображения.
        /// </summary>
        public Mat SecondImage
        {
            get
            {
                ThrowIfDisposed();
                return secondImage ?? throw new InvalidOperationException("Второе изображение pipeline ещё не загружено.");
            }
        }

        /// <summary>
        /// Показывает, загружено ли второе изображение.
        /// </summary>
        public bool HasSecondImage
        {
            get
            {
                ThrowIfDisposed();
                return secondImage != null && !secondImage.Empty();
            }
        }

        /// <summary>
        /// Показывает,
        /// создана ли коллекция каналов.
        /// </summary>
        public bool HasChannels
        {
            get
            {
                ThrowIfDisposed();
                return channels != null && channels.Count > 0;
            }
        }

        /// <summary>
        /// Коллекция каналов изображения.
        ///
        /// Индексы:
        /// 0 - Blue
        /// 1 - Green
        /// 2 - Red
        /// </summary>
        public IReadOnlyList<Mat> Channels
        {
            get
            {
                ThrowIfDisposed();
                return channels ?? throw new InvalidOperationException("Коллекция каналов ещё не создана.");
            }
        }

        /// <summary>
        /// Площади контуров,
        /// рассчитанные блоком ContourArea.
        /// </summary>
        public ContourAreaData? ContourAreas { get; set; }

        /// <summary>
        /// Длины контуров,
        /// рассчитанные блоком ArcLength.
        /// </summary>
        public ArcLengthData? ArcLengths { get; set; }

        /// <summary>
        /// Ограничивающие прямоугольники контуров,
        /// рассчитанные блоком BoundingRect.
        /// </summary>
        public BoundingRectData? BoundingRects { get; set; }

        /// <summary>
        /// Минимальные повёрнутые прямоугольники,
        /// рассчитанные блоком MinAreaRect.
        /// </summary>
        public MinAreaRectData? MinAreaRects { get; set; }

        public MinEnclosingCircleData? MinEnclosingCircles { get; set; }

        public ConvexHullData? ConvexHulls { get; set; }

        public FitEllipseData? FitEllipses { get; set; }

        /// <summary>
        /// Матрица преобразования двух изображений.
        /// Рассчитывается блоком FindTransformECC.
        /// </summary>
        public TransformMatrixData? TransformMatrix { get; set; }

        /// <summary>
        /// Контуры, найденные последним
        /// блоком FindContours.
        /// </summary>
        public ContourData? Contours { get; set; }

        /// <summary>
        /// Текстовый результат последнего блока.
        /// </summary>
        public string ResultText { get; set; } = string.Empty;

        public PipelineContext()
        {
        }

        /// <summary>
        /// Заменяет текущее основное изображение.
        ///
        /// Предыдущее изображение освобождается.
        /// Владение newImage переходит PipelineContext.
        /// </summary>
        public void ReplaceImage(Mat newImage)
        {
            ThrowIfDisposed();
            ValidateNewImage(newImage, nameof(newImage));
            if (ReferenceEquals(image, newImage))
            {
                return;
            }
            Mat? previousImage = image;
            image = newImage;
            previousImage?.Dispose();
        }

        /// <summary>
        /// Заменяет второе изображение.
        ///
        /// Предыдущее второе изображение освобождается.
        /// Владение newImage переходит PipelineContext.
        /// </summary>
        public void ReplaceSecondImage(Mat newImage)
        {
            ThrowIfDisposed();
            ValidateNewImage(newImage, nameof(newImage));
            if (ReferenceEquals(secondImage, newImage))
            {
                return;
            }
            Mat? previousImage = secondImage;
            secondImage = newImage;
            previousImage?.Dispose();
        }

        /// <summary>
        /// Заменяет коллекцию каналов изображения.
        ///
        /// Владение Mat переходит PipelineContext.
        /// Предыдущая коллекция освобождается.
        /// </summary>
        public void ReplaceChannels(IEnumerable<Mat> newChannels)
        {
            ThrowIfDisposed();
            ArgumentNullException.ThrowIfNull(newChannels);
            List<Mat> newList = newChannels.ToList();
            if (newList.Count == 0)
            {
                throw new ArgumentException("Коллекция каналов не может быть пустой.", nameof(newChannels));
            }
            foreach (Mat channel in newList)
            {
                ValidateNewImage(channel, nameof(newChannels));
            }
            DisposeChannels();
            channels = newList;
        }

        /// <summary>
        /// Передаёт владение основным изображением
        /// вызывающему коду.
        /// </summary>
        public Mat DetachImage()
        {
            ThrowIfDisposed();
            Mat detachedImage = image ?? throw new InvalidOperationException("Основное изображение pipeline не было создано.");
            image = null;
            return detachedImage;
        }

        private static void ValidateNewImage(Mat newImage, string parameterName)
        {
            ArgumentNullException.ThrowIfNull(newImage, parameterName);
            if (newImage.Empty())
            {
                throw new ArgumentException("Нельзя установить пустое изображение.", parameterName);
            }
        }

        private void DisposeChannels()
        {
            if (channels == null)
            {
                return;
            }
            foreach (Mat channel in channels)
            {
                channel.Dispose();
            }
            channels.Clear();
            channels = null;
        }

        private void ThrowIfDisposed()
        {
            ObjectDisposedException.ThrowIf(disposed, this);
        }

        public void Dispose()
        {
            if (disposed)
            {
                return;
            }
            disposed = true;
            image?.Dispose();
            image = null;
            secondImage?.Dispose();
            secondImage = null;
            DisposeChannels();
            Contours = null;
            ContourAreas = null;
            ArcLengths = null;
            BoundingRects = null;
            MinAreaRects = null;
            MinEnclosingCircles = null;
            ConvexHulls = null;
            FitEllipses = null;
            TransformMatrix?.Matrix.Dispose();
            TransformMatrix = null;
            ResultText = string.Empty;
            GC.SuppressFinalize(this);
        }
    }
}