﻿using System;
using System.Linq;
using AppKit.Core.Application.Constants;
using AppKit.Core.Business.Entities;
using AppKit.Core.Requests.Exceptions;
using AppKit.Core.Requests.Filtering.FilterEntries.IntervalFilterEntries;
using AppKit.Core.Requests.Filtering.FilterTypes;

namespace AppKit.Core.Ef6MssqlImpl.Requests
{
    public static class FilteringIntervalExtensions
    {
        public static IQueryable<TObj> TakeFilter<TObj>(this IQueryable<TObj> input, Int32IntervalFilterEntry filterEntry) where TObj : IIdentificationEntity
        {
            if (filterEntry == null)
                throw new NullRequestFilterException();

            return ChooseFilterByIntervalColumn(input, filterEntry.FilterColumn, filterEntry.Interval.Min, filterEntry.Interval.Max, filterEntry.FilterType);
        }

        public static IQueryable<TObj> TakeFilter<TObj>(this IQueryable<TObj> input, Int64IntervalFilterEntry filterEntry) where TObj : IIdentificationEntity
        {
            if (filterEntry == null)
                throw new NullRequestFilterException();

            return ChooseFilterByIntervalColumn(input, filterEntry.FilterColumn, filterEntry.Interval.Min, filterEntry.Interval.Max, filterEntry.FilterType);
        }

        public static IQueryable<TObj> TakeFilter<TObj>(this IQueryable<TObj> input, DecimalIntervalFilterEntry filterEntry) where TObj : IIdentificationEntity
        {
            if (filterEntry == null)
                throw new NullRequestFilterException();

            return ChooseFilterByIntervalColumn(input, filterEntry.FilterColumn, filterEntry.Interval.Min, filterEntry.Interval.Max, filterEntry.FilterType);
        }

        public static IQueryable<TObj> TakeFilter<TObj>(this IQueryable<TObj> input, DateTimeIntervalFilterEntry filterEntry) where TObj : IIdentificationEntity
        {
            if (filterEntry == null)
                throw new NullRequestFilterException();

            return ChooseFilterByIntervalColumn(input, filterEntry.FilterColumn, filterEntry.Interval.Min, filterEntry.Interval.Max, filterEntry.FilterType);
        }

        internal static IQueryable<TObj> ChooseFilterByIntervalColumn<TObj, TVal>(IQueryable<TObj> input, string filterColumn, TVal minValue, TVal maxValue,
            IntervalFilterType filterType) where TObj : IIdentificationEntity
        {
            if (String.IsNullOrEmpty(filterColumn))
                throw new NullRequestFilterColumnException();

            if (filterType == IntervalFilterType.Inside)
                return FilteringUtils.TakeFilterByColumn(input, filterColumn, me => FilteringUtils.CalcInsideIntervalExpression(minValue, maxValue, me), 
                    FilteringUtils.WhereMethodName);
            if (filterType == IntervalFilterType.Outside)
                return FilteringUtils.TakeFilterByColumn(input, filterColumn, me => FilteringUtils.CalcOutsideIntervalExpression(minValue, maxValue, me), 
                    FilteringUtils.WhereMethodName);

            throw new InvalidRequestFilterTypeException((int)filterType);
        }

        public static IQueryable<TObj> TakeFilter<TObj>(this IQueryable<TObj> input, DoubleIntervalFilterEntry filterEntry) where TObj : IIdentificationEntity
        {
            if (filterEntry == null)
                throw new NullRequestFilterException();

            double epsilon = Math.Abs(filterEntry.Interval.Max * GlobalSettingConstants.DoubleEpsilon);
            return ChooseFilterByIntervalFloatingColumn(input, filterEntry.FilterColumn, filterEntry.Interval.Min - epsilon, filterEntry.Interval.Max + epsilon, 
                filterEntry.FilterType);
        }

        public static IQueryable<TObj> TakeFilter<TObj>(this IQueryable<TObj> input, SingleIntervalFilterEntry filterEntry) where TObj : IIdentificationEntity
        {
            if (filterEntry == null)
                throw new NullRequestFilterException();

            float epsilon = Math.Abs(filterEntry.Interval.Max * GlobalSettingConstants.SingleEpsilon);
            return ChooseFilterByIntervalFloatingColumn(input, filterEntry.FilterColumn, filterEntry.Interval.Min - epsilon, filterEntry.Interval.Max + epsilon, 
                filterEntry.FilterType);
        }

        internal static IQueryable<TObj> ChooseFilterByIntervalFloatingColumn<TObj, TVal>(IQueryable<TObj> input, string filterColumn, TVal minValue, TVal maxValue,
            IntervalFilterType filterType) where TObj : IIdentificationEntity
        {
            if (String.IsNullOrEmpty(filterColumn))
                throw new NullRequestFilterColumnException();

            if (filterType == IntervalFilterType.Inside)
                return FilteringUtils.TakeFilterByColumn(input, filterColumn, me => FilteringUtils.CalcInsideIntervalExpression(minValue, maxValue, me), 
                    FilteringUtils.WhereMethodName);
            if (filterType == IntervalFilterType.Outside)
                return FilteringUtils.TakeFilterByColumn(input, filterColumn, me => FilteringUtils.CalcOutsideIntervalExpression(minValue, maxValue, me), 
                    FilteringUtils.WhereMethodName);

            throw new InvalidRequestFilterTypeException((int)filterType);
        }
    }
}
