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

namespace Elwig.Models.Dtos {
    public class DataTable<T> {

        public string Name { get; set; }
        public string FullName { get; set; }
        public IEnumerable<T> Rows { get; private set; }
        public int RowNum => Rows.Count();
        public IEnumerable<(string, Type?)> ColumnDefs => _map.Select(m => (m.Item1, m.Item2?.PropertyType ?? m.Item3?.FieldType));
        public IEnumerable<string> ColumnNames => ColumnDefs.Select(m => m.Item1);
        public IEnumerable<Type?> ColumnTypes => ColumnDefs.Select(m => m.Item2);
        public IEnumerable<(string, int)> ColumnSpans => ColumnDefs.Select(c => {
            var type = c.Item2;
            var elType = type?.GetElementType();
            return (c.Item1,
                type != null && type.IsValueType && type.Name.StartsWith("ValueTuple") ? type.GetFields().Length :
                type != null && elType != null && type.IsArray && elType.IsValueType && elType.Name.StartsWith("ValueTuple") ? elType.GetFields().Length : 1
            );
        }).ToList();
        public int ColNum => ColumnNames.Count();
        private readonly PropertyInfo[] _properties;
        private readonly FieldInfo[] _fields;
        private readonly (string, PropertyInfo?, FieldInfo?)[] _map;

        public DataTable(string name, string fullName, IEnumerable<T> rows, IEnumerable<(string, string)>? colNames = null) {
            _fields = typeof(T).GetFields();
            _properties = typeof(T).GetProperties();
            colNames ??= _properties.Select(p => p.Name).Union(_fields.Select(f => f.Name)).Select(i => (i, i)).ToList();
            _map = colNames.Select(n => (n.Item2, _properties.FirstOrDefault(p => p?.Name == n.Item1, null), _fields.FirstOrDefault(f => f?.Name == n.Item1, null))).ToArray();
            Name = name;
            FullName = fullName;
            Rows = rows;
        }

        public DataTable(string name, IEnumerable<T> rows, IEnumerable<(string, string)>? colNames = null) :
            this(name, name, rows, colNames) { }

        protected IEnumerable<(string, object?)> GetNamedRowData(T row) {
            return _map.Select(i => (i.Item1, i.Item2?.GetValue(row) ?? i.Item3?.GetValue(row)));
        }

        protected IEnumerable<object?> GetRowData(T row) {
            return GetNamedRowData(row).Select(i => i.Item2);
        }

        public IEnumerable<IEnumerable<object?>> GetData() {
            return Rows.Select(GetRowData);
        }

        public IEnumerable<IEnumerable<(string, object?)>> GetNamedData() {
            return Rows.Select(GetNamedRowData);
        }
    }
}