using System;
using System.ComponentModel;

namespace OpenCVStudio_v0
{
    public sealed class ScharrBlockProperties
    {
        private const double MinimumScale = 0.000001;
        private const double MaximumScale = 10000.0;

        private const double MinimumDelta = -1000000.0;
        private const double MaximumDelta = 1000000.0;

        private readonly ProjectManager projectManager;
        private readonly ProjectBlock projectBlock;

        private SobelDirection direction;
        private double scale;
        private double delta;
        private bool convertToGray;
        private RotateBorderMode borderMode;

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

            direction = projectManager.GetBlockParameter(projectBlock.Id, "Direction", SobelDirection.Both);
            scale = projectManager.GetBlockParameter(projectBlock.Id, "Scale", 1.0);
            delta = projectManager.GetBlockParameter(projectBlock.Id, "Delta", 0.0);
            convertToGray = projectManager.GetBlockParameter(projectBlock.Id, "ConvertToGray", true);
            borderMode = projectManager.GetBlockParameter(projectBlock.Id, "BorderMode", RotateBorderMode.Reflect101);
        }

        [Category("Градиент")]
        [DisplayName("Направление")]
        [Description("Направление вычисления градиента. X выделяет вертикальные границы, Y выделяет горизонтальные границы, Both объединяет оба направления.")]
        public SobelDirection Direction
        {
            get => direction;

            set
            {
                if (!Enum.IsDefined(value))
                    throw new ArgumentOutOfRangeException(nameof(value), value, "Неизвестное направление оператора Шарра.");

                if (direction == value)
                    return;

                direction = value;

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

        [Category("Градиент")]
        [DisplayName("Масштаб")]
        [Description("Множитель вычисленного градиента. Стандартное значение — 1.")]
        public double Scale
        {
            get => scale;

            set
            {
                ValidateScale(value);

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

                scale = value;

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

        [Category("Градиент")]
        [DisplayName("Смещение")]
        [Description("Значение, добавляемое к вычисленному градиенту. Обычно используется значение 0.")]
        public double Delta
        {
            get => delta;

            set
            {
                ValidateDelta(value);

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

                delta = value;

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

        [Category("Входное изображение")]
        [DisplayName("Преобразовать в серый")]
        [Description("Если включено, цветное изображение автоматически преобразуется в оттенки серого перед вычислением градиента.")]
        public bool ConvertToGray
        {
            get => convertToGray;

            set
            {
                if (convertToGray == value)
                    return;

                convertToGray = value;

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

        [Category("Границы изображения")]
        [DisplayName("Обработка границ")]
        [Description("Определяет способ вычисления отсутствующих пикселей за пределами изображения.")]
        public RotateBorderMode BorderMode
        {
            get => borderMode;

            set
            {
                if (!Enum.IsDefined(value))
                    throw new ArgumentOutOfRangeException(nameof(value), value, "Неизвестный режим обработки границ.");

                if (borderMode == value)
                    return;

                borderMode = value;

                projectManager.SetBlockParameter(projectBlock.Id, "BorderMode", 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 ValidateScale(double value)
        {
            if (double.IsNaN(value) || double.IsInfinity(value) || value < MinimumScale || value > MaximumScale)
                throw new ArgumentOutOfRangeException(nameof(value), value, $"Масштаб должен находиться в диапазоне от {MinimumScale} до {MaximumScale}.");
        }

        private static void ValidateDelta(double value)
        {
            if (double.IsNaN(value) || double.IsInfinity(value) || value < MinimumDelta || value > MaximumDelta)
                throw new ArgumentOutOfRangeException(nameof(value), value, $"Смещение должно находиться в диапазоне от {MinimumDelta} до {MaximumDelta}.");
        }
    }
}