using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using InABox.Clients;
using InABox.Configuration;
using InABox.Core;
using InABox.WPF;
using Expression = System.Linq.Expressions.Expression;
namespace InABox.DynamicGrid;
public interface IDynamicDataGrid : IDynamicGrid
{
///
/// The tag the the DynamicGridColumns are stored against. If set to ,
/// the name of is used as a default.
///
string? ColumnsTag { get; set; }
IColumns LoadEditorColumns();
}
public class DynamicDataGrid : DynamicGrid, IDynamicDataGrid where TEntity : Entity, IRemotable, IPersistent, new()
{
public delegate void OnReloadEventHandler(object sender, Filters criteria, Columns columns, ref SortOrder? sortby);
private readonly int ChunkSize = 500;
private Button MergeBtn = null!; //Late-initialised
public DynamicGridFilterButtonComponent FilterComponent;
protected DynamicGridCustomColumnsComponent ColumnsComponent;
private Column[] FilterColumns;
public DynamicDataGrid() : base()
{
var fields = DatabaseSchema.Properties(typeof(TEntity));
foreach (var field in fields)
if (!MasterColumns.Any(x => x.ColumnName == field.Name))
MasterColumns.Add(new DynamicGridColumn { ColumnName = field.Name });
var cols = LookupFactory.DefineColumns();
// Minimum Columns for Lookup values
foreach (var col in cols)
HiddenColumns.Add(CoreUtils.CreateLambdaExpression(col.Property));
// Minimum Columns for Successful Saving
// This should be cross-checked with the relevant Store<>
// so that clients will (usually) provide sufficient columns for saving
foreach (var col in LookupFactory.RequiredColumns())
HiddenColumns.Add(CoreUtils.CreateLambdaExpression(col.Property));
//HiddenColumns.Add(x => x.ID);
if (typeof(TEntity).GetInterfaces().Contains(typeof(IIssues)))
{
HiddenColumns.Add(x => (x as IIssues)!.Issues);
var coltype = typeof(DynamicIssuesColumn<>).MakeGenericType(typeof(TEntity));
ActionColumns.Add((Activator.CreateInstance(coltype, this) as DynamicActionColumn)!);
}
SetupFilterColumns();
}
protected override void Init()
{
FilterComponent = new(this,
new GlobalConfiguration(GetTag()),
new UserConfiguration(GetTag()));
FilterComponent.OnFilterRefresh += () => Refresh(false, true);
ColumnsComponent = new DynamicGridCustomColumnsComponent(this, GetTag());
MergeBtn = AddButton("Merge", Wpf.Resources.merge.AsBitmapImage(Color.White), DoMerge);
}
protected override void DoReconfigure(DynamicGridOptions options)
{
if (Security.CanEdit())
{
options.AddRows = true;
options.EditRows = true;
}
if (Security.CanDelete())
options.DeleteRows = true;
if (Security.CanImport() && typeof(TEntity).HasInterface())
options.ImportData = true;
if (Security.CanExport() && typeof(TEntity).HasInterface())
options.ExportData = true;
if (Security.CanMerge())
options.MultiSelect = true;
}
[MemberNotNull(nameof(FilterColumns))]
private void SetupFilterColumns()
{
if (typeof(TEntity).GetCustomAttribute() is AutoEntity auto)
{
if (auto.Generator is not null)
{
var columns = auto.Generator.IDColumns;
FilterColumns = columns.Select(x => new Column(x.Property)).ToArray();
}
else
{
FilterColumns = Array.Empty>();
}
}
else
{
FilterColumns = new[] { new Column(x => x.ID) };
}
foreach (var column in FilterColumns)
{
AddHiddenColumn(column.Property);
}
}
protected override void BeforeLoad(IDynamicEditorForm form, TEntity[] items)
{
form.ReadOnly = form.ReadOnly || !Security.CanEdit();
base.BeforeLoad(form, items);
}
private string? _columnsTag;
public string? ColumnsTag
{
get => _columnsTag;
set
{
_columnsTag = value;
ColumnsComponent.Tag = GetTag();
}
}
protected override void OptionsChanged()
{
base.OptionsChanged();
if (MergeBtn != null)
MergeBtn.Visibility = Visibility.Collapsed;
FilterComponent.ShowFilterList = Options.FilterRows && !Options.HideDatabaseFilters;
}
protected override void SelectItems(CoreRow[]? rows)
{
base.SelectItems(rows);
MergeBtn.Visibility = Options.MultiSelect && typeof(TEntity).IsAssignableTo(typeof(IMergeable)) && Security.CanMerge() && rows != null && rows.Length > 1
? Visibility.Visible
: Visibility.Collapsed;
}
public event OnReloadEventHandler? OnReload;
protected bool IsPaging { get; private set; } = false;
protected override string FormatRecordCount()
{
return IsPaging
? $"{base.FormatRecordCount()} (loading..)"
: base.FormatRecordCount();
}
protected override void Reload(Filters criteria, Columns columns, ref SortOrder? sort,
Action action)
{
criteria.Add(FilterComponent.GetFilter());
OnReload?.Invoke(this, criteria, columns, ref sort);
if(Options.PageSize > 0)
{
var inSort = sort;
Task.Run(() =>
{
var page = CoreRange.Database(Options.PageSize);
var filter = criteria.Combine();
IsPaging = true;
while (true)
{
try
{
var data = Client.Query(filter, columns, inSort, page);
data.Offset = page.Offset;
IsPaging = data.Rows.Count == page.Limit;
action(data, null);
if (!IsPaging)
break;
// Proposal - Let's slow it down a bit to enhance UI responsiveness?
Thread.Sleep(100);
page.Next();
}
catch (Exception e)
{
action(null, e);
break;
}
}
});
}
else
{
Client.Query(criteria.Combine(), columns, sort, action);
}
}
private CoreRow[]? SelectedBeforeRefresh;
protected override void OnAfterRefresh()
{
base.OnAfterRefresh();
if (SelectedBeforeRefresh is not null)
{
var selectedValues = SelectedBeforeRefresh
.Select(x => FilterColumns.Select(c => x[c.Property]).ToList())
.ToList();
SelectedBeforeRefresh = null;
var selectedRows = Data.Rows.Where(r =>
{
return selectedValues.Any(v =>
{
for (int i = 0; i < v.Count; ++i)
{
if (v[i]?.Equals(r[FilterColumns[i].Property]) != true)
return false;
}
return true;
});
}).ToArray();
SelectedRows = selectedRows;
SelectItems(selectedRows);
}
}
public override void Refresh(bool reloadcolumns, bool reloaddata)
{
SelectedBeforeRefresh = SelectedRows;
base.Refresh(reloadcolumns, reloaddata);
}
IColumns IDynamicDataGrid.LoadEditorColumns() => LoadEditorColumns();
public Columns LoadEditorColumns()
{
return DynamicGridUtils.LoadEditorColumns(DataColumns());
}
private Filter? GetRowFilter(CoreRow row)
{
var newFilters = new Filters();
foreach (var column in FilterColumns)
{
newFilters.Add(new Filter(column.Property).IsEqualTo(row[column.Property]));
}
return newFilters.Combine();
}
public override TEntity[] LoadItems(CoreRow[] rows)
{
Filter? filter = null;
var results = new List();
for (var i = 0; i < rows.Length; i += ChunkSize)
{
var chunk = rows.Skip(i).Take(ChunkSize);
foreach (var row in chunk)
{
var newFilter = GetRowFilter(row);
if (newFilter is not null)
{
//var id = row.Get(x => x.ID);
if (filter is null)
filter = newFilter;//new Filter(x => x.ID).IsEqualTo(id);
else
filter = filter.Or(newFilter);//.Or(x => x.ID).IsEqualTo(id);
}
}
var columns = LoadEditorColumns(); // new Columns().Default(ColumnType.IncludeOptional, ColumnType.IncludeForeignKeys, ColumnType.IncludeUserProperties);
var data = Client.Query(filter, columns);
results.AddRange(data.ToObjects());
}
return results.ToArray();
}
public override TEntity LoadItem(CoreRow row)
{
var id = row.Get(x => x.ID);
var filter = GetRowFilter(row);//new Filter(x => x.ID).IsEqualTo(id);
return Client.Query(filter, LoadEditorColumns()).ToObjects().FirstOrDefault()
?? throw new Exception($"No {typeof(TEntity).Name} with ID {id}");
}
public override void SaveItem(TEntity item)
{
Client.Save(item, "Edited by User");
}
public override void SaveItems(TEntity[] items)
{
Client.Save(items, "Edited by User");
}
public override void DeleteItems(params CoreRow[] rows)
{
var deletes = new List();
foreach (var row in rows)
{
var delete = new TEntity();
foreach (var column in FilterColumns)
{
CoreUtils.SetPropertyValue(delete, column.Property, row[column.Property]);
}
//var delete = /* row.ToObject(); */
deletes.Add(delete);
}
Client.Delete(deletes, "Deleted on User Request");
}
private object GetPropertyValue(Expression member, object obj)
{
var objectMember = Expression.Convert(member, typeof(object));
var getterLambda = Expression.Lambda>(objectMember);
var getter = getterLambda.Compile();
return getter();
}
protected override void ObjectToRow(TEntity obj, CoreRow row)
{
foreach (var column in Data.Columns)
{
var prop = DatabaseSchema.Property(obj.GetType(), column.ColumnName);
if (prop is StandardProperty)
row.Set(column.ColumnName, CoreUtils.GetPropertyValue(obj, prop.Name));
else if (prop is CustomProperty)
row.Set(column.ColumnName, obj.UserProperties[column.ColumnName]);
}
//base.ObjectToRow(obj, row);
}
//private void Auditgrid_OnReload(object sender, Dictionary criteria, ref String sort)
//{
// criteria["DocumentType"] = typeof(TEntity).EntityName();
// criteria["DocumentID"] = (sender as FrameworkElement).Tag;
// sort = "TimeStamp";
//}
public override void LoadEditorButtons(TEntity item, DynamicEditorButtons buttons)
{
base.LoadEditorButtons(item, buttons);
if (ClientFactory.IsSupported())
buttons.Add("Audit Trail", Wpf.Resources.view.AsBitmapImage(), item, AuditTrailClick);
}
private void AuditTrailClick(object sender, object item)
{
var entity = (TEntity)item;
var window = new AuditWindow(entity.ID);
window.ShowDialog();
}
protected override DynamicGridColumns LoadColumns()
{
return ColumnsComponent.LoadColumns();
}
protected override void SaveColumns(DynamicGridColumns columns)
{
ColumnsComponent.SaveColumns(columns);
}
protected override void LoadColumnsMenu(ContextMenu menu)
{
base.LoadColumnsMenu(menu);
ColumnsComponent.LoadColumnsMenu(menu);
}
private string GetTag()
{
var tag = typeof(TEntity).Name;
if (!string.IsNullOrWhiteSpace(ColumnsTag))
tag = string.Format("{0}.{1}", tag, ColumnsTag);
return tag;
}
protected override DynamicGridSettings LoadSettings()
{
var tag = GetTag();
var user = Task.Run(() => new UserConfiguration(tag).Load());
user.Wait();
//var global = Task.Run(() => new GlobalConfiguration(tag).Load());
//global.Wait();
//Task.WaitAll(user, global);
//var columns = user.Result.Any() ? user.Result : global.Result;
return user.Result;
}
protected override void SaveSettings(DynamicGridSettings settings)
{
var tag = GetTag();
new UserConfiguration(tag).Save(settings);
}
protected override BaseEditor? GetEditor(object item, DynamicGridColumn column)
{
var prop = DatabaseSchema.Properties(typeof(TEntity)).FirstOrDefault(x => x.Name == column.ColumnName);
if (prop != null)
return prop.Editor;
return base.GetEditor(item, column);
}
protected override object? GetEditorValue(object item, string name)
{
if (item is TEntity entity)
{
var prop = DatabaseSchema.Properties(typeof(TEntity)).FirstOrDefault(x => x.Name == name);
if (prop is CustomProperty)
{
if (entity.UserProperties.ContainsKey(name))
return entity.UserProperties[name];
return null;
}
}
return base.GetEditorValue(item, name);
}
protected override void SetEditorValue(object item, string name, object value)
{
var prop = DatabaseSchema.Properties(typeof(TEntity)).FirstOrDefault(x => x.Name == name);
if (prop is CustomProperty && item is TEntity entity)
{
entity.UserProperties[name] = value;
}
else
{
base.SetEditorValue(item, name, value);
}
}
protected bool Duplicate(
CoreRow row,
Expression> codefield,
Type[] childtypes)
{
var id = row.Get(x => x.ID);
var code = row.Get(codefield) as string;
var tasks = new List();
var itemtask = Task.Run(() =>
{
var filter = new Filter(x => x.ID).IsEqualTo(id);
var result = new Client().Load(filter).FirstOrDefault()
?? throw new Exception("Entity does not exist!");
return result;
});
tasks.Add(itemtask);
//itemtask.Wait();
Task>? codetask = null;
if (!string.IsNullOrWhiteSpace(code))
{
codetask = Task.Run(() =>
{
var columns = Columns.None().Add(codefield);
//columns.Add(codefield);
var filter = new Filter(codefield).BeginsWith(code);
var table = new Client().Query(filter, columns);
var result = table.Rows.Select(x => x.Get(codefield) as string).ToList();
return result;
});
tasks.Add(codetask);
}
//codetask.Wait();
var children = new Dictionary();
foreach (var childtype in childtypes)
{
var childtask = Task.Run(() =>
{
var prop = childtype.GetProperties().FirstOrDefault(x =>
x.PropertyType.GetInterfaces().Contains(typeof(IEntityLink)) &&
x.PropertyType.GetInheritedGenericTypeArguments().FirstOrDefault() == typeof(TEntity));
if (prop is not null)
{
var filter = Core.Filter.Create(childtype);
filter.Expression = CoreUtils.GetMemberExpression(childtype, prop.Name + ".ID");
filter.Operator = Operator.IsEqualTo;
filter.Value = id;
var sort = LookupFactory.DefineSort(childtype);
var client = ClientFactory.CreateClient(childtype);
var table = client.Query(filter, null, sort);
foreach (var r in table.Rows)
{
r["ID"] = Guid.Empty;
r[prop.Name + ".ID"] = Guid.Empty;
}
children[childtype] = table;
}
else
{
Logger.Send(LogType.Error, "", $"DynamicDataGrid<{typeof(TEntity)}>.Duplicate(): No parent property found for child type {childtype}");
}
});
tasks.Add(childtask);
//childtask.Wait();
}
//var manytomanys = CoreUtils.TypeList(
// AppDomain.CurrentDomain.GetAssemblies(),
// x => x.GetInterfaces().Any(intf => intf.IsGenericType && intf.GetGenericTypeDefinition() == typeof(IManyToMany<,>) && intf.GenericTypeArguments.Contains(typeof(T)))
//);
//Task childtask = Task.Run(() =>
//{
// var result = new CoreTable();
// result.LoadColumns(typeof(TChild));
// var children = new Client().Load(new Filter(x => linkfield).IsEqualTo(id));
// foreach (var child in children)
// {
// child.ID = Guid.Empty;
// String linkprop = CoreUtils.GetFullPropertyName(linkfield, ".");
// CoreUtils.SetPropertyValue(child, linkprop, Guid.Empty);
// var newrow = result.NewRow();
// result.LoadRow(newrow, child);
// result.Rows.Add(newrow);
// }
// return result;
//});
//tasks.Add(childtask);
Task.WaitAll(tasks.ToArray());
var item = itemtask.Result;
item.ID = Guid.Empty;
if (codetask != null)
{
var codes = codetask.Result;
var i = 1;
while (codes.Contains(string.Format("{0} ({1})", code, i)))
i++;
var codeprop = CoreUtils.GetFullPropertyName(codefield, ".");
CoreUtils.SetPropertyValue(item, codeprop, string.Format("{0} ({1})", code, i));
}
var grid = new DynamicDataGrid();
return grid.EditItems(new[] { item }, t => children.ContainsKey(t) ? children[t] : null, true);
}
private bool DoMerge(Button arg1, CoreRow[] arg2)
{
if (arg2 == null || arg2.Length <= 1)
return false;
var targetid = arg2.Last().Get(x => x.ID);
var target = arg2.Last().ToObject().ToString();
var otherids = arg2.Select(r => r.Get(x => x.ID)).Where(x => x != targetid).ToArray();
string[] others = arg2.Where(r => otherids.Contains(r.Get("ID"))).Select(x => x.ToObject().ToString()!).ToArray();
var rows = arg2.Length;
if (MessageBox.Show(
string.Format(
"This will merge the following items:\n\n- {0}\n\n into:\n\n- {1}\n\nAfter this, the items will be permanently removed.\nAre you sure you wish to do this?",
string.Join("\n- ", others),
target
),
"Merge Items Warning",
MessageBoxButton.YesNo,
MessageBoxImage.Stop) != MessageBoxResult.Yes
)
return false;
using (new WaitCursor())
{
var types = CoreUtils.Entities.Where(
x =>
x.IsClass
&& !x.IsGenericType
&& x.IsSubclassOf(typeof(Entity))
&& !x.Equals(typeof(AuditTrail))
&& !x.Equals(typeof(TEntity))
&& x.GetCustomAttribute() == null
&& x.HasInterface()
&& x.HasInterface()
).ToArray();
foreach (var type in types)
{
var props = CoreUtils.PropertyList(
type,
x =>
x.PropertyType.GetInterfaces().Contains(typeof(IEntityLink))
&& x.PropertyType.GetInheritedGenericTypeArguments().Contains(typeof(TEntity))
);
foreach (var prop in props)
{
var propname = string.Format(prop.Name + ".ID");
var filter = Core.Filter.Create(type);
filter.Expression = CoreUtils.CreateMemberExpression(type, propname);
filter.Operator = Operator.InList;
filter.Value = otherids;
var columns = Columns.None(type)
.Add("ID")
.Add(propname);
var updates = ClientFactory.CreateClient(type).Query(filter, columns).Rows.Select(r => r.ToObject(type)).ToArray();
if (updates.Any())
{
foreach (var update in updates)
CoreUtils.SetPropertyValue(update, propname, targetid);
ClientFactory.CreateClient(type).Save(updates,
string.Format("Merged {0} Records", typeof(TEntity).EntityName().Split('.').Last()));
}
}
}
var histories = new Client()
.Query(
new Filter(x => x.EntityID).InList(otherids),
Columns.None()
.Add(x => x.ID).Add(x => x.EntityID))
.ToArray();
foreach (var history in histories)
history.EntityID = targetid;
if (histories.Length != 0)
new Client().Save(histories, "");
var deletes = new List