using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Collections;
using System.Linq.Expressions;

namespace LINQ_to_Random
{
    //class Query<T> : IEnumerable, IEnumerable<T>, IQueryable, IOrderedQueryable
    class Query<T> : IOrderedQueryable<T>
    {
        IQueryProvider źródło = null;
        Expression wyrażenie = null;

        public Query(IQueryProvider źródło)
        {
            if (źródło == null) throw new ArgumentNullException("źródło");
            this.źródło = źródło;
            wyrażenie = Expression.Constant(this);
        }

        public Query(IQueryProvider źródło, Expression wyrażenie)
        {
            if (źródło == null) throw new ArgumentNullException("źródło");
            this.źródło = źródło;
            if (wyrażenie == null) throw new ArgumentNullException("wyrażenie");
            if (!typeof(IQueryable<T>).IsAssignableFrom(wyrażenie.Type))
                throw new ArgumentOutOfRangeException("wyrażenie");
            this.wyrażenie = wyrażenie;
        }

        #region IEnumerable,IEnumerable<T>
        public IEnumerator<T> GetEnumerator()
        {
            return ((IEnumerable<T>)źródło.Execute(wyrażenie)).GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return ((IEnumerable)źródło.Execute(wyrażenie)).GetEnumerator();
        }
        #endregion

        Expression IQueryable.Expression
        {
            get { return this.wyrażenie; }
        }

        Type IQueryable.ElementType
        {
            get { return typeof(T); }
        }

        IQueryProvider IQueryable.Provider
        {
            get { return źródło; }
        }
    }

    class GeneratorLiczbLosowych<T> : IQueryProvider
    {
        int size = 0;

        public GeneratorLiczbLosowych(int size)
        {
            if (size < 1) throw new Exception("Rozmiar musi być liczbą większą od zera");
            else this.size = size;
        }

        /*
        public IEnumerable<T> Execute(Expression expression)
        {
            AnalizatorZapytania tłumaczZapytania = new AnalizatorZapytania(expression);

            bool czySortowanie = tłumaczZapytania.czySortowanie;

            if (typeof(T) == typeof(double)) return (IEnumerable<T>)ExecuteDouble();
            if (typeof(T) == typeof(int)) return (IEnumerable<T>)ExecuteInt();

            throw new Exception("Parametry typu innego niż double nie są obsługiwane");
        }
        */

        public IEnumerable<T> Execute(Expression expression)
        {
            AnalizatorZapytania tłumaczZapytania = new AnalizatorZapytania(expression);

            bool czySortowanie = tłumaczZapytania.czySortowanie;

            //domyślne parametry filtru
            double limitDolny = 0;
            double limitGórny = 0;
            if (typeof(T) == typeof(double)) limitGórny = 1.0;
            if (typeof(T) == typeof(int)) limitGórny = int.MaxValue;

            if (tłumaczZapytania.czyLimitDolny) limitDolny = tłumaczZapytania.limitDolny;
            if (tłumaczZapytania.czyLimitGórny) limitGórny = tłumaczZapytania.limitGórny;

            System.Windows.Forms.MessageBox.Show("Pobierane będą liczby z zakresu od " + limitDolny + " do " + limitGórny);

            double zakres = limitGórny - limitDolny;

            IEnumerable<T> zbiórLiczbLosowych = null;
            if (typeof(T) == typeof(double))
                zbiórLiczbLosowych = (IEnumerable<T>)ExecuteDouble(czySortowanie, limitDolny, zakres);
            if (typeof(T) == typeof(int))
                zbiórLiczbLosowych = (IEnumerable<T>)ExecuteInt(czySortowanie, (int)limitDolny, (int)zakres);

            if (zbiórLiczbLosowych == null)
                throw new Exception("Parametry innego typu niż double lub int nie są obsługiwane");

            return zbiórLiczbLosowych;
        }

        private List<double> ExecuteDouble(bool czySortowanie, double min, double zakres)
        {
            Random random = new Random();
            List<double> lista = new List<double>(size);
            for (int i = 0; i < size; i++)
            {
                double liczba = min + zakres * random.NextDouble();
                lista.Add(liczba);
            }
            if (czySortowanie) lista.Sort();
            return lista;
        }

        private List<int> ExecuteInt(bool czySortowanie, int min, int zakres)
        {
            Random random = new Random();
            List<int> lista = new List<int>(size);
            for (int i = 0; i < size; i++)
            {
                int liczba = min + random.Next(zakres);
                lista.Add(liczba);
            }
            if (czySortowanie) lista.Sort();
            return lista;
        }

        #region IQueryProvider
        IQueryable IQueryProvider.CreateQuery(Expression expression)
        {
            return new Query<T>(this, expression);
        }

        IQueryable<S> IQueryProvider.CreateQuery<S>(Expression expression)
        {
            return (IQueryable<S>)(new Query<T>(this, expression));
        }

        S IQueryProvider.Execute<S>(Expression expression)
        {
            return (S)this.Execute(expression);
        }

        object IQueryProvider.Execute(Expression expression)
        {
            return this.Execute(expression);
        }
        #endregion        
    }

    class AnalizatorZapytania : ExpressionVisitor
    {
        private StringBuilder bufor = null;
        private StringBuilder buforFiltrowania = new StringBuilder();

        public Type typ = null;

        public bool czyLimitDolny = false;
        public bool czyLimitGórny = false;
        public double limitDolny = 0;
        public double limitGórny = 1.0;
        public bool czySortowanie = false;

        public AnalizatorZapytania(Expression expression)
        {
            this.Visit(expression);

            System.Windows.Forms.MessageBox.Show("Wynik analizy funkcji Where: " + buforFiltrowania.ToString());

            string tmp = buforFiltrowania.ToString();

            int pozycja = tmp.IndexOf('>');
            czyLimitDolny = (pozycja >= 0);
            if (czyLimitDolny)
            {
                int koniec = tmp.Substring(pozycja + 1).IndexOf(' ');
                if (koniec < 0) koniec = pozycja + tmp.Substring(pozycja + 1).Length;
                string limitDolnyStr = tmp.Substring(pozycja + 1, koniec - pozycja);
                limitDolny = double.Parse(limitDolnyStr);
            }

            pozycja = tmp.IndexOf('<');
            czyLimitGórny = (pozycja >= 0);
            if (czyLimitGórny)
            {
                int koniec = tmp.Substring(pozycja + 1).IndexOf(' ');
                if (koniec < 0) koniec = pozycja + tmp.Substring(pozycja + 1).Length;
                string limitGórnyStr = tmp.Substring(pozycja + 1, koniec - pozycja);
                limitGórny = double.Parse(limitGórnyStr);
            }
        }

        private static Expression StripQuotes(Expression e)
        {
            while (e.NodeType == ExpressionType.Quote)
            {
                e = ((UnaryExpression)e).Operand;
            }
            return e;
        }

        protected override Expression VisitMethodCall(MethodCallExpression m)
        {
            bufor = null;

            switch (m.Method.Name)
            {
                case "Where": bufor = buforFiltrowania; break;
                case "OrderBy": czySortowanie = true; break;
                case "Select": break;
                default:
                    throw new NotSupportedException("Metoda " + m.Method.Name + " nie jest obsługiwana");
            }

            if (m.Method.DeclaringType == typeof(Queryable))
            {
                this.Visit(m.Arguments[0]);
                LambdaExpression lambda = (LambdaExpression)StripQuotes(m.Arguments[1]);
                this.Visit(lambda.Body);
                return m;
            }
            throw new NotSupportedException("Metoda " + m.Method.Name + " nie jest obsługiwana");
        }

        protected override Expression VisitBinary(BinaryExpression b)
        {
            this.Visit(b.Left);
            switch (b.NodeType)
            {
                case ExpressionType.And:
                case ExpressionType.AndAlso:
                    bufor.Append(" ");
                    break;
                case ExpressionType.LessThan:
                    bufor.Append("<");
                    break;
                case ExpressionType.GreaterThan:
                    bufor.Append(">");
                    break;
                default:
                    throw new NotSupportedException("Operator binarny " + b.NodeType + " nie jest obsługiwany");
            }
            this.Visit(b.Right);
            return b;
        }

        protected override Expression VisitConstant(ConstantExpression c)
        {
            IQueryable q = c.Value as IQueryable;
            if (q != null)
            {
                typ = q.ElementType;
            }
            else if (c.Value == null)
            {
                throw new Exception("Obiekt wyrażenia nie może być null");
            }
            else
            {
                bufor.Append(c.Value);
            }
            return c;
        }
    }
}
