using System;
using System.ComponentModel;

namespace OpenCVStudio_v0
{
    public sealed class PyrMeanShiftFilteringBlockProperties
    {
        private const double MinimumSpatialRadius = 1.0;
        private const double MaximumSpatialRadius = 300.0;

        private const double MinimumColorRadius = 1.0;
        private const double MaximumColorRadius = 300.0;

        private const int MinimumMaxLevel = 0;
        private const int MaximumMaxLevel = 8;

        private const int MinimumIterations = 1;
        private const int MaximumIterations = 100;

        private const double MinimumEpsilon = 0.0;
        private const double MaximumEpsilon = 100.0;

        private readonly ProjectManager projectManager;
        private readonly ProjectBlock projectBlock;

        private double spatialRadius;
        private double colorRadius;
        private int maxLevel;
        private int maxIterations;
        private double epsilon;

        public PyrMeanShiftFilteringBlockProperties(ProjectManager projectManager, ProjectBlock projectBlock)
        {
            this.projectManager = projectManager ?? throw new ArgumentNullException(nameof(projectManager));
            this.projectBlock = projectBlock ?? throw new ArgumentNullException(nameof(projectBlock));

            spatialRadius = projectManager.GetBlockParameter(projectBlock.Id, "SpatialRadius", 10.0);
            colorRadius = projectManager.GetBlockParameter(projectBlock.Id, "ColorRadius", 20.0);
            maxLevel = projectManager.GetBlockParameter(projectBlock.Id, "MaxLevel", 1);
            maxIterations = projectManager.GetBlockParameter(projectBlock.Id, "MaxIterations", 5);
            epsilon = projectManager.GetBlockParameter(projectBlock.Id, "Epsilon", 1.0);
        }

        [Category("Mean Shift")]
        [DisplayName("Пространственный радиус")]
        [Description("Радиус поиска соседних пикселей по координатам изображения. Чем больше значение, тем крупнее сглаживаемые области.")]
        public double SpatialRadius
        {
            get => spatialRadius;

            set
            {
                ValidateSpatialRadius(value);

                if (Math.Abs(spatialRadius - value) < double.Epsilon)
                    return;

                spatialRadius = value;

                projectManager.SetBlockParameter(projectBlock.Id, "SpatialRadius", value);
            }
        }

        [Category("Mean Shift")]
        [DisplayName("Цветовой радиус")]
        [Description("Максимальное различие цветов, при котором пиксели считаются принадлежащими одной цветовой области.")]
        public double ColorRadius
        {
            get => colorRadius;

            set
            {
                ValidateColorRadius(value);

                if (Math.Abs(colorRadius - value) < double.Epsilon)
                    return;

                colorRadius = value;

                projectManager.SetBlockParameter(projectBlock.Id, "ColorRadius", value);
            }
        }

        [Category("Пирамида изображения")]
        [DisplayName("Максимальный уровень")]
        [Description("Количество дополнительных уровней пирамиды изображения. Значение 0 отключает использование дополнительных уровней.")]
        public int MaxLevel
        {
            get => maxLevel;

            set
            {
                ValidateMaxLevel(value);

                if (maxLevel == value)
                    return;

                maxLevel = value;

                projectManager.SetBlockParameter(projectBlock.Id, "MaxLevel", value);
            }
        }

        [Category("Критерий остановки")]
        [DisplayName("Максимум итераций")]
        [Description("Максимальное количество итераций поиска среднего значения.")]
        public int MaxIterations
        {
            get => maxIterations;

            set
            {
                ValidateMaxIterations(value);

                if (maxIterations == value)
                    return;

                maxIterations = value;

                projectManager.SetBlockParameter(projectBlock.Id, "MaxIterations", value);
            }
        }

        [Category("Критерий остановки")]
        [DisplayName("Точность")]
        [Description("Минимальное изменение положения или цвета, после которого итерационный поиск считается завершённым.")]
        public double Epsilon
        {
            get => epsilon;

            set
            {
                ValidateEpsilon(value);

                if (Math.Abs(epsilon - value) < double.Epsilon)
                    return;

                epsilon = value;

                projectManager.SetBlockParameter(projectBlock.Id, "Epsilon", value);
            }
        }

        [Category("Блок")]
        [DisplayName("Название")]
        [Description("Название блока в конвейере обработки.")]
        [ReadOnly(true)]
        public string BlockName => projectBlock.Name;

        [Category("Блок")]
        [DisplayName("Тип")]
        [Description("Внутренний идентификатор типа блока.")]
        [ReadOnly(true)]
        public string BlockType => projectBlock.BlockType;

        [Category("Блок")]
        [DisplayName("Позиция")]
        [Description("Положение блока в конвейере обработки.")]
        [ReadOnly(true)]
        public int Position => projectBlock.Order + 1;

        private static void ValidateSpatialRadius(double value)
        {
            if (double.IsNaN(value) || double.IsInfinity(value) || value < MinimumSpatialRadius || value > MaximumSpatialRadius)
                throw new ArgumentOutOfRangeException(nameof(value), value, $"Пространственный радиус должен находиться в диапазоне от {MinimumSpatialRadius} до {MaximumSpatialRadius}.");
        }

        private static void ValidateColorRadius(double value)
        {
            if (double.IsNaN(value) || double.IsInfinity(value) || value < MinimumColorRadius || value > MaximumColorRadius)
                throw new ArgumentOutOfRangeException(nameof(value), value, $"Цветовой радиус должен находиться в диапазоне от {MinimumColorRadius} до {MaximumColorRadius}.");
        }

        private static void ValidateMaxLevel(int value)
        {
            if (value < MinimumMaxLevel || value > MaximumMaxLevel)
                throw new ArgumentOutOfRangeException(nameof(value), value, $"Максимальный уровень должен находиться в диапазоне от {MinimumMaxLevel} до {MaximumMaxLevel}.");
        }

        private static void ValidateMaxIterations(int value)
        {
            if (value < MinimumIterations || value > MaximumIterations)
                throw new ArgumentOutOfRangeException(nameof(value), value, $"Количество итераций должно находиться в диапазоне от {MinimumIterations} до {MaximumIterations}.");
        }

        private static void ValidateEpsilon(double value)
        {
            if (double.IsNaN(value) || double.IsInfinity(value) || value < MinimumEpsilon || value > MaximumEpsilon)
                throw new ArgumentOutOfRangeException(nameof(value), value, $"Точность должна находиться в диапазоне от {MinimumEpsilon} до {MaximumEpsilon}.");
        }
    }
}