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

namespace DataTable
{
    public class TableMapper<T>
        where T : new()
    {
        private readonly Dictionary<string, PropertyHelper> props;

        public TableMapper()
        {
            var props = typeof(T).GetProperties();
            this.props = new Dictionary<string, PropertyHelper>(props.Length);
            foreach (var prop in props)
            {
                var helper = new PropertyHelper(prop);
                this.props.Add(helper.Id, helper);
            }
        }

        public IEnumerable<T> AsEnumerable(Table table)
        {
            if ((table?.Headers is null) ||
                (table?.Rows is null) ||
                (!table.Headers.Any()) ||
                (!table.Rows.Any()) ||
                (!props.Any()))
                yield break;

            var headerIdToProp = new Dictionary<int, PropertyHelper>(props.Count);
            foreach (var (_, propHelper) in props)
                for (var i = 0; i < table.Headers.Count(); i++)
                {
                    var header = table.Headers.ElementAt(i);
                    var columnType = System.Type.GetType(header.ColumnType);
                    if (columnType.IsAssignableTo(propHelper.PropertyType) &&
                        ((header.Name == propHelper.PropertyName) ||
                        (header.Name.ToLower() == propHelper.PropertyName.ToLower())))
                        headerIdToProp.Add(i, propHelper);
                }

            if (!headerIdToProp.Any())
                yield break;

            foreach (var row in table.Rows)
            {
                var obj = new T();
                foreach (var (i, propHelper) in headerIdToProp)
                    propHelper.Set(obj, row.ElementAt(i));
                yield return obj;
            }

            yield break;
        }

        public Table MakeTable(IEnumerable<T> data)
        {
            var table = new Table();
            table.Headers = props.Select(pair => new Header
            {
                Name = pair.Value.PropertyName,
                ColumnType = pair.Value.PropertyType.Name,
            }).ToArray();

            var rows = new List<List<object>>(data.Count());
            foreach (var dataItem in data)
                rows.Add(MakeRow(dataItem));

            table.Rows = rows;

            return table;
        }

        private List<object> MakeRow(T dataItem)
        {
            var colunms = new List<object>(props.Count);
            foreach (var (_, propHelper) in props)
            {
                var propValue = propHelper.Get(dataItem);
                colunms.Add(propValue);
            }
            return colunms;
        }

    }
}