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

using System.Linq.Expressions;
using System.Reflection;

namespace LinqToTxt
{
    struct ElementAndOr
    {
        public int pierwszaKolumna; //-1 brak, -2 nazwa w stringu, >=0 numer kolumny
        public string nazwaPierwszejKolumny;
        public int drugaKolumna;
        public string nazwaDrugiejKolumny;
        public bool czyAnd;
        public int rodzic;

        public ElementAndOr(int p, int d, bool and, int rodzic)
        {            
            pierwszaKolumna = p;
            nazwaPierwszejKolumny = p.ToString();
            drugaKolumna = d;
            nazwaDrugiejKolumny = string.Empty;
            czyAnd = and;
            this.rodzic = rodzic;
        }

        public ElementAndOr(string p, bool and, int rodzic)
        {
            pierwszaKolumna = -2;
            nazwaPierwszejKolumny = p;
            czyAnd = and;
            this.rodzic = rodzic;
            drugaKolumna = -1;
            nazwaDrugiejKolumny = string.Empty;
        }
    }

    class DaneFiltrowania
    {
        private string kolumna;
        private string wyrażenieRegularne;
        private double liczba;
        private string nazwaWłasności;

        /// <summary>
        /// get,set Nazwa kolumny w bazie danych.
        /// </summary>
        public string NazwaKolumny
        {
            get { return kolumna; }
            set { this.kolumna = value; }
        }

        /// <summary>
        /// get Nazwa własności klasy odpowiadającej danej tabeli.
        /// </summary>
        public string NazwaWłasności
        {
            get { return nazwaWłasności; }
        }

        /// <summary>
        /// get,set Liczba do warunku where dla kolumn typu int, double.
        /// </summary>
        public double Liczba
        {
            get { return liczba; }
            set { this.liczba = value; }
        }

        /// <summary>
        /// get,set Wyrażenie regularne. Co trzeba zrobić w czasie filtracji z daną kolumną.
        /// </summary>
        public string WyrażenieRegularne
        {
            get { return wyrażenieRegularne; }
            set { this.wyrażenieRegularne = value; }
        }

        public DaneFiltrowania(string właściwość, string wyrażenie, double liczba)
        {

            this.kolumna = String.Empty;
            this.wyrażenieRegularne = wyrażenie;
            this.liczba = liczba;
            this.nazwaWłasności = właściwość;
        }

        public void ZmieńWyrażenie(string przed, string po)
        {
            wyrażenieRegularne = przed + wyrażenieRegularne + po;
        }
    }

    class AnalizatorZapytania : ExpressionVisitor
    {
        private bool czyWhere = false;
        private bool czyOrder = false;

        private Type typWZapytaniu; 
        private int poziom = 0;
        private int krok = 0;
        private int l = 0;
        private int numerRodzica = 0;

        public List<DaneFiltrowania> filtracja = new List<DaneFiltrowania>();

        public int sortowanie = -1; //numer kolumny
        public string sortowanieNazwa = string.Empty;

        public List<ElementAndOr> warunkiAndOr = new List<ElementAndOr>();

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

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

        protected override Expression VisitMethodCall(MethodCallExpression m)
        {
            switch (m.Method.Name)
            {
                case "Where": czyWhere = true; break;
                case "Select": break;
                case "OrderBy": czyOrder = true; break;
                case "get_Item": //występuję tylko dla typy List<String>
                    if (czyWhere) return this.ParamentrWhere(m);
                    else if (czyOrder) return this.ParamentrOrder(m);
                    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);
                switch (m.Method.Name)
                {
                    case "Where": czyWhere = false; break;
                    case "OrderBy": czyOrder = false; break;
                    default: break;
                }
                return m;
            }

            throw new NotSupportedException("Metoda " + m.Method.Name + " nie jest obsługiwana.");
        }

        protected override Expression VisitBinary(BinaryExpression b)
        {
            int tmp = 0;
            int ja = -2;
            bool warunekAndOr = b.NodeType == ExpressionType.And ||
                b.NodeType == ExpressionType.AndAlso ||
                b.NodeType == ExpressionType.Or ||
                b.NodeType == ExpressionType.OrElse;
            if (warunekAndOr)
            {
                tmp = poziom - l ;
                l = 0;
                krok++;
                poziom++;
                ja = poziom;
                numerRodzica = ja;
            }
            this.Visit(b.Left);
            if (warunekAndOr)
            {
                if (poziom != krok) l = poziom - krok;
                else l = 0;
                numerRodzica = ja;
            }
            this.Visit(b.Right);

            switch (b.NodeType)
            {
                case ExpressionType.And:
                case ExpressionType.AndAlso:
                    int pierAnd = warunkiAndOr.FindIndex(c => c.rodzic == ja);
                    warunkiAndOr.Add(new ElementAndOr(pierAnd, warunkiAndOr.FindIndex(pierAnd + 1, c => c.rodzic == ja), true, tmp));
                    break; 
                case ExpressionType.Or:
                case ExpressionType.OrElse:
                    int pierOr = warunkiAndOr.FindIndex(c => c.rodzic == ja);
                    warunkiAndOr.Add(new ElementAndOr(pierOr, warunkiAndOr.FindIndex(pierOr + 1, c => c.rodzic == ja), false, tmp));
                    break;
                case ExpressionType.LessThan:
                    filtracja[filtracja.Count - 1].ZmieńWyrażenie("[<]", string.Empty);
                    break;
                case ExpressionType.LessThanOrEqual:
                    filtracja[filtracja.Count - 1].ZmieńWyrażenie("[=<]", string.Empty);
                    break;
                case ExpressionType.GreaterThan:
                    filtracja[filtracja.Count - 1].ZmieńWyrażenie("[>]", string.Empty);
                    break;
                case ExpressionType.GreaterThanOrEqual:
                    filtracja[filtracja.Count - 1].ZmieńWyrażenie("[>=]", string.Empty);
                    break;
                case ExpressionType.Equal:
                    if (!filtracja[filtracja.Count - 1].WyrażenieRegularne.StartsWith("[==]"))
                        filtracja[filtracja.Count - 1].ZmieńWyrażenie("[=]", string.Empty);
                    break;
                case ExpressionType.NotEqual:
                    if (!filtracja[filtracja.Count - 1].WyrażenieRegularne.StartsWith("[==]"))
                        filtracja[filtracja.Count - 1].ZmieńWyrażenie("[!=]", string.Empty);
                    else
                        filtracja[filtracja.Count - 1].WyrażenieRegularne = filtracja[filtracja.Count - 1].WyrażenieRegularne.Replace("[==]", "[!==]");// ZmieńWyrażenie("[!=]", string.Empty); 
                    break;
                default: throw new NotSupportedException("Operant Binarny " + b.NodeType + "nie jest obsługiwany");
            }
            if (warunekAndOr) krok--;
            return b;
        }

        private Expression ParamentrOrder(MethodCallExpression m)
        {
            sortowanie = (int)((ConstantExpression)m.Arguments[0]).Value;
            return m;
        }

        private Expression ParamentrWhere(MethodCallExpression m)
        {
            filtracja.Add(
                new DaneFiltrowania(
                    ((ConstantExpression)m.Arguments[0]).Value.ToString(), 
                    string.Empty, 0.0));
            warunkiAndOr.Add(
                new ElementAndOr(
                    (int)((ConstantExpression)m.Arguments[0]).Value, 
                    -1, 
                    false, 
                    poziom));
            return m;
        }

        protected override Expression VisitConstant(ConstantExpression c)
        {
            PrzypiszStala(c.Type.Name, c.Value);
            return c;
        }

        protected override Expression VisitMember(MemberExpression m)
        {
            if (m.Member.DeclaringType == typWZapytaniu)
            {
                if (czyWhere)
                {
                    filtracja.Add(new DaneFiltrowania(m.Member.Name, string.Empty, 0.0));
                    warunkiAndOr.Add(new ElementAndOr(m.Member.Name, false, numerRodzica));
                    return m;
                }
                else if (czyOrder)
                {
                    sortowanieNazwa = m.Member.Name;
                    return m;
                }
            }
            else
            {
                switch (m.Member.MemberType)
                {
                    case MemberTypes.Property:
                        PropertyInfo outerProp = (PropertyInfo)m.Member;
                        MemberExpression innerMember = (MemberExpression)m.Expression;
                        FieldInfo innerFieldProperty = (FieldInfo)innerMember.Member;
                        ConstantExpression constExpressionProperty = (ConstantExpression)innerMember.Expression;
                        object outerObj = innerFieldProperty.GetValue(constExpressionProperty.Value);
                        object valueProperty = outerProp.GetValue(outerObj, null);
                        PrzypiszStala(outerProp.PropertyType.Name, valueProperty);
                        break;
                    case MemberTypes.Field:
                        FieldInfo innerFieldField = (FieldInfo)m.Member;
                        ConstantExpression constExpressionField = (ConstantExpression)m.Expression;
                        object innerObj = constExpressionField.Value;
                        object valueField = innerFieldField.GetValue(innerObj);
                        PrzypiszStala(innerFieldField.FieldType.Name, valueField);
                        break;
                    default: break;
                }
            }
            return m;
        }

        private void PrzypiszStala(string typeName, object value)
        {
            switch (typeName)
            {
                case "String":
                    filtracja[filtracja.Count - 1].WyrażenieRegularne = "[==]" + value.ToString();
                    break;
                case "Int32":
                    filtracja[filtracja.Count - 1].Liczba = Convert.ToDouble(value);
                    break;
                case "Double":
                    filtracja[filtracja.Count - 1].Liczba = (double)value;
                    break;
                case "Query`1":
                    typWZapytaniu = ((System.Linq.IQueryable)value).ElementType; //nowe
                    break;
            }
        }
    }
}