using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; using InABox.Core; using System.Diagnostics.CodeAnalysis; namespace InABox.Mobile { public abstract class ListModel : Model, IListModel, IEnumerable where TParent : ListModel where TEntity : Entity, IRemotable, IPersistent, new() where TItem : Shell, new() { protected ListModel(IModelHost host, Func> filter, bool transient = false) : base(host, filter, transient) { Reset(); } protected ListModel(IModelHost host, Func> filter, [NotNull] String filename) : base(host, filter, filename) { Reset(); } protected override void Initialize() { _allitems = null; _items.Clear(); } public virtual SortOrder Sort => LookupFactory.DefineSort(); private IList _allitems; private CoreTable _table = new CoreTable(); private readonly CoreObservableCollection _items = new CoreObservableCollection(); public IList Items => _items; IEnumerable IListModel.Items => this.Items; public Func SearchPredicate { get; set; } public void Search(Func predicate) { SearchPredicate = predicate; Search(); } public void Search() { _items.ReplaceRange( SearchPredicate != null ? new List(_allitems.Where(SearchPredicate)) : new List(_allitems) ); } protected virtual Expression> ImageColumn => null; public override void BeforeLoad(MultiQuery query) { } public override void AfterLoad(MultiQuery query) { } public override IModel Refresh(bool force) { if (!Loaded || force) Load(); return this; } public override void Refresh(bool force, Action loaded) { if (!Loaded || force) Load(loaded); else loaded?.Invoke(); } public override void Load(Action loaded = null) { MultiQuery query = new MultiQuery(); query.Add( Filter(), GetColumns(), Sort ); if (ImageColumn != null) { query.Add( new Filter(x => x.ID).InQuery(Filter(), ImageColumn), new Columns(x => x.ID) .Add(x => x.Data) ); } BeforeLoad(query); // If we have a valid transport, always try and get new data from the server if (Host.IsConnected()) { if (loaded != null) query.Query((q) => { if (Type == ModelType.Persistent) SaveToStorage(q); DoAfterLoad(q, loaded); }); else { query.Query(); if (Type == ModelType.Persistent) SaveToStorage(query); DoAfterLoad(query); } } else { if (Type == ModelType.Transient) { InitializeTables(query); } else if (Type == ModelType.Normal) { // Only load if (_allitems == null) InitializeTables(query); } else if (Type == ModelType.Persistent) { // Treat it as normal, unless its the first time through // in which case try to load it from storage, if the // data has been previously cached if (_allitems == null) LoadFromStorage(query); } DoAfterLoad(query, loaded); } } private void DoAfterLoad(MultiQuery query, Action loaded = null) { _table = query.Get(); _allitems = new List(query.Get().Rows.Select(row => CreateItem(row))); if (ImageColumn != null) { Images.Clear(); query.Get().IntoDictionary(Images, x => x.ID, r => r.Get(x => x.Data)); } Search(null); AfterLoad(query); Loaded = true; loaded?.Invoke(); NotifyChanged(); } protected T CreateItem(CoreRow row) where T : Shell, new() { var result = new T() { Row = row, Parent = (TParent)this }; result.PropertyChanged += (sender, args) => DoPropertyChanged(result, args); return result; } public TItem CreateItem() { CoreRow row = _table.NewRow(); var result = CreateItem(row); return result; } public void CommitItem(TItem item) { _table.Rows.Add(item.Row); _allitems.Add(item); NotifyChanged(); } public TItem AddItem() { var result = CreateItem(); CommitItem(result); return result; } public void DeleteItem(TItem item) { _table.Rows.Remove(item.Row); _allitems.Remove(item); NotifyChanged(); } object IListModel.CreateItem() => this.CreateItem(); void IListModel.CommitItem(object item) { if (item is TItem titem) CommitItem(titem); } object IListModel.AddItem() => this.AddItem(); void IListModel.DeleteItem(object item) { if (item is TItem titem) DeleteItem(titem); } IEnumerator IEnumerable.GetEnumerator() { return Items.GetEnumerator(); } public IEnumerator GetEnumerator() { return Items.GetEnumerator(); } } }