using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
using InABox.Clients;
using InABox.Configuration;
using InABox.Core;
using InABox.Core.Reports;
using InABox.WPF;

namespace InABox.DynamicGrid
{
    public interface IDynamicOneToManyGrid<TOne, TMany> : IDynamicEditorPage
    {
        List<TMany> Items { get; }
        void LoadItems(TMany[] items);
    }

    public class DynamicOneToManyGrid<TOne, TMany> : DynamicGrid<TMany>, IDynamicEditorPage, IDynamicOneToManyGrid<TOne, TMany>
        where TOne : Entity, new() where TMany : Entity, IPersistent, IRemotable, new()
    {
        private TMany[] MasterList = { };
        private readonly PropertyInfo property;

        public PageType PageType => PageType.Other;

        public DynamicOneToManyGrid()
        {
            Ready = false;
            Items = new List<TMany>();
            Criteria = new Filters<TMany>();

            property = CoreUtils.GetOneToManyProperty(typeof(TMany), typeof(TOne));

            Options.BeginUpdate();

            Options.Add(DynamicGridOption.RecordCount)
                .Add(DynamicGridOption.SelectColumns);

            if (Security.CanEdit<TMany>())
                Options.Add(DynamicGridOption.AddRows).Add(DynamicGridOption.EditRows);
            if (Security.CanDelete<TMany>())
                Options.Add(DynamicGridOption.DeleteRows);
            if (Security.CanImport<TMany>())
                Options.Add(DynamicGridOption.ImportData);
            if (Security.CanExport<TMany>())
                Options.Add(DynamicGridOption.ExportData);
            if (Security.CanMerge<TMany>())
                Options.Add(DynamicGridOption.MultiSelect);
            Options.EndUpdate();

            /*var t = typeof(TMany).BaseType;
            if (t != null && t.IsConstructedGenericType && t.GetGenericTypeDefinition() == typeof(EntityForm<,>))
            {
                ActionColumns.Add(new DynamicImageColumn(QAEditImage, QAEditClick));
                if (DynamicGridUtils.PreviewReport != null)
                    ActionColumns.Add(new DynamicImageColumn(QAPrintImage, QAPrintClick));
                HiddenColumns.Add(x => (x as IDigitalFormInstance).FormData);
                HiddenColumns.Add(x => (x as IDigitalFormInstance).FormCompleted);
                HiddenColumns.Add(x => (x as IDigitalFormInstance).FormCompletedBy.ID);
                HiddenColumns.Add(x => (x as IDigitalFormInstance).Form.ID);
            }*/
        }

        protected Filters<TMany> Criteria { get; }

        public TOne Item { get; protected set; }

        public bool Ready { get; set; }

        public DynamicEditorGrid EditorGrid { get; set; }

        public string Caption()
        {
            var caption = typeof(TMany).GetCustomAttribute(typeof(Caption));
            if (caption != null)
                return ((Caption)caption).Text;
            var result = new Inflector.Inflector(new CultureInfo("en")).Pluralize(typeof(TMany).Name);
            return result;
        }

        public virtual int Order()
        {
            return int.MinValue;
        }

        public virtual void Load(object item, Func<Type, CoreTable>? PageDataHandler)
        {
            Item = (TOne)item;
            
            CoreTable? data = PageDataHandler?.Invoke(typeof(TMany));

            if (data == null)
            {
                if (Item.ID == Guid.Empty)
                {
                    data = new CoreTable();
                    data.LoadColumns(typeof(TMany));
                }
                else
                {
                    var criteria = new Filters<TMany>();
                    var exp = CoreUtils.GetPropertyExpression<TMany>(property.Name + ".ID");
                    criteria.Add(new Filter<TMany>(exp).IsEqualTo(Item.ID).And(exp).IsNotEqualTo(Guid.Empty));
                    criteria.AddRange(Criteria.Items);
                    var sort = LookupFactory.DefineSort<TMany>();
                    data = new Client<TMany>().Query(criteria.Combine(), null, sort);
                }
            }

            MasterList = data.Rows.Select(x => x.ToObject<TMany>()).ToArray();

            Items = MasterList.ToList();
            Refresh(true, true);
            Ready = true;
        }

        public void BeforeSave(object item)
        {
            // Don't need to do anything here
        }

        protected virtual void OnDeleteItem(TMany item)
        {
            new Client<TMany>().Delete(item, typeof(TMany).Name + " Deleted by User");
        }

        public void AfterSave(object item)
        {
            // First remove any deleted files
            foreach (var map in MasterList)
                if (!Items.Contains(map))
                    OnDeleteItem(map);

            foreach (var map in Items)
            {
                var prop = (property.GetValue(map) as IEntityLink)!;
                prop.ID = Item.ID;
                prop.Synchronise(Item);
                //if (map.IsChanged())
                //    new Client<TMany>().Save(map, "Updated by User");
            }

            new Client<TMany>().Save(Items.Where(x => x.IsChanged()), "Updated by User");
        }

        protected override CoreTable LoadImportKeys(string[] fields)
        {
            var result = base.LoadImportKeys(fields);
            result.LoadRows(MasterList);
            return result;
        }

        protected override bool CustomiseImportItem(TMany item)
        {
            var result = base.CustomiseImportItem(item);
            if (result)
            {
                var prop = (property.GetValue(item) as IEntityLink)!;
                prop.ID = Item.ID;
                prop.Synchronise(Item);
            }

            return result;
        }

        public Size MinimumSize()
        {
            return new Size(400, 400);
        }

        public List<TMany> Items { get; private set; }

        public void LoadItems(TMany[] items)
        {
            Items.Clear();
            Items.AddRange(items);
            Refresh(false, true);
        }

        protected override DynamicGridColumns LoadColumns()
        {
            var tag = typeof(TOne).Name + "." + typeof(TMany).Name;
            var global = Task.Run(() => new GlobalConfiguration<DynamicGridColumns>(tag).Load());
            var user = Task.Run(() => new UserConfiguration<DynamicGridColumns>(tag).Load());
            Task.WaitAll(global, user);
            var columns = user.Result.Any() ? user.Result : global.Result;
            if (columns.Count == 0)
                columns.AddRange(base.LoadColumns().Where(x => !x.ColumnName.StartsWith(property.Name)));
            return columns;
        }

        protected override void SaveColumns(DynamicGridColumns columns)
        {
            var tag = typeof(TOne).Name + "." + typeof(TMany).Name;
            new UserConfiguration<DynamicGridColumns>(tag).Save(columns);
        }

        protected override TMany CreateItem()
        {
            var result = new TMany();
            var prop = (IEntityLink)property.GetValue(result);
            prop.ID = Item.ID;
            return result;
        }

        protected override TMany LoadItem(CoreRow row)
        {
            return Items[_recordmap[row].Index];
        }

        protected override TMany[] LoadItems(CoreRow[] rows)
        {
            var result = new List<TMany>();
            foreach (var row in rows)
                result.Add(LoadItem(row));
            return result.ToArray();
        }

        public override void SaveItem(TMany item)
        {
            
            if (!Items.Contains(item))
                Items.Add(item);

            if (item is ISequenceable) Items = Items.AsQueryable().OrderBy(x => (x as ISequenceable).Sequence).ToList();
            //var sort = LookupFactory.DefineSort<TMany>();
            //if (sort != null)
            //    Items = Items.AsQueryable().SortBy(sort.Expression).ToList();
        }

        protected override void DeleteItems(params CoreRow[] rows)
        {
            var items = rows.Select(LoadItem).ToList();
            foreach(var item in items)
            {
                Items.Remove(item);
            }
        }

        protected override void Reload(Filters<TMany> criteria, Columns<TMany> columns, ref SortOrder<TMany>? sort,
            Action<CoreTable?, Exception?> action)
        {
            //var exp = BaseObject.DefaultSortOrder<TMany>().Expression;
            //Items = Items.AsQueryable().SortBy(exp).To

            var results = new CoreTable();
            results.LoadColumns(typeof(TMany));

            if (sort != null)
            {
                var exp = IQueryableExtensions.ToLambda<TMany>(sort.Expression);
                var sorted = sort.Direction == SortDirection.Ascending
                    ? Items.AsQueryable().OrderBy(exp)
                    : Items.AsQueryable().OrderByDescending(exp);
                foreach (var then in sort.Thens)
                {
                    var thexp = IQueryableExtensions.ToLambda<TMany>(then.Expression);
                    sorted = sort.Direction == SortDirection.Ascending ? sorted.ThenBy(exp) : sorted.ThenByDescending(exp);
                }
                Items = sorted.ToList();
            }
            results.LoadRows(Items);

            //if (sort != null)
            //    results.LoadRows(Items.AsQueryable().SortBy(sort.Expression));
            //else
            //    results.LoadRows(Items.OrderBy(x=>x.Sort));
            action.Invoke(results, null);
        }

        protected override BaseEditor? GetEditor(object item, DynamicGridColumn column)
        {
            var type = CoreUtils.GetProperty(typeof(TMany), column.ColumnName).DeclaringType;
            if (type.GetInterfaces().Contains(typeof(IEntityLink)) && type.ContainsInheritedGenericType(typeof(TOne)))
                return new NullEditor();
            return base.GetEditor(item, column);
        }

        //protected override void EditorValueChanged(object item, string name, object value, List<String> changes)
        //{
        //    Entity entity = (Entity)item;

        //    Dictionary<String, String> previous = new Dictionary<string, string>();

        //    if (entity.OriginalValues != null)
        //    {
        //        foreach (var key in entity.OriginalValues.Keys)
        //            previous[key] = entity.OriginalValues.ToString();
        //    }

        //    base.EditorValueChanged(item, name, value, changes);

        //    if (entity.OriginalValues != null)
        //    {
        //        foreach (var key in entity.OriginalValues.Keys)
        //        {
        //            if (key != name)
        //            {
        //                String oldval = entity.OriginalValues[key] != null ? entity.OriginalValues[key].ToString() : "";
        //                if ((!previous.ContainsKey(key)) || (!previous[key].Equals(oldval)))
        //                {
        //                    if (!changes.Contains(key))
        //                        changes.Add(key);
        //                }
        //            }
        //        }
        //    }
        //}

        /*private BitmapImage QAPrintImage(CoreRow arg)
        {
            return Wpf.Resources.print.AsBitmapImage();
        }

        private bool QAPrintClick(CoreRow arg)
        {
            var formid = arg.Get<TMany, Guid>(x => (x as IDigitalFormInstance).Form.ID);
            var model = new DigitalFormReportDataModel<TMany>(new Filter<TMany>("Parent.ID").IsEqualTo(Item.ID), formid);
            var section = formid.ToString();

            // TODO: This is a hack
            DynamicGridUtils.PrintMenu?.Invoke(null, section, model, true);

            return false;
        }*/

        /*private BitmapImage QAEditImage(CoreRow arg)
        {
            if (arg == null)
                return Wpf.Resources.pencil.AsBitmapImage();
            var completed = arg.Get<TMany, DateTime>(x => (x as IBaseDigitalFormInstance).FormCompleted);
            return completed.IsEmpty() ? Wpf.Resources.pencil.AsBitmapImage() : Wpf.Resources.view.AsBitmapImage();
        }

        private bool QAEditClick(CoreRow arg)
        {
            var item = LoadItem(arg);
            var result = DynamicFormEditWindow.EditDigitalForm(item as IDigitalFormInstance);
            if (result)
                SaveItem(item);
            return result;
        }*/

        public override DynamicEditorPages LoadEditorPages(TMany item)
        {
            return item.ID != Guid.Empty ? base.LoadEditorPages(item) : new DynamicEditorPages();
        }

        protected override bool BeforePaste(IEnumerable<TMany> items, ClipAction action)
        {
            if(action == ClipAction.Copy)
            {
                foreach(var item in items)
                {
                    item.ID = Guid.Empty;
                }
            }
            return base.BeforePaste(items, action);
        }
    }
}