using InABox.Clients;
using InABox.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace InABox.DynamicGrid;
///
/// Defines a common interface for dealing with grids like
/// or , which display s, but do not load them necessarily from the database,
/// instead keeping them in memory.
///
/// This interface then allows other functions, like
/// , to work based on and manage our column handling better.
///
///
public interface IDynamicMemoryEntityGrid
where T : Entity, IRemotable, IPersistent, new()
{
///
/// A set of columns representing which columns have been loaded from the database.
///
///
/// This is used to refresh the data when the columns change.
///
/// It is if no data has been loaded from the database (that is, the data was gotten from
/// a page data handler instead.)
///
public HashSet? LoadedColumns { get; }
public IEnumerable Items { get; }
}
public static class DynamicMemoryEntityGridExtensions
{
public static void EnsureColumns(this IDynamicMemoryEntityGrid grid, Columns columns)
where T : Entity, IRemotable, IPersistent, new()
{
RequireColumns(grid, columns);
LoadForeignProperties(grid, columns);
}
///
/// Load the properties of any s on this where the is not .
/// This allows us to populate columns of transient objects, as long as they are linked by the ID. What this actually then does is query each
/// linked table with the required columns.
///
///
public static void LoadForeignProperties(this IDynamicMemoryEntityGrid grid, Columns columns)
where T : Entity, IRemotable, IPersistent, new()
{
// Lists of properties that we need, arranged by the entity link property which is their parent.
// LinkIDProperty : (Type, Properties: [(columnName, property)], Objects)
var newData = new Dictionary>, HashSet>>();
foreach (var column in columns)
{
var property = DatabaseSchema.Property(typeof(T), column.Property);
if (property?.GetOuterParent(x => x.IsEntityLink) is IProperty linkProperty)
{
var remaining = column.Property[(linkProperty.Name.Length + 1)..];
if (remaining.Equals(nameof(IEntityLink.ID)))
{
// This guy isn't foreign, so we don't pull him.
continue;
}
var idProperty = DatabaseSchema.Property(typeof(T), linkProperty.Name + "." + nameof(IEntityLink.ID))!;
var linkType = linkProperty.PropertyType.GetInterfaceDefinition(typeof(IEntityLink<>))!.GenericTypeArguments[0];
if (!newData.TryGetValue(idProperty, out var data))
{
data = new Tuple>, HashSet>(
linkType,
new List>(),
new HashSet());
newData.Add(idProperty, data);
}
var any = false;
foreach (var item in grid.Items)
{
if (!item.LoadedColumns.Contains(column.Property))
{
var linkID = (Guid)idProperty.Getter()(item);
if (linkID != Guid.Empty)
{
any = true;
data.Item3.Add(item);
}
}
}
if (any)
{
data.Item2.Add(new(remaining, property));
}
}
}
var queryDefs = new List();
foreach (var (prop, data) in newData)
{
if (data.Item2.Count != 0)
{
var ids = data.Item3.Select(prop.Getter()).Cast().ToArray();
queryDefs.Add(new KeyedQueryDef(prop.Name, data.Item1,
Filter.Create(data.Item1, x => x.ID).InList(ids),
Columns.None(data.Item1)
.Add(data.Item2.Select(x => x.Item1))
.Add(x => x.ID)));
}
}
var results = Client.QueryMultiple(queryDefs);
foreach(var (prop, data) in newData)
{
var table = results.GetOrDefault(prop.Name);
if(table is null)
{
continue;
}
foreach (var entity in data.Item3)
{
var linkID = (Guid)prop.Getter()(entity);
var row = table.Rows.FirstOrDefault(x => x.Get(x => x.ID) == linkID);
if (row is not null)
{
foreach (var (name, property) in data.Item2)
{
if (!entity.LoadedColumns.Contains(property.Name))
{
property.Setter()(entity, row[name]);
entity.LoadedColumns.Add(property.Name);
}
}
}
}
}
}
public static void RequireColumns(this IDynamicMemoryEntityGrid grid, Columns columns)
where T : Entity, IRemotable, IPersistent, new()
{
if (grid.LoadedColumns is null) return;
// Figure out which columns we still need.
var newColumns = columns.Where(x => !grid.LoadedColumns.Contains(x.Property)).ToColumns(ColumnTypeFlags.None);
if (newColumns.Count > 0 && typeof(T).GetCustomAttribute() is null)
{
var data = Client.Query(
new Filter(x => x.ID).InList(grid.Items.Select(x => x.ID).Where(x => x != Guid.Empty).ToArray()),
// We also need to add ID, so we know which item to fill.
newColumns.Add(x => x.ID));
foreach (var row in data.Rows)
{
var item = grid.Items.FirstOrDefault(x => x.ID == row.Get(y => y.ID));
if (item is not null)
{
row.FillObject(item, overrideExisting: false);
}
}
// Remember that we have now loaded this data.
foreach (var column in newColumns)
{
grid.LoadedColumns.Add(column.Property);
}
}
}
}