/// <summary>
/// Archivo con la clase encargada de paginar las consultas
/// </summary>
/// <remarks>
/// Autor: Camilo Prieto
/// Fecha: 15/04/2019
/// </remarks>

namespace UtilitariosNet.Clases.Paginador
{
    using System;
    using System.Linq;
    using System.Linq.Expressions;
    using Utilitarios.TransversalesNet.Clases.Paginador.DTO;
    using Utilitarios.TransversalesNet.Clases.Paginador.Enumeraciones;
    using UtilitariosNet.Clases.Comunes;
    using UtilitariosNet.Clases.Comunes.Entidades;
    using UtilitariosNet.Clases.Mapeador.Extensiones;

    public static class Paginacion
    {
        /// <summary>
        /// Realiza un Mapeo entre Objetos a partir de las Propiedades
        /// </summary>
        /// <param name="fuenteDatos">Objecto generado de la consulta</param>
        /// <param name="paginador">Objeto con los parametros de paginación</param>
        /// <returns>Objeto de respuesta con los registros paginados</returns>
        public static RespuestaPaginada<T> Paginar<T>(this IQueryable<T> fuenteDatos, IPaginadorDTO paginador)
        {
            if ((!paginador.EsNulo() && paginador.Registros < 0) || paginador.EsNulo())
            {
                return new RespuestaPaginada<T>
                {
                    Paginador = new Paginador() { TotalRegistros = fuenteDatos.Count() },
                    Entidades = fuenteDatos.ToList()
                };
            }

            if (paginador.TotalRegistros == 0)
            {
                paginador.TotalRegistros = fuenteDatos.Count();
            }

            IOrderedQueryable<T> resultadosOrdenados = null;
            int count = 0;

            foreach (Ordenamiento propiedad in paginador.PropiedadesDeOrdenamiento)
            {
                string nombrePropiedad = propiedad.NombrePropiedad;

                if (nombrePropiedad.Contains('.'))
                {
                    try
                    {
                        string orderby = string.Concat(propiedad.NombrePropiedad, (propiedad.Orden == DireccionOrdenamiento.Desc) ? " DESC" : " ASC");
                        resultadosOrdenados = count.EsCero() ? fuenteDatos.OrderByForeign(orderby) : resultadosOrdenados.OrderByForeign(orderby);
                    }
                    catch
                    {
                        nombrePropiedad = propiedad.NombrePropiedad.Split('.').Last();
                        resultadosOrdenados = count.EsCero() ? fuenteDatos.OrderBy(nombrePropiedad, (propiedad.Orden == DireccionOrdenamiento.Desc)) : resultadosOrdenados.ThenByDescending(nombrePropiedad);
                    }
                }
                else
                {
                    resultadosOrdenados = count.EsCero() ? fuenteDatos.OrderBy(nombrePropiedad, (propiedad.Orden == DireccionOrdenamiento.Desc)) : resultadosOrdenados.ThenByDescending(nombrePropiedad);
                }

                count++;
            }

            var resultadosPaginados = resultadosOrdenados
               .Skip(paginador.Pagina * paginador.Registros)
               .Take(paginador.Registros).ToList();

            return new RespuestaPaginada<T>
            {
                Entidades = resultadosPaginados,
                Paginador = paginador.MapearObjetoPorJson<Paginador>()
            };
        }

        private static IOrderedQueryable<TEntity> OrderByForeign<TEntity>(this IQueryable<TEntity> source, string orderByValues)
        {
            IQueryable<TEntity> returnValue = null;

            string orderPair = orderByValues.Trim().Split(',')[0];
            string command = orderPair.ToUpper().Contains("DESC") ? "OrderByDescending" : "OrderBy";

            var type = typeof(TEntity);
            var parameter = Expression.Parameter(type, "p");

            string propertyName = (orderPair.Split(' ')[0]).Trim();

            System.Reflection.PropertyInfo property;
            MemberExpression propertyAccess;

            if (propertyName.Contains('.'))
            {
                String[] childProperties = propertyName.Split('.');
                property = typeof(TEntity).GetProperty(childProperties[0]);
                propertyAccess = Expression.MakeMemberAccess(parameter, property);

                for (int i = 1; i < childProperties.Length; i++)
                {
                    System.Type t = property.PropertyType;
                    if (!t.IsGenericType)
                    {
                        property = t.GetProperty(childProperties[i]);
                    }
                    else
                    {
                        property = t.GetGenericArguments().First().GetProperty(childProperties[i]);
                    }

                    propertyAccess = Expression.MakeMemberAccess(propertyAccess, property);
                }
            }
            else
            {
                property = type.GetProperty(propertyName);
                propertyAccess = Expression.MakeMemberAccess(parameter, property);
            }

            var orderByExpression = Expression.Lambda(propertyAccess, parameter);

            var resultExpression = Expression.Call(typeof(Queryable), command, new System.Type[] { type, property.PropertyType },

            source.Expression, Expression.Quote(orderByExpression));

            returnValue = source.Provider.CreateQuery<TEntity>(resultExpression);

            if (orderByValues.Trim().Split(',').Count() > 1)
            {
                string newSearchForWords = orderByValues.ToString().Remove(0, orderByValues.ToString().IndexOf(',') + 1);
                return source.OrderBy(newSearchForWords);
            }

            return (IOrderedQueryable<TEntity>)returnValue;
        }

        private static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string propertyName)
        {
            return OrderingHelper(source, propertyName, false, false);
        }

        private static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string propertyName, bool descending)
        {
            return OrderingHelper(source, propertyName, descending, false);
        }

        private static IOrderedQueryable<T> ThenByDescending<T>(this IOrderedQueryable<T> source, string propertyName)
        {
            return OrderingHelper(source, propertyName, true, true);
        }

        private static IOrderedQueryable<T> OrderingHelper<T>(IQueryable<T> source, string propertyName, bool descending, bool anotherLevel)
        {
            ParameterExpression param = Expression.Parameter(typeof(T), string.Empty);

            MemberExpression property = Expression.PropertyOrField(param, propertyName);
            LambdaExpression sort = Expression.Lambda(property, param);

            MethodCallExpression call = Expression.Call(
                typeof(Queryable),
                string.Concat((!anotherLevel ? "OrderBy" : "ThenBy"), (descending ? "Descending" : string.Empty)),
                new[] { typeof(T), property.Type },
                source.Expression,
                Expression.Quote(sort));

            return (IOrderedQueryable<T>)source.Provider.CreateQuery<T>(call);
        }
    }
}