using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using InABox.Clients;
using InABox.Core;
namespace InABox.Core
{
public class Credentials
{
public virtual string UserID { get; set; }
public virtual string Password { get; set; }
}
public interface IEntity
{
Guid ID { get; set; }
Guid Deleted { get; set; }
bool IsChanged();
void CommitChanges();
void CancelChanges();
}
public interface ITaxable
{
double ExTax { get; set; }
double TaxRate { get; set; }
double Tax { get; set; }
double IncTax { get; set; }
}
public interface IIssues
{
string Issues { get; set; }
}
public interface IExportable
{
}
public interface IImportable
{
}
///
/// Indicate that an is able to be merged together.
///
///
/// It is recommended that an that implements this should provide a implementation.
///
public interface IMergeable
{
}
public interface ISecure { }
public interface IDuplicatable
{
IEntityDuplicator GetDuplicator();
}
public interface IEntityDuplicator
{
//void Duplicate(IFilter filter);
void Duplicate(IEnumerable entities);
}
public class EntityDuplicator : IEntityDuplicator where TEntity : Entity, IRemotable, IPersistent
{
private interface IRelationship
{
Type ParentType { get; }
Type ChildType { get; }
IFilter GetFilter(Entity parent);
}
private class EntityLinkRelationship : IRelationship
{
public Type ParentType => typeof(TParent);
public Type ChildType => typeof(TChild);
public Column Column { get; set; }
public IFilter GetFilter(Entity parent)
{
return new Filter(Column).IsEqualTo(parent.ID);
}
}
private class GenericRelationship : IRelationship
where TParent : Entity
{
public Type ParentType => typeof(TParent);
public Type ChildType => typeof(TChild);
public Column Column { get; set; }
public Func Func { get; set; }
public IFilter GetFilter(Entity parent)
{
return new Filter(Column).IsEqualTo(Func(parent as TParent));
}
}
private readonly List _relationships = new List();
public void Duplicate(IEnumerable entites) =>
Duplicate(typeof(TEntity),
new Filter(x => x.ID).InList(entites.Select(x => x.ID).ToArray()));
private void Duplicate(Type parent, IFilter filter)
{
var table = ClientFactory.CreateClient(parent)
.Query(filter, Columns.Local(parent));
foreach (var row in table.Rows)
{
var update = (row.ToObject(parent) as Entity)!;
var id = update.ID;
update.ID = Guid.Empty;
update.CommitChanges();
ClientFactory.CreateClient(parent).Save(update, "Duplicated Record");
foreach (var relationship in _relationships.Where(x => x.ParentType == parent))
{
Duplicate(relationship.ChildType, relationship.GetFilter(update));
}
}
}
public void AddChild(Expression> childkey)
where TParent : Entity, IRemotable, IPersistent
where TChild : Entity, IRemotable, IPersistent
where TParentLink : IEntityLink
{
_relationships.Add(new EntityLinkRelationship
{
Column = new Column(CoreUtils.GetFullPropertyName(childkey, ".") + ".ID")
});
}
public void AddChild(Column linkColumn, Func value)
where TParent : Entity, IRemotable, IPersistent
where TChild : Entity, IRemotable, IPersistent
{
_relationships.Add(new GenericRelationship
{
Column = linkColumn,
Func = value
});
}
void IEntityDuplicator.Duplicate(IEnumerable entities) => Duplicate(entities.Cast());
}
///
/// An is required if it has the defined on it.
/// If it is part of an (or ), then it is only required
/// if the property on the parent class also has .
///
public class RequiredColumnAttribute : Attribute { }
public abstract class Entity : BaseObject, IEntity
{
private bool bTaxing;
//public String Name { get; set; }
[TimestampEditor(Visible = Visible.Optional, Editable = Editable.Hidden)]
[RequiredColumn]
public virtual DateTime LastUpdate { get; set; } = DateTime.Now;
[CodeEditor(Visible = Visible.Optional, Editable = Editable.Hidden)]
[RequiredColumn]
public string LastUpdateBy { get; set; } = ClientFactory.UserID;
[NullEditor]
[RequiredColumn]
public virtual DateTime Created { get; set; } = DateTime.Now;
[NullEditor]
[RequiredColumn]
public virtual string CreatedBy { get; set; } = ClientFactory.UserID;
[NullEditor]
[RequiredColumn]
public Guid ID { get; set; } = Guid.Empty;
///
/// If the entity is deleted, holds the ID of the Deletion. Otherwise, it holds Guid.Empty
///
[NullEditor]
[DoNotSerialize]
[Obsolete]
public Guid Deleted { get; set; } = Guid.Empty;
public static Type ClassVersion(Type t)
{
//Type t = MethodBase.GetCurrentMethod().DeclaringType;
var ti = t.GetTypeInfo();
var interfaces = ti.GetInterfaces();
if (ti.GetInterfaces().Contains(typeof(IPersistent)))
{
if (ti.BaseType != null)
throw new Exception(t.Name + " hase no Base Type");
if (ti.BaseType.Equals(typeof(Entity)))
throw new Exception(t.Name + " may not derive directly from TEntity");
var props = t.GetTypeInfo().GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance);
if (props.Count() > 0)
throw new Exception(t.Name + "may not declare properties");
}
return t.GetTypeInfo().BaseType;
}
//[NullEditor]
//public List History { get; set; }
//public Entity() : base()
//{
// CommitChanges();
//}
//public Entity(Guid id) : base()
//{
// ID = id;
// History = new List();
// UserProperties = new Dictionary();
// DataModel.InitializeEntity(this);
// CheckSequence();
// CommitChanges();
//}
public static bool IsEntityLinkValid(Expression> expression, CoreRow arg) where U : IEntityLink
{
return arg.IsEntityLinkValid(expression);
}
///
/// Gets the ID of an entity link of an entity, doing a validity check (see )
///
/// The entity type
/// The entity link type
/// An expression to the entity link of type
/// The row representing the entity of type
/// The ID on the entity link, or null if the entity link is invalid
public static Guid? EntityLinkID(Expression> expression, CoreRow arg) where U : IEntityLink
{
var col = CoreUtils.GetFullPropertyName(expression, ".");
var id = arg.Get(col + ".ID");
if (id != Guid.Empty && arg.Get(col + ".Deleted") == Guid.Empty)
return id;
return null;
}
protected override void SetChanged(string name, object? before, object? after)
{
base.SetChanged(name, before, after);
CheckTax(name, before, after);
}
private void CheckTax(string name, object? before, object? after)
{
if (this is ITaxable taxable && !bTaxing)
{
bTaxing = true;
try
{
if (name.Equals("ExTax"))
{
taxable.Tax = (double)after * (taxable.TaxRate / 100.0F);
taxable.IncTax = (double)after + taxable.Tax;
}
else if (name.Equals("TaxRate"))
{
taxable.Tax = taxable.ExTax * ((double)after / 100.0F);
taxable.IncTax = taxable.ExTax + taxable.Tax;
}
else if (name.Equals("Tax"))
{
taxable.ExTax = taxable.IncTax - (double)after;
}
else if (name.Equals("IncTax"))
{
taxable.ExTax = (double)after / ((100.0F + taxable.TaxRate) / 100.0F);
taxable.Tax = (double)after - taxable.ExTax;
}
}
catch (Exception e)
{
Logger.Send(LogType.Error, "", String.Join("\n",e.Message,e.StackTrace));
}
bTaxing = false;
}
}
protected override void DoPropertyChanged(string name, object? before, object? after)
{
if (!IsObserving())
return;
//CheckSequence();
if (!name.Equals("LastUpdate"))
LastUpdate = DateTime.Now;
LastUpdateBy = ClientFactory.UserID;
// This doesn;t work - keeps being updated to current date
// Created => null ::Set ID = guid.empty -> now :: any other change -> unchanged!
// Moved to Create(), should not simply be overwritten on deserialise from json
//if (Created.Equals(DateTime.MinValue))
//{
// Created = DateTime.Now;
// CreatedBy = ClientFactory.UserID;
//}
}
}
public interface ILicense where TLicenseToken : LicenseToken
{
}
public interface IPersistent
{
}
public interface IRemotable
{
}
//public interface IRemoteQuery
//{
//}
//public interface IRemoteUpdate
//{
//}
//public interface IRemoteDelete
//{
//}
public interface ISequenceable
{
long Sequence { get; set; }
}
public interface IAutoIncrement
{
Expression> AutoIncrementField();
Filter? AutoIncrementFilter();
}
public interface INumericAutoIncrement : IAutoIncrement
{
}
public interface IStringAutoIncrement
{
string AutoIncrementPrefix();
string AutoIncrementFormat();
}
public interface IStringAutoIncrement : IAutoIncrement, IStringAutoIncrement
{
}
///
/// Used to flag an entity as exhibiting the properties of a ManyToMany relationship, allowing PRS to auto-generate things like grids and datamodels based on
/// entity relationships.
///
///
/// This will cause a ManyToMany grid of to appear on all editors.
/// Hence, if one wishes to cause both grids to appear (that is, for to appear for and
/// vice versa, one must flag the entity with both IManyToMany<, > and
/// IManyToMany<, >.
///
///
///
public interface IManyToMany where TLeft : Entity where TRight : Entity
{
}
public interface IOneToMany where TOne : Entity
{
}
public static class EntityFactory
{
public delegate object ObjectActivator(params object[] args);
private static readonly Dictionary _cache = new Dictionary();
public static ObjectActivator GetActivator(ConstructorInfo ctor)
{
var type = ctor.DeclaringType;
var paramsInfo = ctor.GetParameters();
//create a single param of type object[]
var param =
Expression.Parameter(typeof(object[]), "args");
var argsExp =
new Expression[paramsInfo.Length];
//pick each arg from the params array
//and create a typed expression of them
for (var i = 0; i < paramsInfo.Length; i++)
{
Expression index = Expression.Constant(i);
var paramType = paramsInfo[i].ParameterType;
Expression paramAccessorExp =
Expression.ArrayIndex(param, index);
Expression paramCastExp =
Expression.Convert(paramAccessorExp, paramType);
argsExp[i] = paramCastExp;
}
//make a NewExpression that calls the
//ctor with the args we just created
var newExp = Expression.New(ctor, argsExp);
//create a lambda with the New
//Expression as body and our param object[] as arg
var lambda =
Expression.Lambda(typeof(ObjectActivator), newExp, param);
//compile it
var compiled = (ObjectActivator)lambda.Compile();
return compiled;
}
public static T CreateEntity() where T : BaseObject
{
if (!_cache.ContainsKey(typeof(T)))
{
var ctor = typeof(T).GetConstructors().First();
_cache[typeof(T)] = GetActivator(ctor);
}
var createdActivator = _cache[typeof(T)];
return (T)createdActivator();
}
public static object CreateEntity(Type type)
{
if (!_cache.ContainsKey(type))
{
var ctor = type.GetConstructors().First();
var activator = typeof(EntityFactory).GetMethod("GetActivator").MakeGenericMethod(type);
_cache[type] = (ObjectActivator)activator.Invoke(null, new object[] { ctor });
}
var createdActivator = _cache[type];
return createdActivator();
}
}
}