using System;
using System.Collections.Generic;
using System.Linq;
using System.Dynamic;
using Umbraco.Core;
using Umbraco.Core.Dynamics;
using System.Collections;
using System.Reflection;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Web.Dynamics;
namespace Umbraco.Web.Models
{
///
/// Represents a collection of DynamicPublishedContent items.
///
public class DynamicPublishedContentList : DynamicObject, IEnumerable
{
private readonly List _content;
private readonly PublishedContentSet _contentSet;
internal readonly List Items;
#region Constructor
public DynamicPublishedContentList()
{
_content = new List();
_contentSet = new PublishedContentSet(_content);
Items = new List();
}
public DynamicPublishedContentList(IEnumerable items)
{
_content = items.ToList();
_contentSet = new PublishedContentSet(_content);
Items = _contentSet.Select(x => new DynamicPublishedContent(x, this)).ToList();
}
public DynamicPublishedContentList(IEnumerable items)
{
_content = items.Select(x => x.PublishedContent).ToList();
_contentSet = new PublishedContentSet(_content);
Items = _contentSet.Select(x => new DynamicPublishedContent(x, this)).ToList();
}
#endregion
#region ContentSet
// so we are ~compatible with strongly typed syntax
public DynamicPublishedContentList ToContentSet()
{
return this;
}
#endregion
#region IList (well, part of it)
///
/// Adds an item to the collection.
///
/// The item to add.
public void Add(DynamicPublishedContent dynamicContent)
{
var content = dynamicContent.PublishedContent;
_content.Add(content);
_contentSet.SourceChanged();
var setContent = _contentSet.MapContent(content);
Items.Add(new DynamicPublishedContent(setContent, this));
}
///
/// Removes an item from the collection.
///
/// The item to remove.
public void Remove(DynamicPublishedContent dynamicContent)
{
if (Items.Contains(dynamicContent) == false) return;
Items.Remove(dynamicContent);
_content.Remove(dynamicContent.PublishedContent);
_contentSet.SourceChanged();
}
#endregion
#region DynamicObject
// because we want to return DynamicNull and not null, we need to implement the index property
// via the dynamic getter and not natively - otherwise it's not possible to return DynamicNull
public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
{
result = DynamicNull.Null;
if (indexes.Length != 1)
return false;
var index = indexes[0] as int?;
if (index.HasValue == false)
return false;
if (index >= 0 && index < Items.Count)
result = Items[index.Value];
return true;
}
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
//TODO: Nowhere here are we checking if args is the correct length!
//NOTE: For many of these we could actually leave them out since we are executing custom extension methods and because
// we implement IEnumerable they will execute just fine, however, to do that will be quite a bit slower than checking here.
var firstArg = args.FirstOrDefault();
//this is to check for 'DocumentTypeAlias' vs 'NodeTypeAlias' for compatibility
if (firstArg != null && firstArg.ToString().InvariantStartsWith("NodeTypeAlias"))
{
firstArg = "DocumentTypeAlias" + firstArg.ToString().Substring("NodeTypeAlias".Length);
}
var name = binder.Name;
if (name == "Single")
{
string predicate = firstArg == null ? "" : firstArg.ToString();
var values = predicate.IsNullOrWhiteSpace() ? new object[] {} : args.Skip(1).ToArray();
var single = Single(predicate, values);
result = new DynamicPublishedContent(single);
return true;
}
if (name == "SingleOrDefault")
{
string predicate = firstArg == null ? "" : firstArg.ToString();
var values = predicate.IsNullOrWhiteSpace() ? new object[] { } : args.Skip(1).ToArray();
var single = SingleOrDefault(predicate, values);
if (single == null)
result = DynamicNull.Null;
else
result = new DynamicPublishedContent(single);
return true;
}
if (name == "First")
{
string predicate = firstArg == null ? "" : firstArg.ToString();
var values = predicate.IsNullOrWhiteSpace() ? new object[] { } : args.Skip(1).ToArray();
var first = First(predicate, values);
result = new DynamicPublishedContent(first);
return true;
}
if (name == "FirstOrDefault")
{
string predicate = firstArg == null ? "" : firstArg.ToString();
var values = predicate.IsNullOrWhiteSpace() ? new object[] { } : args.Skip(1).ToArray();
var first = FirstOrDefault(predicate, values);
if (first == null)
result = DynamicNull.Null;
else
result = new DynamicPublishedContent(first);
return true;
}
if (name == "Last")
{
string predicate = firstArg == null ? "" : firstArg.ToString();
var values = predicate.IsNullOrWhiteSpace() ? new object[] { } : args.Skip(1).ToArray();
var last = Last(predicate, values);
result = new DynamicPublishedContent(last);
return true;
}
if (name == "LastOrDefault")
{
string predicate = firstArg == null ? "" : firstArg.ToString();
var values = predicate.IsNullOrWhiteSpace() ? new object[] { } : args.Skip(1).ToArray();
var last = LastOrDefault(predicate, values);
if (last == null)
result = DynamicNull.Null;
else
result = new DynamicPublishedContent(last);
return true;
}
if (name == "Where")
{
string predicate = firstArg.ToString();
var values = args.Skip(1).ToArray();
//TODO: We are pre-resolving the where into a ToList() here which will have performance impacts if there where clauses
// are nested! We should somehow support an QueryableDocumentList!
result = new DynamicPublishedContentList(Where(predicate, values).ToList());
return true;
}
if (name == "OrderBy")
{
//TODO: We are pre-resolving the where into a ToList() here which will have performance impacts if there where clauses
// are nested! We should somehow support an QueryableDocumentList!
result = new DynamicPublishedContentList(OrderBy(firstArg.ToString()).ToList());
return true;
}
if (name == "Take")
{
result = new DynamicPublishedContentList(this.Take((int)firstArg));
return true;
}
if (name == "Skip")
{
result = new DynamicPublishedContentList(this.Skip((int)firstArg));
return true;
}
if (name == "InGroupsOf")
{
int groupSize;
if (int.TryParse(firstArg.ToString(), out groupSize))
{
result = InGroupsOf(groupSize);
return true;
}
result = DynamicNull.Null;
return true;
}
if (name == "GroupedInto")
{
int groupCount;
if (int.TryParse(firstArg.ToString(), out groupCount))
{
result = GroupedInto(groupCount);
return true;
}
result = DynamicNull.Null;
return true;
}
if (name == "GroupBy")
{
result = GroupBy(firstArg.ToString());
return true;
}
if (name == "Average" || name == "Min" || name == "Max" || name == "Sum")
{
result = Aggregate(args, name);
return true;
}
if (name == "Union")
{
// check DynamicPublishedContentList before IEnumerable<...> because DynamicPublishedContentList
// is IEnumerable<...> so ... the check on DynamicPublishedContentList would never be reached.
var firstArgAsDynamicPublishedContentList = firstArg as DynamicPublishedContentList;
if (firstArgAsDynamicPublishedContentList != null)
{
result = new DynamicPublishedContentList(Items.Union((firstArgAsDynamicPublishedContentList).Items));
return true;
}
var firstArgAsIEnumerable = firstArg as IEnumerable;
if (firstArgAsIEnumerable != null)
{
result = new DynamicPublishedContentList(Items.Union(firstArgAsIEnumerable));
return true;
}
}
if (name == "Except")
{
if ((firstArg as IEnumerable) != null)
{
result = new DynamicPublishedContentList(Items.Except(firstArg as IEnumerable, new DynamicPublishedContentIdEqualityComparer()));
return true;
}
}
if (name == "Intersect")
{
if ((firstArg as IEnumerable) != null)
{
result = new DynamicPublishedContentList(Items.Intersect(firstArg as IEnumerable, new DynamicPublishedContentIdEqualityComparer()));
return true;
}
}
if (name == "Distinct")
{
result = new DynamicPublishedContentList(Items.Distinct(new DynamicPublishedContentIdEqualityComparer()));
return true;
}
if (name == "Pluck" || name == "Select")
{
result = Pluck(args);
return true;
}
//ok, now lets try to match by member, property, extensino method
var attempt = DynamicInstanceHelper.TryInvokeMember(this, binder, args, new[]
{
typeof (IEnumerable),
typeof (DynamicPublishedContentList)
});
if (attempt.Success)
{
result = attempt.Result.ObjectResult;
//need to check the return type and possibly cast if result is from an extension method found
if (attempt.Result.Reason == DynamicInstanceHelper.TryInvokeMemberSuccessReason.FoundExtensionMethod)
{
//we don't need to cast if the result is already DynamicPublishedContentList
if (attempt.Result.ObjectResult != null && (!(attempt.Result.ObjectResult is DynamicPublishedContentList)))
{
if (attempt.Result.ObjectResult is IPublishedContent)
{
result = new DynamicPublishedContent((IPublishedContent)attempt.Result.ObjectResult);
}
else if (attempt.Result.ObjectResult is IEnumerable)
{
result = new DynamicPublishedContentList((IEnumerable)attempt.Result.ObjectResult);
}
else if (attempt.Result.ObjectResult is IEnumerable)
{
result = new DynamicPublishedContentList((IEnumerable)attempt.Result.ObjectResult);
}
}
}
return true;
}
//this is the result of an extension method execution gone wrong so we return dynamic null
if (attempt.Result.Reason == DynamicInstanceHelper.TryInvokeMemberSuccessReason.FoundExtensionMethod
&& attempt.Exception != null && attempt.Exception is TargetInvocationException)
{
result = DynamicNull.Null;
return true;
}
result = null;
return false;
}
#endregion
#region Linq and stuff
private T Aggregate(IEnumerable data, string name) where T : struct
{
switch (name)
{
case "Min":
return data.Min();
case "Max":
return data.Max();
case "Average":
if (typeof(T) == typeof(int))
{
return (T)Convert.ChangeType((data as List).Average(), typeof(T));
}
if (typeof(T) == typeof(decimal))
{
return (T)Convert.ChangeType((data as List).Average(), typeof(T));
}
break;
case "Sum":
if (typeof(T) == typeof(int))
{
return (T)Convert.ChangeType((data as List).Sum(), typeof(T));
}
if (typeof(T) == typeof(decimal))
{
return (T)Convert.ChangeType((data as List).Sum(), typeof(T));
}
break;
}
return default(T);
}
private object Aggregate(object[] args, string name)
{
object result;
string predicate = args.First().ToString();
var values = args.Skip(1).ToArray();
var query = (IQueryable