using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace InABox.Core
{
    [UserTracking(false)]
    public class ExportColumn : Entity, ILicense<CoreLicense>
    {
        public string Property { get; set; }
        public string Name { get; set; }
        public string Format { get; set; }
    }

    public abstract class AbstractExporter
    {
        protected static PropertyInfo GetProperty<T>(Expression<Func<T, object>> expression)
        {
            var member = expression.Body as MemberExpression;
            if (member == null)
            {
                var unary = expression.Body as UnaryExpression;
                member = (MemberExpression)unary.Operand;
            }

            return (PropertyInfo)member.Member;
        }

        public abstract void Export<T>(Stream stream, IEnumerable<T> items, Dictionary<string, Expression<Func<T, object>>> expressions);

        public abstract void Export(Stream stream, IEnumerable items, Dictionary<string, string> properties);

        public abstract void Export(Stream stream, IEnumerable items, IEnumerable<ExportColumn> columns);

        public abstract void Export(Stream stream, CoreTable table);

        public void Export<T>(Stream stream, IEnumerable<T> items)
        {
            var expressions = new Dictionary<string, Expression<Func<T, object>>>();
            var properties = typeof(T).GetTypeInfo().GetProperties();
            foreach (var propInfo in properties)
            {
                var parameter = Expression.Parameter(typeof(T));
                var property = Expression.Property(parameter, propInfo);
                var conversion = Expression.Convert(property, typeof(object));
                var lambda = Expression.Lambda<Func<T, object>>(conversion, parameter);
                expressions[propInfo.Name] = lambda;
            }

            Export(stream, items, expressions);
        }
    }

    public abstract class DelimitedExporter : AbstractExporter
    {
        protected char Delimiter = char.MinValue;

        public override void Export<T>(Stream stream, IEnumerable<T> items, Dictionary<string, Expression<Func<T, object>>> expressions)
        {
            using (var sw = new StreamWriter(stream, Encoding.Unicode))
            {
                var row = new List<string>();
                foreach (var column in expressions.Keys)
                    row.Add(column);
                sw.WriteLine(string.Join(Convert.ToString(Delimiter), row));

                foreach (var item in items)
                {
                    row.Clear();
                    foreach (var column in expressions.Keys)
                    {
                        var expression = expressions[column];
                        if (expression != null)
                        {
                            var f = expression.Compile();
                            var o = f.Invoke(item);
                            row.Add(o != null ? o.ToString() : "");
                        }
                        else
                        {
                            row.Add("");
                        }
                    }

                    sw.WriteLine(string.Join(Convert.ToString(Delimiter), row));
                }
            }
        }

        public override void Export(Stream stream, IEnumerable items, Dictionary<string, string> properties)
        {
            using (var sw = new StreamWriter(stream, Encoding.Unicode))
            {
                var row = new List<string>();
                foreach (var column in properties.Keys)
                    row.Add(column);
                sw.WriteLine(string.Join(Convert.ToString(Delimiter), row));

                foreach (var item in items)
                {
                    row.Clear();
                    foreach (var column in properties.Keys)
                    {
                        var property = properties[column];
                        if (!string.IsNullOrEmpty(property))
                        {
                            var o = CoreUtils.GetPropertyValue(item, property);
                            row.Add(o != null ? o.ToString() : "");
                        }
                        else
                        {
                            row.Add("");
                        }
                    }

                    sw.WriteLine(string.Join(Convert.ToString(Delimiter), row));
                }
            }
        }

        public override void Export(Stream stream, IEnumerable items, IEnumerable<ExportColumn> columns)
        {
            using (var sw = new StreamWriter(stream, Encoding.Unicode))
            {
                var row = new List<string>();
                foreach (var column in columns)
                    row.Add(column.Name);

                sw.WriteLine(string.Join(Convert.ToString(Delimiter), row));
                if (items != null)
                    foreach (var item in items)
                    {
                        row.Clear();
                        foreach (var column in columns)
                        {
                            var property = column.Property;
                            if (!string.IsNullOrEmpty(property))
                            {
                                var o = CoreUtils.GetPropertyValue(item, property);
                                var fmt = "{0" + (string.IsNullOrWhiteSpace(column.Format) ? "" : ":" + column.Format) + "}";
                                row.Add(o != null ? string.Format(fmt, o) : "");
                            }
                            else
                            {
                                row.Add("");
                            }
                        }

                        sw.WriteLine(string.Join(Convert.ToString(Delimiter), row));
                    }
            }
        }

        public override void Export(Stream stream, CoreTable table)
        {
            using var sw = new StreamWriter(stream, Encoding.Unicode);
            var row = new List<string>();
            foreach (var column in table.Columns)
                row.Add(column.ColumnName);

            sw.WriteLine(string.Join(Convert.ToString(Delimiter), row));
            foreach (var datarow in table.Rows)
            {
                row.Clear();
                foreach (var column in table.Columns)
                {
                    var o = datarow[column.ColumnName];
                    row.Add(o != null ? o.ToString() : "");
                }

                sw.WriteLine(string.Join(Convert.ToString(Delimiter), row));
            }
        }
    }

    public class CSVExporter : DelimitedExporter
    {
        public CSVExporter()
        {
            Delimiter = ',';
        }
    }

    public class TabExporter : DelimitedExporter
    {
        public TabExporter()
        {
            Delimiter = '\t';
        }
    }
}