// Copyright (C) 2008-2020 Xtensive LLC.
// This code is distributed under MIT license terms.
// See the License.txt file in the project root for more information.
// Created by: Alex Kofman
// Created:    2008.07.25

using System;
using System.Collections.Generic;
using Xtensive.Comparison;
using Xtensive.Orm.Model;
using Xtensive.Reflection;

namespace Xtensive.Orm.Validation
{
  /// <summary>
  /// Ensures field value fits in the specified range.
  /// </summary>
  public sealed class RangeConstraint : PropertyValidator
  {
    private abstract class ValidationHandler
    {
      private static readonly Type ValidationHandlerType = typeof(ValidationHandler<>);

      protected abstract void Configure(object min, object max);

      public abstract bool Validate(object value);

      public static ValidationHandler Create(Type valueType, object min, object max)
      {
        var validatorType = ValidationHandlerType.CachedMakeGenericType(valueType);
        var result = (ValidationHandler) Activator.CreateInstance(validatorType);
        result.Configure(min, max);
        return result;
      }
    }

    private sealed class ValidationHandler<TValue> : ValidationHandler
    {
      private static readonly IComparer<TValue> Comparer = Comparer<TValue>.Default;

      private bool hasMin;
      private bool hasMax;

      private TValue min;
      private TValue max;

      protected override void Configure(object minObj, object maxObj)
      {
        if (minObj!=null) {
          min = (TValue) Convert.ChangeType(minObj, typeof (TValue));
          hasMin = true;
        }

        if (maxObj!=null) {
          max = (TValue) Convert.ChangeType(maxObj, typeof (TValue));
          hasMax = true;
        }
      }

      public override bool Validate(object value)
      {
        var typedValue = (TValue) value;

        return (!hasMin || Comparer.Compare(typedValue, min) >= 0)
          && (!hasMax || Comparer.Compare(typedValue, max) <= 0);
      }
    }

    private ValidationHandler handler;

    /// <summary>
    /// Gets or sets the minimal allowed value.
    /// <see langword="null" /> means "ignore this boundary".
    /// Default value is <see langword="null" />.
    /// </summary>
    public object Min { get; set; }

    /// <summary>
    /// Gets or sets the maximal allowed value.
    /// <see langword="null" /> means "ignore this boundary".
    /// Default value is <see langword="null" />.
    /// </summary>
    public object Max { get; set; }

    public override void Configure(Domain domain, TypeInfo type, FieldInfo field)
    {
      base.Configure(domain, type, field);

      if (Min==null && Max==null)
        ThrowConfigurationError(Strings.MaxOrMinPropertyShouldBeSpecified);

      if (!field.ValueType.IsOfGenericInterface(WellKnownInterfaces.ComparableOfT))
        ThrowConfigurationError(Strings.FieldShouldBeOfComparableType);

      handler = ValidationHandler.Create(field.ValueType, Min, Max);
    }

    public override ValidationResult Validate(Entity target, object fieldValue)
    {
      if (handler.Validate(fieldValue))
        return Success();
      if (Min==null)
        return Error(string.Format(Strings.ValueShouldNotBeGreaterThanMax, Max), fieldValue);
      if (Max==null)
        return Error(string.Format(Strings.ValueShouldNotBeLessThanMin, Min), fieldValue);
      return Error(string.Format(Strings.ValueShouldNotBeLessThanMinOrGreaterThanMax, Min, Max), fieldValue);
    }

    public override IPropertyValidator CreateNew()
    {
      return new RangeConstraint {
        IsImmediate = IsImmediate,
        Min = Min,
        Max = Max,
        SkipOnTransactionCommit = SkipOnTransactionCommit,
        ValidateOnlyIfModified = ValidateOnlyIfModified
      };
    }
  }
}