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

using Helion;
using System.Linq.Expressions;
using System.Data.Linq.Mapping;
using System.Reflection;
using System.Text.RegularExpressions;

namespace LinqToTxt
{
    public class ZrodloLinqToTxt<T> : IQueryProvider
    {
        private PlikTekstowy dane;
        private Dictionary<Expression, List<int>> numeryWybranychWierszy = new Dictionary<Expression, List<int>>();
        public Dictionary<Expression, List<int>> NumeryWybranychWierszy { get { return numeryWybranychWierszy; } }

        /*
        private string SprawdźEncjeIntefejs(string ścieżka)
        {
            if (!System.IO.Directory.Exists(ścieżka))
                throw new NotSupportedException("Ścieżka do bazy danych musi wskazywać katalog.");
            T temp = (T)typeof(T).GetConstructor(new Type[] { }).Invoke(null);
            if (temp == null || (string)typeof(T).GetProperty("NazwaTabeli").GetValue(temp, null) == string.Empty)
                throw new NotSupportedException("Klasa nie posiada własności z nazwą tabeli, lub pole to jest puste.");
            return System.IO.Path.Combine(ścieżka, (string)typeof(T).GetProperty("NazwaTabeli").GetValue(temp, null)+".txt");
        }

        private string SprawdźEncjeAtrybuty(string ścieżka)
        {
            if (!System.IO.Directory.Exists(ścieżka))
                throw new NotSupportedException("Ścieżka do bazy danych musi wskazywać katalog.");
            if ((typeof(T).GetCustomAttributes(true)[0] as TableAttribute) == null)
                throw new NotSupportedException("Klasa nie posiada atrybutu tabeli. Patrz przestrzeń nazw System.Data.Linq.Mapping.");
            if ((typeof(T).GetCustomAttributes(true)[0] as TableAttribute).Name == string.Empty)
                throw new NotSupportedException("Atrybut tableli nie ma wypełnionego pola Name.");
            return System.IO.Path.Combine(ścieżka, (typeof(T).GetCustomAttributes(true)[0] as TableAttribute).Name + ".txt");
        }
        */

        public ZrodloLinqToTxt(string ścieżka, string znakKomentarza = "#", string znakiSeparacji = "\t;")
        {
            /*
            if (typeof(T) != typeof(List<string>))
            {
                if (typeof(ILinqToTxt).IsAssignableFrom(typeof(T)))
                    ścieżka = SprawdźEncjeIntefejs(ścieżka);
                else
                    ścieżka = SprawdźEncjeAtrybuty(ścieżka);
            }
            */
            dane = new PlikTekstowy(ścieżka, znakKomentarza, znakiSeparacji);
        }

        public IEnumerable<T> Execute(Expression expression)
        {
            if (typeof(T) == typeof(List<string>))
                return (IEnumerable<T>)ExecuteLista(expression);
            if (typeof(ILinqToTxt).IsAssignableFrom(typeof(T)))
                return (IEnumerable<T>)ExecuteInterfejs(expression);
            return (IEnumerable<T>)ExecuteAtrybuty(expression);
        }

        /// <summary>
        /// Execute dla encji której typ to List(string).
        /// </summary>
        /// <param name="expression">Wyrażenie expression.</param>
        /// <returns>Liste wyników po analizie wyrażenia.</returns>
        private List<List<string>> ExecuteLista(Expression expression)
        {
            AnalizatorZapytania analizator = new AnalizatorZapytania(expression);

            List<List<string>> wynik = this.PobierzDane(analizator, true, expression);

            if (analizator.sortowanie != -1)
                wynik = PlikTekstowy.Sortuj((c, b) => c.CompareTo(b), analizator.sortowanie, wynik);

            return wynik;
        }

        /// <summary>
        /// Execute dla encji implementującej interfejs ILinqToTXT.
        /// </summary>
        /// <param name="expression">Wyrażenie expression.</param>
        /// <returns>Liste wyników po analizie wyrażenia.</returns>
        private List<T> ExecuteInterfejs(Expression expression)
        {
            if (typeof(T).GetConstructor(new Type[] { }).Invoke(null) == null)
                throw new NullReferenceException("Klasa encji musi posiadać bezargumentowy konstruktor.");

            AnalizatorZapytania analizator = new AnalizatorZapytania(expression);

            #region Mapowanie własności
            List<string> nazwyKolumn = dane.NazwyKolumn;
            //podaje nazwe kolumny w bazie a otrzymuję nazwe wlasnosci w encji
            Dictionary<string, string> kolumnyWłasności = new Dictionary<string, string>();
            //podaje nazwe wlasnosci w encji a otrzymuję nazwe kolumny w bazie
            Dictionary<string, string> własnościKolumny = new Dictionary<string, string>();

            T obiekt = (T)typeof(T).GetConstructor(new Type[] { }).Invoke(null);
            PropertyInfo[] wlasnosci = typeof(T).GetProperties();
            for (int i = 0; i < wlasnosci.Length; i++)
            {
                if (!(((ILinqToTxt)obiekt).MapowanieKolumn(wlasnosci[i].Name) == string.Empty))
                {
                    kolumnyWłasności.Add(((ILinqToTxt)obiekt).MapowanieKolumn(wlasnosci[i].Name), wlasnosci[i].Name);
                    własnościKolumny.Add(wlasnosci[i].Name, ((ILinqToTxt)obiekt).MapowanieKolumn(wlasnosci[i].Name));
                }
            }

            for (int i = 0; i < analizator.filtracja.Count; i++)
                analizator.filtracja[i].NazwaKolumny = własnościKolumny[analizator.filtracja[i].NazwaWłasności];
            #endregion

            #region Pobieranie danych
            List<List<string>> wynik = PobierzDane(analizator, false, expression);
            #endregion

            #region Sortowanie danych
            if (analizator.sortowanieNazwa != string.Empty)
                if (własnościKolumny.ContainsKey(analizator.sortowanieNazwa))
                    wynik = dane.Sortuj((c, b) => c.CompareTo(b), własnościKolumny[analizator.sortowanieNazwa], wynik);
            #endregion

            #region Umieszczanie danych w encjach
            List<T> listaWierszy = new List<T>();

            for (int i = 0; i < wynik.Count; i++)
            {
                listaWierszy.Add((T)typeof(T).GetConstructor(new Type[] { }).Invoke(null));
                for (int j = 0; j < wynik[i].Count; j++)
                {
                    ((ILinqToTxt)listaWierszy[i]).UstawWartoscPola(nazwyKolumn[j], wynik[i][j]);
                }
            }
            #endregion

            return listaWierszy;
        }

        /// <summary>
        /// Execute dla klasy encji wykorzystującej atrybuty mapowania LINQ.
        /// </summary>
        /// <param name="expression">Wyrażenie expression.</param>
        /// <returns>Liste wyników po analizie wyrażenia.</returns>
        private List<T> ExecuteAtrybuty(Expression expression)
        {
            if (typeof(T).GetConstructor(new Type[] { }).Invoke(null) == null)
                throw new NullReferenceException("Klasa encji musi posiadać bezargumentowy konstruktor.");

            AnalizatorZapytania analizator = new AnalizatorZapytania(expression);

            #region Mapowanie własności
            List<string> nazwyKolumn = dane.NazwyKolumn;
            int długośćNagłówka = nazwyKolumn.Count;
            //podaje nazwe kolumny w bazie a otrzymuję nazwe własności w klasie encji
            Dictionary<string, string> kolumnyWłasności = new Dictionary<string, string>(długośćNagłówka);
            //podaje nazwe własności w klasie encji a otrzymuję nazwe kolumny w bazie
            Dictionary<string, string> własnościKolumny = new Dictionary<string, string>(długośćNagłówka);

            bool czyObecneAtrybutyKolumn = false;

            PropertyInfo[] wlasnosci = typeof(T).GetProperties();
            for (int i = 0; i < wlasnosci.Length; i++)
            {
                ColumnAttribute atrybut = wlasnosci[i].GetCustomAttributes(true)[0] as ColumnAttribute;
                if (atrybut != null)
                {
                    if (!nazwyKolumn.Exists(c => c == atrybut.Name ? true : false))
                        throw new NotSupportedException("Klasa mapuje własność " + wlasnosci[i].Name + " na kolumne : " + atrybut.Name + " której nie ma w tabeli.");
                    kolumnyWłasności.Add(atrybut.Name, wlasnosci[i].Name);
                    własnościKolumny.Add(wlasnosci[i].Name, atrybut.Name);
                    czyObecneAtrybutyKolumn = true;
                }
            }
            if (!czyObecneAtrybutyKolumn) throw new NotSupportedException("Żadne pole lub własność nie ma zdefiniowanego atrybutu kolumny.");
            for (int i = 0; i < analizator.filtracja.Count; i++)
                analizator.filtracja[i].NazwaKolumny = własnościKolumny[analizator.filtracja[i].NazwaWłasności];
            #endregion

            #region Pobieranie danych
            List<List<string>> wynik = PobierzDane(analizator, false, expression);
            #endregion

            #region Sortowanie danych
            if (analizator.sortowanieNazwa != string.Empty)
                if (własnościKolumny.ContainsKey(analizator.sortowanieNazwa))
                    wynik = dane.Sortuj((c, b) => c.CompareTo(b), własnościKolumny[analizator.sortowanieNazwa], wynik);
            #endregion

            #region Umieszczanie danych w encjach
            List<T> listaWierszy = new List<T>();
            for (int i = 0; i < wynik.Count; i++)
            {
                listaWierszy.Add((T)typeof(T).GetConstructor(new Type[] { }).Invoke(null));
                for (int j = 0; j < wynik[i].Count; j++)
                {
                    if (kolumnyWłasności.ContainsKey(nazwyKolumn[j]))
                    {                        
                        switch (typeof(T).GetProperty(kolumnyWłasności[nazwyKolumn[j]]).PropertyType.Name) //typ własności klasy encji
                        {
                            case "String":
                                typeof(T).GetProperty(kolumnyWłasności[nazwyKolumn[j]]).SetValue(listaWierszy[i], wynik[i][j], null);
                                break;
                            case "Int16":
                                Int16 tmp16;
                                if (Int16.TryParse(wynik[i][j], out tmp16))
                                    typeof(T).GetProperty(kolumnyWłasności[nazwyKolumn[j]]).SetValue(listaWierszy[i], Int16.Parse(wynik[i][j]), null);
                                else
                                    typeof(T).GetProperty(kolumnyWłasności[nazwyKolumn[j]]).SetValue(listaWierszy[i], Int16.MinValue, null);
                                break;
                            case "Int32":
                                Int32 tmp32;
                                if (Int32.TryParse(wynik[i][j], out tmp32))
                                    typeof(T).GetProperty(kolumnyWłasności[nazwyKolumn[j]]).SetValue(listaWierszy[i], Int32.Parse(wynik[i][j]), null);
                                else
                                    typeof(T).GetProperty(kolumnyWłasności[nazwyKolumn[j]]).SetValue(listaWierszy[i], Int32.MinValue, null);
                                break;
                            case "Int64":
                                Int64 tmp64;
                                if (Int64.TryParse(wynik[i][j], out tmp64))
                                    typeof(T).GetProperty(kolumnyWłasności[nazwyKolumn[j]]).SetValue(listaWierszy[i], Int64.Parse(wynik[i][j]), null);
                                else
                                    typeof(T).GetProperty(kolumnyWłasności[nazwyKolumn[j]]).SetValue(listaWierszy[i], Int64.MinValue, null);
                                break;
                            case "Double":
                                Double tmpD;
                                if (Double.TryParse(wynik[i][j], out tmpD))
                                    typeof(T).GetProperty(kolumnyWłasności[nazwyKolumn[j]]).SetValue(listaWierszy[i], Double.Parse(wynik[i][j]), null);
                                else
                                    typeof(T).GetProperty(kolumnyWłasności[nazwyKolumn[j]]).SetValue(listaWierszy[i], Double.MinValue, null);
                                break;
                            default: throw new NotSupportedException("Typ własności: " + typeof(T).GetProperty(kolumnyWłasności[nazwyKolumn[j]]).PropertyType.Name + " nie jest obsługiwany. Obsługiwane formaty to String, Int16, Int32, Int64, Double"); //może nic nie robić
                        }
                    }
                }
            }
            #endregion

            return listaWierszy;
        }

        /// <summary>
        /// Niweluje powtarzające się wiersze w tabeli.
        /// </summary>
        /// <param name="tabela">Tabela w której chcemy pozbyć się powtórzeń.</param>
        /// <returns>Tabela bez powtórzeń.</returns>
        private List<List<string>> Distinct(List<List<string>> tabela)
        {
            Dictionary<string, int> tmp = new Dictionary<string, int>();
            List<List<string>> wynik = new List<List<string>>();
            for (int i = 0; i < tabela.Count; i++)
            {
                string klucz = string.Empty;
                for (int j = 0; j < tabela[i].Count; j++)
                    klucz += tabela[i][j];
                if (!tmp.ContainsKey(klucz))
                {
                    wynik.Add(tabela[i]);
                    tmp.Add(klucz, i);
                }
            }
            return wynik;
        }

        /// <summary>
        /// Wykonuję Or na dwóch tabelach, zapisanych jedna pod drugą w tabeli zbiorczej.
        /// </summary>
        /// <param name="tabela">Tabela zbiorcza, połączone dwie tabele.</param>
        /// <returns>Tabele po operacji Or.</returns>
        private List<List<string>> WarunekOr(List<List<string>> tabela)
        {
            return this.Distinct(tabela);
        }

        private List<int> WarunekOr(List<int> pierwsza, List<int> druga)
        {
            pierwsza.Sort();
            druga.Sort();
            List<int> wynik = new List<int>();

            wynik.AddRange(pierwsza);

            for (int j = 0; j < druga.Count; j++)
            {
                if (!wynik.Exists(c => c == druga[j] ? true : false))
                    wynik.Add(druga[j]);
            }
            return wynik;
        }

        /// <summary>
        /// Wykonuję And na dwóch tabelach, zapisanych jedna pod drugą w tabeli zbiorczej.
        /// </summary>
        /// <param name="tabela">Tabela zbiorcza, połączone dwie tabele.</param>
        /// <param name="wielkośćPierwszejTabeli">Wielkość pierwszej tabeli.</param>
        /// <returns>Tabele po operacji AND.</returns>
        private List<List<string>> WarunekAnd(List<List<string>> tabela, int wielkośćPierwszejTabeli)
        {
            Dictionary<string, int> tmp = new Dictionary<string, int>();
            List<List<string>> wynik = new List<List<string>>();
            for (int i = 0; i < wielkośćPierwszejTabeli; i++)
            {
                string klucz = string.Empty;
                for (int j = 0; j < tabela[i].Count; j++)
                    klucz += tabela[i][j];
                tmp.Add(klucz, i);
            }
            for (int i = wielkośćPierwszejTabeli; i < tabela.Count; i++)
            {
                string klucz = string.Empty;
                for (int j = 0; j < tabela[i].Count; j++)
                    klucz += tabela[i][j];
                if (tmp.ContainsKey(klucz))
                {
                    if (tmp[klucz] < wielkośćPierwszejTabeli)
                        wynik.Add(tabela[i]);
                }
                else tmp.Add(klucz, i);
            }
            return wynik;
        }

        private List<int> WarunekAnd(List<int> pierwsza, List<int> druga)
        {
            pierwsza.Sort();
            druga.Sort();
            List<int> wynik = new List<int>();
            int znacznik = 0;
            for (int i = 0; i < pierwsza.Count; i++)
            {
                for (int j = znacznik; j < druga.Count; j++)
                {
                    if (pierwsza[i] < druga[j])
                    {
                        znacznik = j;
                        break;
                    }
                    if (pierwsza[i] == druga[j])
                    {
                        znacznik = j;
                        wynik.Add(pierwsza[i]);
                        break;
                    }
                }
            }
            return wynik;
        }

        /// <summary>
        /// Pobiera dane korzystając z klasy PlikTekstowy.
        /// </summary>
        /// <param name="analizator">Analizator wyrażenia.</param>
        /// <param name="czyListaString">Czy metoda wywołująca obsługuje List(string).</param>
        /// <param name="exp">Wyrażenie zapytania.</param>
        /// <returns></returns>
        private List<List<string>> PobierzDane(AnalizatorZapytania analizator, bool czyListaString, Expression exp)
        {
            if (analizator.warunkiAndOr.Count == 0)
            {
                List<int> numeryW = new List<int>(dane.Tabela.Count);
                for (int i = 0; i < numeryW.Count; i++)
                    numeryW[i] = i;
                if (!numeryWybranychWierszy.ContainsKey(exp))
                    numeryWybranychWierszy.Add(exp, numeryW);
                return dane.Tabela;
            }
            List<List<List<string>>> listaWarunków = new List<List<List<string>>>();
            Dictionary<string, int> wlasnosciFiltracja = new Dictionary<string, int>();
            List<List<int>> numeryWierszy = new List<List<int>>();
            foreach (ElementAndOr warunek in analizator.warunkiAndOr)
            {
                List<List<string>> poFiltracji = new List<List<string>>();
                List<int> numeryDlaFiltracji = new List<int>();
                if (warunek.drugaKolumna == -1)
                {
                    int start = -1;
                    if (wlasnosciFiltracja.ContainsKey(warunek.nazwaPierwszejKolumny))
                        start = wlasnosciFiltracja[warunek.nazwaPierwszejKolumny];
                    int znaleziony = analizator.filtracja.FindIndex(start + 1, c => c.NazwaWłasności == warunek.nazwaPierwszejKolumny ? true : false);
                    DaneFiltrowania filtr = analizator.filtracja[znaleziony];
                    if (!wlasnosciFiltracja.ContainsKey(warunek.nazwaPierwszejKolumny))
                        wlasnosciFiltracja.Add(warunek.nazwaPierwszejKolumny, znaleziony);

                    Regex regularne = new Regex(filtr.WyrażenieRegularne.Substring(filtr.WyrażenieRegularne.IndexOf("]") + 1));
                    try
                    {//podwójne == oznaczają że filtracja odnosi się do danych typu string
                        switch (filtr.WyrażenieRegularne.Substring(1, filtr.WyrażenieRegularne.IndexOf("]") - 1))
                        {
                            case "==":
                                if (czyListaString)
                                    poFiltracji = dane.Filtruj(c => regularne.IsMatch(c) ? true : false, Int32.Parse(filtr.NazwaWłasności));
                                else
                                    poFiltracji = dane.Filtruj(c => regularne.IsMatch(c) ? true : false, filtr.NazwaKolumny);
                                break;
                            case "!==":
                                if (czyListaString)
                                    poFiltracji = dane.Filtruj(c => !regularne.IsMatch(c) ? true : false, Int32.Parse(filtr.NazwaWłasności));
                                else
                                    poFiltracji = dane.Filtruj(c => !regularne.IsMatch(c) ? true : false, filtr.NazwaKolumny);
                                break;
                            case ">":
                                poFiltracji = dane.Filtruj((c, d) => Convert.ToDouble(c) > d ? true : false, filtr.NazwaKolumny, filtr.Liczba);
                                break;
                            case ">=":
                                poFiltracji = dane.Filtruj((c, d) => Convert.ToDouble(c) >= d ? true : false, filtr.NazwaKolumny, filtr.Liczba);
                                break;
                            case "<":
                                poFiltracji = dane.Filtruj((c, d) => Convert.ToDouble(c) < d ? true : false, filtr.NazwaKolumny, filtr.Liczba);
                                break;
                            case "<=":
                                poFiltracji = dane.Filtruj((c, d) => Convert.ToDouble(c) <= d ? true : false, filtr.NazwaKolumny, filtr.Liczba);
                                break;
                            case "=":
                                poFiltracji = dane.Filtruj((c, d) => Convert.ToDouble(c) == d ? true : false, filtr.NazwaKolumny, filtr.Liczba);
                                break;
                            case "!=":
                                poFiltracji = dane.Filtruj((c, d) => Convert.ToDouble(c) != d ? true : false, filtr.NazwaKolumny, filtr.Liczba);
                                break;
                            default: break;
                        }
                        numeryDlaFiltracji.AddRange(dane.NumeryWierszy);
                    }
                    catch
                    {
                        if (filtr.NazwaKolumny != string.Empty)
                            throw new NotSupportedException("W bazie danych są dane niespełniające zgodności typów. Błędne dane występują w kolumnie: " + filtr.NazwaKolumny);
                        else
                            throw new NotSupportedException("W bazie danych są dane niespełniające zgodności typów. Błędne dane występują w kolumnie nr: " + filtr.NazwaWłasności);

                    }
                }
                else
                {
                    poFiltracji.AddRange(listaWarunków[warunek.pierwszaKolumna]);
                    poFiltracji.AddRange(listaWarunków[warunek.drugaKolumna]);
                    poFiltracji = warunek.czyAnd ?
                        this.WarunekAnd(poFiltracji, listaWarunków[warunek.pierwszaKolumna].Count) :
                        this.WarunekOr(poFiltracji);
                    numeryDlaFiltracji = warunek.czyAnd ?
                        this.WarunekAnd(numeryWierszy[warunek.pierwszaKolumna], numeryWierszy[warunek.drugaKolumna]) :
                        this.WarunekOr(numeryWierszy[warunek.pierwszaKolumna], numeryWierszy[warunek.drugaKolumna]);

                }
                numeryWierszy.Add(numeryDlaFiltracji);
                listaWarunków.Add(poFiltracji);
            }
            if (!numeryWybranychWierszy.ContainsKey(exp))
                numeryWybranychWierszy.Add(exp, numeryWierszy[numeryWierszy.Count - 1]);
            return listaWarunków[listaWarunków.Count - 1];
        }

        #region Insert
        private void InsertLista(T daneInsert)
        {
            if ((daneInsert as List<string>).Count > dane.IloscKolumn)
                throw new NotSupportedException("Lista musi mieć długość <= od ilość kolumn w tabeli.");
            List<string> temp = new List<string>();
            for (int i = 0; i < (daneInsert as List<string>).Count; i++)
            {
                if ((daneInsert as List<string>)[i] != string.Empty)
                    temp.Add((daneInsert as List<string>)[i]);
                else temp.Add("NULL");
            }
            for (int i = (daneInsert as List<string>).Count; i < dane.IloscKolumn; i++)
                temp.Add("NULL");
            dane.DodajWiersz(temp);
            return;
        }

        private void InsertInterfejs(T daneInsert)
        {
            if (typeof(T).GetConstructor(new Type[] { }).Invoke(null) == null)
                throw new NullReferenceException("Klasa encji musi posiadać bezargumentowy konstruktor.");

            #region Mapowanie własności
            List<string> nazwyKolumn = dane.NazwyKolumn;
            //podaje nazwe kolumny w bazie a otrzymuję nazwe properties w encji
            Dictionary<string, string> kolumnyWłasności = new Dictionary<string, string>();

            T temp = (T)typeof(T).GetConstructor(new Type[] { }).Invoke(null);
            PropertyInfo[] wlasnosci = typeof(T).GetProperties();
            for (int i = 0; i < wlasnosci.Length; i++)
            {
                if (!(((ILinqToTxt)temp).MapowanieKolumn(wlasnosci[i].Name) == string.Empty))
                    kolumnyWłasności.Add(((ILinqToTxt)temp).MapowanieKolumn(wlasnosci[i].Name), wlasnosci[i].Name);
            }
            #endregion

            #region Tworzenie rekordu tabeli
            List<string> wierszDoDodania = new List<string>();

            for (int i = 0; i < dane.IloscKolumn; i++)
            {
                if (kolumnyWłasności.ContainsKey(nazwyKolumn[i]))
                {
                    if (!typeof(T).GetProperty(kolumnyWłasności[nazwyKolumn[i]]).CanRead)
                        throw new NotSupportedException("Wszystkie properties które odpowiadają kolumną w tabeli muszą mieć metode get");
                    string wartoscWlasnosci = typeof(T).GetProperty(kolumnyWłasności[nazwyKolumn[i]]).GetValue(daneInsert, null) != null &&
                                              typeof(T).GetProperty(kolumnyWłasności[nazwyKolumn[i]]).GetValue(daneInsert, null).ToString() != String.Empty ?
                                              typeof(T).GetProperty(kolumnyWłasności[nazwyKolumn[i]]).GetValue(daneInsert, null).ToString() : "NULL";
                    wierszDoDodania.Add(wartoscWlasnosci);
                }
                else wierszDoDodania.Add("NULL");
            }
            dane.DodajWiersz(wierszDoDodania);
            #endregion
        }

        private void InsertAtrybuty(T daneInsert)
        {
            if (typeof(T).GetConstructor(new Type[] { }).Invoke(null) == null)
                throw new NullReferenceException("Klasa encji musi posiadać bezargumentowy konstruktor.");

            #region Mapowanie własności

            List<string> nazwyKolumn = dane.NazwyKolumn;
            int długośćNagłówka = nazwyKolumn.Count;
            //podaje nazwe kolumny w bazie a otrzymuję nazwe properties w encji
            Dictionary<string, string> kolumnyWłasności = new Dictionary<string, string>(długośćNagłówka);

            PropertyInfo[] wlasnosci = typeof(T).GetProperties();
            for (int i = 0; i < wlasnosci.Length; i++)
            {
                ColumnAttribute atrybut = wlasnosci[i].GetCustomAttributes(true)[0] as ColumnAttribute;
                if (atrybut == null)
                    throw new NotSupportedException("Własność nie posiada zdefiniowanego atrybutu Column.");
                if (!nazwyKolumn.Exists(c => c == atrybut.Name ? true : false))
                    throw new NotSupportedException("Klasa encji wskazuje właściwość " + wlasnosci[i].Name + " na kolumne " + atrybut.Name + ", której nie ma w danej tabeli.");
                kolumnyWłasności.Add(atrybut.Name, wlasnosci[i].Name);
            }
            #endregion

            #region Tworzenie rekordu tabeli
            List<string> wierszDoDodania = new List<string>();

            for (int i = 0; i < dane.IloscKolumn; i++)
            {
                if (kolumnyWłasności.ContainsKey(nazwyKolumn[i]))
                {
                    if (!typeof(T).GetProperty(kolumnyWłasności[nazwyKolumn[i]]).CanRead)
                        throw new NotSupportedException("Wszystkie properties które odpowiadają kolumną w tabeli muszą mieć metode get");
                    string wartoscWlasnosci = typeof(T).GetProperty(kolumnyWłasności[nazwyKolumn[i]]).GetValue(daneInsert, null) != null && typeof(T).GetProperty(kolumnyWłasności[nazwyKolumn[i]]).GetValue(daneInsert, null).ToString() != String.Empty ? typeof(T).GetProperty(kolumnyWłasności[nazwyKolumn[i]]).GetValue(daneInsert, null).ToString() : "NULL";
                    wierszDoDodania.Add(wartoscWlasnosci);
                }
                else wierszDoDodania.Add("NULL");
            }
            dane.DodajWiersz(wierszDoDodania);
            #endregion
        }

        public void Insert(T daneInsert)
        {
            if (typeof(T) == typeof(List<string>))
            {
                this.InsertLista(daneInsert);
                return;
            }
            if (typeof(ILinqToTxt).IsAssignableFrom(typeof(T)))
            {
                this.InsertInterfejs(daneInsert);
                return;
            }

            this.InsertAtrybuty(daneInsert);
        }
        #endregion

        #region Delete
        private int DeleteLista(List<T> daneDelete, bool czyZapisać)
        {
            Dictionary<string, List<int>> daneHash = dane.PobierzHashDanych();
            List<int> doUsunięcia = new List<int>();

            #region Hashowanie Wierszy
            for (int i = 0; i < daneDelete.Count; i++)
            {
                string temp = string.Empty;
                for (int j = 0; j < (daneDelete[i] as List<string>).Count; j++)
                {
                    temp += (daneDelete[i] as List<string>)[j] != string.Empty ? (daneDelete[i] as List<string>)[j] : "NULL";
                }
                if (daneHash.ContainsKey(temp))
                    doUsunięcia.AddRange(daneHash[temp]);
            }
            #endregion

            #region Usuwanie
            if (czyZapisać)
            {
                int ileUsunięto = dane.UsunZTabeli(doUsunięcia);
                dane.ZapiszZmianyWTabeli();
                return ileUsunięto;
            }
            else return dane.UsunZTabeli(doUsunięcia);
            #endregion
        }

        private int DeleteInterfejs(List<T> daneDelete, bool czyZapisać)
        {
            Dictionary<string, List<int>> daneHash = dane.PobierzHashDanych();
            List<int> doUsunięcia = new List<int>();

            #region Mapowanie własności
            List<string> nazwyKolumn = dane.NazwyKolumn;
            //podaje nazwe kolumny w bazie a otrzymuję nazwe properties w encji
            Dictionary<string, string> kolumnyWłasności = new Dictionary<string, string>();

            T temp = (T)typeof(T).GetConstructor(new Type[] { }).Invoke(null);
            PropertyInfo[] wlasnosci = typeof(T).GetProperties();
            for (int i = 0; i < wlasnosci.Length; i++)
            {
                if (!(((ILinqToTxt)temp).MapowanieKolumn(wlasnosci[i].Name) == string.Empty))
                    kolumnyWłasności.Add(((ILinqToTxt)temp).MapowanieKolumn(wlasnosci[i].Name), wlasnosci[i].Name);
            }
            #endregion

            #region Hashowanie wierszy
            for (int j = 0; j < daneDelete.Count; j++)
            {
                string tempString = string.Empty;
                for (int i = 0; i < dane.IloscKolumn; i++)
                {
                    if (kolumnyWłasności.ContainsKey(nazwyKolumn[i]))
                    {
                        if (!typeof(T).GetProperty(kolumnyWłasności[nazwyKolumn[i]]).CanRead)
                            throw new NotSupportedException("Wszystkie properties które odpowiadają kolumną w tabeli muszą mieć metode get");
                        string wartoscWlasnosci = typeof(T).GetProperty(kolumnyWłasności[nazwyKolumn[i]]).GetValue(daneDelete[j], null) != null && typeof(T).GetProperty(kolumnyWłasności[nazwyKolumn[i]]).GetValue(daneDelete[j], null).ToString() != String.Empty ? typeof(T).GetProperty(kolumnyWłasności[nazwyKolumn[i]]).GetValue(daneDelete[j], null).ToString() : "NULL";
                        tempString += wartoscWlasnosci;
                    }
                    else tempString += "NULL";
                }
                if (daneHash.ContainsKey(tempString))
                    doUsunięcia.AddRange(daneHash[tempString]);
            }
            #endregion

            #region Usuwanie
            if (czyZapisać)
            {
                int ileUsunięto = dane.UsunZTabeli(doUsunięcia);
                dane.ZapiszZmianyWTabeli();
                return ileUsunięto;
            }
            else return dane.UsunZTabeli(doUsunięcia);
            #endregion
        }

        private int DeleteAtrybuty(List<T> daneDelete, bool czyZapisać)
        {
            Dictionary<string, List<int>> daneHash = dane.PobierzHashDanych();
            List<int> doUsunięcia = new List<int>();

            #region Mapowanie własności
            List<string> nazwyKolumn = dane.NazwyKolumn;
            int długośćNagłówka = nazwyKolumn.Count;
            //podaje nazwe kolumny w bazie a otrzymuję nazwe properties w encji
            Dictionary<string, string> kolumnyWłasności = new Dictionary<string, string>(długośćNagłówka);

            PropertyInfo[] wlasnosci = typeof(T).GetProperties();
            for (int i = 0; i < wlasnosci.Length; i++)
            {
                ColumnAttribute atrybut = wlasnosci[i].GetCustomAttributes(true)[0] as ColumnAttribute;
                if (atrybut == null)
                    throw new NotSupportedException("Własność nie ma zdefiniowanego atrybutu Column.");
                if (!nazwyKolumn.Exists(c => c == atrybut.Name ? true : false))
                    throw new NotSupportedException("Klasa mapuję właściwość " + wlasnosci[i].Name + " na kolumnę " + atrybut.Name + ", której nie ma w danej tabeli.");
                kolumnyWłasności.Add(atrybut.Name, wlasnosci[i].Name);
            }
            #endregion

            #region Hashowanie wierszy
            for (int j = 0; j < daneDelete.Count; j++)
            {
                string temp = string.Empty;
                for (int i = 0; i < dane.IloscKolumn; i++)
                {
                    if (kolumnyWłasności.ContainsKey(nazwyKolumn[i]))
                    {
                        if (!typeof(T).GetProperty(kolumnyWłasności[nazwyKolumn[i]]).CanRead)
                            throw new NotSupportedException("Wszystkie properties które odpowiadają kolumną w tabeli muszą mieć metode get");
                        string wartoscWlasnosci = typeof(T).GetProperty(kolumnyWłasności[nazwyKolumn[i]]).GetValue(daneDelete[j], null) != null && typeof(T).GetProperty(kolumnyWłasności[nazwyKolumn[i]]).GetValue(daneDelete[j], null).ToString() != String.Empty ? typeof(T).GetProperty(kolumnyWłasności[nazwyKolumn[i]]).GetValue(daneDelete[j], null).ToString() : "NULL";
                        temp += wartoscWlasnosci;
                    }
                    else temp += "NULL";
                }

                if (daneHash.ContainsKey(temp))
                    doUsunięcia.AddRange(daneHash[temp]);
            }
            #endregion

            #region Usuwanie
            if (czyZapisać)
            {
                int ileUsunięto = dane.UsunZTabeli(doUsunięcia);
                dane.ZapiszZmianyWTabeli();
                return ileUsunięto;
            }
            else return dane.UsunZTabeli(doUsunięcia);
            #endregion
        }

        public int Delete(List<T> daneDelete, bool czyZapisać)
        {
            if (typeof(T) == typeof(List<string>))
                return this.DeleteLista(daneDelete, czyZapisać);
            if (typeof(ILinqToTxt).IsAssignableFrom(typeof(T))) //interfejs
                return this.DeleteInterfejs(daneDelete, czyZapisać);
            return this.DeleteAtrybuty(daneDelete, czyZapisać);
        }

        public int Delete(List<int> daneDelete, bool czyZapisać)
        {
            if (czyZapisać)
            {
                int ileUsunięto = dane.UsunZTabeli(daneDelete);
                dane.ZapiszZmianyWTabeli();
                return ileUsunięto;
            }
            return dane.UsunZTabeli(daneDelete);
        }
        #endregion

        #region Update
        private int UpdateInterfejs(List<int> daneDoAktualizacji, Dictionary<string, string> noweWartości, bool czyZapisać)
        {
            #region Mapowanie własności
            List<string> nazwyKolumn = dane.NazwyKolumn;
            //podaje nazwe properties w encji a otrzymuję nr kolumny w bazie
            Dictionary<string, int> własnościKolumny = new Dictionary<string, int>();

            T temp = (T)typeof(T).GetConstructor(new Type[] { }).Invoke(null);
            PropertyInfo[] wlasnosci = typeof(T).GetProperties();
            for (int i = 0; i < wlasnosci.Length; i++)
            {
                if (!(((ILinqToTxt)temp).MapowanieKolumn(wlasnosci[i].Name) == string.Empty))
                    własnościKolumny.Add(wlasnosci[i].Name, nazwyKolumn.FindIndex(c => c == ((ILinqToTxt)temp).MapowanieKolumn(wlasnosci[i].Name)));
            }
            #endregion

            #region Tworzenie rekordu
            List<string> wierszAktualizujący = new List<string>(nazwyKolumn.Count);
            for (int i = 0; i < nazwyKolumn.Count; i++)
                wierszAktualizujący.Add(string.Empty);
            foreach (string klucz in noweWartości.Keys)
            {
                wierszAktualizujący[własnościKolumny[klucz]] = noweWartości[klucz];
            }
            #endregion

            if (czyZapisać)
            {
                int ilośćZmienionychWierszy = dane.AktualizujWiersz(wierszAktualizujący, daneDoAktualizacji);
                dane.ZapiszZmianyWTabeli();
                return ilośćZmienionychWierszy;
            }
            return dane.AktualizujWiersz(wierszAktualizujący, daneDoAktualizacji);
        }

        private int UpdateAtrybuty(List<int> daneDoAktualizacji, Dictionary<string, string> noweWartości, bool czyZapisać)
        {
            #region Mapowanie własności

            List<string> nazwyKolumn = dane.NazwyKolumn;
            int długośćNagłówka = nazwyKolumn.Count;

            //podaje nazwe properties w encji a otrzymuję nazwe kolumny w bazie
            Dictionary<string, int> własnościKolumny = new Dictionary<string, int>(długośćNagłówka);

            PropertyInfo[] wlasnosci = typeof(T).GetProperties();
            for (int i = 0; i < wlasnosci.Length; i++)
            {
                ColumnAttribute atrybut = wlasnosci[i].GetCustomAttributes(true)[0] as ColumnAttribute;
                if (atrybut == null)
                    throw new NotSupportedException("Własność klasy encji nie ma zdefiniowanego atrybutu Column.");
                if (!nazwyKolumn.Exists(c => c == atrybut.Name ? true : false))
                    throw new NotSupportedException("Klasa mapuję właściwość " + wlasnosci[i].Name + " na kolumnę " + atrybut.Name + ", której nie ma w danej tabeli.");
                własnościKolumny.Add(wlasnosci[i].Name, nazwyKolumn.FindIndex(c => c == atrybut.Name));
            }
            #endregion

            #region Tworzenie rekordu tabeli
            List<string> wierszAktualizujący = new List<string>(nazwyKolumn.Count);
            for (int i = 0; i < nazwyKolumn.Count; i++)
                wierszAktualizujący.Add(string.Empty);
            foreach (string klucz in noweWartości.Keys)
            {
                wierszAktualizujący[własnościKolumny[klucz]] = noweWartości[klucz];
            }
            #endregion

            if (czyZapisać)
            {
                int ilośćZmienionychWierszy = dane.AktualizujWiersz(wierszAktualizujący, daneDoAktualizacji);
                dane.ZapiszZmianyWTabeli();
                return ilośćZmienionychWierszy;
            }
            return dane.AktualizujWiersz(wierszAktualizujący, daneDoAktualizacji);
        }

        public int Update(List<int> daneDoAktualizacji, Dictionary<string, string> noweWartości, bool czyZapisać)
        {
            if (typeof(T) == typeof(List<string>))
                return -1;
            if (typeof(ILinqToTxt).IsAssignableFrom(typeof(T)))
                return this.UpdateInterfejs(daneDoAktualizacji, noweWartości, czyZapisać);
            return this.UpdateAtrybuty(daneDoAktualizacji, noweWartości, czyZapisać);
        }

        public int Update(List<int> daneDoAktualizacji, List<string> wierszAktualizujący, bool czyZapisać)
        {
            if (wierszAktualizujący.Count != dane.NazwyKolumn.Count) return -1;
            if (czyZapisać)
            {
                int ilośćZmienionychWierszy = dane.AktualizujWiersz(wierszAktualizujący, daneDoAktualizacji);
                dane.ZapiszZmianyWTabeli();
                return ilośćZmienionychWierszy;
            }
            return dane.AktualizujWiersz(wierszAktualizujący, daneDoAktualizacji);
        }

        public void Update()
        {
            dane.ZapiszZmianyWTabeli();
        }
        #endregion

        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);
        }
    }
}