You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

1385 lines
53 KiB

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SunlightCentralizedControlManagement_SCCM_.UserClass
{
/// <summary>
/// Wrapper ProjectManager class
/// </summary>
[Serializable]
public class ProjectManager : ProjectManager<IP_Task, object>
{
}
/// <summary>
/// Concrete ProjectManager class for the IProjectManager interface
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="R"></typeparam>
[Serializable]
public class ProjectManager<T, R> : IProjectManager<T, R>
where T : IP_Task
where R : class
{
HashSet<T> _mRegister = new HashSet<T>();
List<T> _mRootIP_Tasks = new List<T>();
Dictionary<T, List<T>> _mMembersOfGroup = new Dictionary<T, List<T>>(); // Map group to list of members
Dictionary<T, HashSet<T>> _mDependantsOfPrecedent = new Dictionary<T, HashSet<T>>(); // Map precendent to list of dependents
Dictionary<T, HashSet<R>> _mResourcesOfIP_Task = new Dictionary<T, HashSet<R>>(); // Map IP_Task to list of resources
Dictionary<T, List<T>> _mPartsOfSplitIP_Task = new Dictionary<T, List<T>>(); // Map split IP_Task to list of IP_Task parts
Dictionary<T, T> _mSplitIP_TaskOfPart = new Dictionary<T, T>(); // Map a IP_Task part to the original split IP_Task
Dictionary<T, T> _mGroupOfMember = new Dictionary<T, T>(); // Map member IP_Task to parent group IP_Task
Dictionary<T, int> _mIP_TaskIndices = new Dictionary<T, int>(); // Map the IP_Task to its zero-based index order position
/// <summary>
/// Create a new Project
/// </summary>
public ProjectManager()
{
Now = TimeSpan.Zero;
Start = DateTime.Now;
}
/// <summary>
/// Get or set the TimeSpan we are at now from Start DateTime
/// </summary>
public TimeSpan Now { get; set; }
/// <summary>
/// Get or set the starting date for this project
/// </summary>
public DateTime Start { get; set; }
/// <summary>
/// Get the date after the specified TimeSpan
/// </summary>
/// <param name="span"></param>
/// <returns></returns>
public DateTime GetDateTime(TimeSpan span)
{
return this.Start.Add(span);
}
/// <summary>
/// Create a new T for this Project and add it to the T tree
/// </summary>
/// <returns></returns>
public void Add(T IP_Task)
{
if (!this._mRegister.Contains(IP_Task))
{
_mRegister.Add(IP_Task);
_mRootIP_Tasks.Add(IP_Task);
_mMembersOfGroup[IP_Task] = new List<T>();
_mDependantsOfPrecedent[IP_Task] = new HashSet<T>();
_mResourcesOfIP_Task[IP_Task] = new HashSet<R>();
_mGroupOfMember[IP_Task] = null;
}
}
/// <summary>
/// Remove IP_Task from this Project
/// </summary>
/// <param name="IP_Task"></param>
public void Delete(T IP_Task)
{
if (IP_Task != null
&& !_mSplitIP_TaskOfPart.ContainsKey(IP_Task) // not a IP_Task part
)
{
// Check if is group so can ungroup the IP_Task
if (this.IsGroup(IP_Task))
this.Ungroup(IP_Task);
if (this.IsSplit(IP_Task))
this.Merge(IP_Task);
// Really delete all references
_mRootIP_Tasks.Remove(IP_Task);
_mMembersOfGroup.Remove(IP_Task);
_mDependantsOfPrecedent.Remove(IP_Task);
_mResourcesOfIP_Task.Remove(IP_Task);
_mGroupOfMember.Remove(IP_Task);
_mPartsOfSplitIP_Task.Remove(IP_Task);
foreach (var g in _mMembersOfGroup) g.Value.Remove(IP_Task); // optimised: no need to check for contains
foreach (var g in _mDependantsOfPrecedent) g.Value.Remove(IP_Task);
_mRegister.Remove(IP_Task);
}
else if (IP_Task != null
&& _mSplitIP_TaskOfPart.ContainsKey(IP_Task) // must be existing part
)
{
var split = _mSplitIP_TaskOfPart[IP_Task];
var parts = _mPartsOfSplitIP_Task[split];
if (parts.Count > 2)
{
parts.Remove(IP_Task); // remove the part from the split IP_Task
_mRegister.Remove(IP_Task); // unregister the part
_mResourcesOfIP_Task.Remove(IP_Task);
_mSplitIP_TaskOfPart.Remove(IP_Task); // remove the reverse lookup
split.Start = parts.First().Start; // recalculate the split IP_Task
split.End = parts.Last().End;
split.Duration = split.End - split.Start;
}
else
{
this.Merge(split);
}
}
}
/// <summary>
/// Add the member T to the group T
/// </summary>
/// <param name="group"></param>
/// <param name="member"></param>
public void Group(T group, T member)
{
if (group != null
&& member != null
&& _mRegister.Contains(group)
)
{
// if the member is a IP_Task part, assign the split IP_Task to the group instead
if (_mSplitIP_TaskOfPart.ContainsKey(member)) member = _mSplitIP_TaskOfPart[member];
if (_mRegister.Contains(member)
&& !group.Equals(member)
&& !_mPartsOfSplitIP_Task.ContainsKey(group) // group cannot be split IP_Task
&& !_mSplitIP_TaskOfPart.ContainsKey(group) // group cannot be parts
&& !this.MembersOf(member).Contains(group)
&& !this.HasRelations(group)
)
{
_DetachIP_Task(member);
// add member to new group
_mMembersOfGroup[group].Add(member);
_mGroupOfMember[member] = group;
_RecalculateAncestorsSchedule();
_RecalculateSlack();
// clear indices since positions changed
_mIP_TaskIndices.Clear();
}
}
}
/// <summary>
/// Remove the member IP_Task from its group
/// </summary>
public void Ungroup(T group, T member)
{
if (group != null
&& member != null
&& _mRegister.Contains(group)
)
{
// change the member to become the split IP_Task is member is a IP_Task part
if (_mSplitIP_TaskOfPart.ContainsKey(member)) member = _mSplitIP_TaskOfPart[member];
if (_mRegister.Contains(member) && this.IsGroup(group))
{
var ancestor = this.GroupsOf(group).LastOrDefault();
if(ancestor == null) // group is in root
_mRootIP_Tasks.Insert(_mRootIP_Tasks.IndexOf(group) + 1, member);
else // group is not in root, we get the ancestor that is in root
_mRootIP_Tasks.Insert(_mRootIP_Tasks.IndexOf(ancestor) + 1, member);
_mMembersOfGroup[group].Remove(member);
_mGroupOfMember[member] = null;
_RecalculateAncestorsSchedule();
}
}
}
/// <summary>
/// Ungroup all member IP_Task under the specfied group IP_Task. The specified group IP_Task will become a normal IP_Task.
/// If the there is a grandparent group, the members will become members of the grandparent group.
/// </summary>
/// <param name="group"></param>
public void Ungroup(T group)
{
if (group != null
//&& _mRegister.Contains(group)
&& _mMembersOfGroup.TryGetValue(group, out List<T> members))
{
var newgroup = this.DirectGroupOf(group);
if (newgroup == null)
{
foreach (var member in members)
{
_mRootIP_Tasks.Add(member);
_mGroupOfMember[member] = null;
}
}
else
{
foreach (var member in members)
{
_mMembersOfGroup[newgroup].Add(member);
_mGroupOfMember[member] = null;
}
}
members.Clear();
_RecalculateAncestorsSchedule();
}
}
/// <summary>
/// Get the zero-based index of the IP_Task in this Project
/// </summary>
/// <param name="IP_Task"></param>
/// <returns></returns>
public int IndexOf(T IP_Task)
{
if (_mRegister.Contains(IP_Task))
{
if (_mIP_TaskIndices.ContainsKey(IP_Task))
return _mIP_TaskIndices[IP_Task];
int i = 0;
foreach (var x in IP_Tasks)
{
if (x.Equals(IP_Task))
{
_mIP_TaskIndices[IP_Task] = i;
return i;
}
i++;
}
}
return -1;
}
/// <summary>
/// Re-order position of the IP_Task by offset amount of places
/// If IP_Task is moved between members, the IP_Task is added to the members' group
/// If IP_Task is a member and it is moved above it's group or below last sibling member, then it is moved out of its group
/// If IP_Task is a part, then its parent split-IP_Task will be move instead
/// </summary>
/// <param name="IP_Task"></param>
/// <param name="offset"></param>
public void Move(T IP_Task, int offset)
{
if (IP_Task != null && _mRegister.Contains(IP_Task) && offset != 0)
{
if (IsPart(IP_Task)) IP_Task = SplitIP_TaskOf(IP_Task);
int indexofIP_Task = IndexOf(IP_Task);
if (indexofIP_Task > -1)
{
int newindexofIP_Task = indexofIP_Task + offset;
// check for out of index bounds
var IP_Taskcount = IP_Tasks.Count();
if (newindexofIP_Task < 0) newindexofIP_Task = 0;
else if (newindexofIP_Task > IP_Taskcount) newindexofIP_Task = IP_Taskcount;
// get the index of the IP_Task that will be displaced
var displacedIP_Task = IP_Tasks.ElementAtOrDefault(newindexofIP_Task);
if(displacedIP_Task == IP_Task)
{
return;
}
if (displacedIP_Task == null)
{
// adding to the end of the IP_Task list
_DetachIP_Task(IP_Task);
_mRootIP_Tasks.Add(IP_Task);
}
else if (!displacedIP_Task.Equals(IP_Task))
{
int indexofdestinationIP_Task;
var displacedIP_Taskparent = this.DirectGroupOf(displacedIP_Task);
if (displacedIP_Taskparent == null) // displacedIP_Task is in root
{
indexofdestinationIP_Task = _mRootIP_Tasks.IndexOf(displacedIP_Task);
_DetachIP_Task(IP_Task);
_mRootIP_Tasks.Insert(indexofdestinationIP_Task, IP_Task);
}
else if (!displacedIP_Taskparent.Equals(IP_Task)) // displaced IP_Task is not under the moving IP_Task
{
var memberlist = _mMembersOfGroup[displacedIP_Taskparent];
indexofdestinationIP_Task = memberlist.IndexOf(displacedIP_Task);
_DetachIP_Task(IP_Task);
memberlist.Insert(indexofdestinationIP_Task, IP_Task);
_mGroupOfMember[IP_Task] = displacedIP_Taskparent;
}
}
_RecalculateAncestorsSchedule();
_RecalculateSlack();
// clear indices since positions changed
_mIP_TaskIndices.Clear();
}
}
}
/// <summary>
/// Get the T tree
/// </summary>
public IEnumerable<T> IP_Tasks
{
get
{
var stack = new Stack<T>(1024);
var rstack = new Stack<T>(30);
foreach (var IP_Task in _mRootIP_Tasks)
{
stack.Push(IP_Task);
while (stack.Count > 0)
{
var visited = stack.Pop();
yield return visited;
foreach (var member in _mMembersOfGroup[visited])
rstack.Push(member);
while (rstack.Count > 0) stack.Push(rstack.Pop());
}
}
}
}
/// <summary>
/// Enumerate upwards from member to and through all the parents and grandparents of the specified IP_Task
/// </summary>
public IEnumerable<T> GroupsOf(T member)
{
T parent = DirectGroupOf(member);
while (parent != null)
{
yield return parent;
parent = DirectGroupOf(parent);
}
}
/// <summary>
/// Enumerate through all the children and grandchildren of the specified group
/// </summary>
/// <param name="group"></param>
/// <returns></returns>
public IEnumerable<T> MembersOf(T group)
{
if (_mRegister.Contains(group))
{
Stack<T> stack = new Stack<T>(20);
Stack<T> rstack = new Stack<T>(10);
foreach (var child in _mMembersOfGroup[group])
{
stack.Push(child);
while (stack.Count > 0)
{
var visitedchild = stack.Pop();
yield return visitedchild;
// push the grandchild
rstack.Clear();
foreach (var grandchild in _mMembersOfGroup[visitedchild])
rstack.Push(grandchild);
// put in the right visiting order
while (rstack.Count > 0)
stack.Push(rstack.Pop());
}
}
}
}
/// <summary>
/// Get the parent group of the specified IP_Task
/// </summary>
/// <param name="member"></param>
/// <returns></returns>
public T DirectGroupOf(T member)
{
if (_mGroupOfMember.ContainsKey(member)) // _mRegister.Contains(IP_Task))
{
return _mGroupOfMember[member];
}
else
{
return null;
}
}
/// <summary>
/// Enumerate through all the direct children of the specified group
/// </summary>
/// <param name="group"></param>
/// <returns></returns>
public IEnumerable<T> DirectMembersOf(T group)
{
if (group == null) yield break;
if (_mMembersOfGroup.TryGetValue(group, out List<T> list))
{
var iter = list.GetEnumerator();
while (iter.MoveNext()) yield return iter.Current;
}
}
/// <summary>
/// Enumerate through all the direct precedents and indirect precedents of the specified IP_Task
/// </summary>
/// <param name="dependant"></param>
/// <returns></returns>
public IEnumerable<T> PrecedentsOf(T dependant)
{
if (_mRegister.Contains(dependant))
{
var stack = new Stack<T>(20);
foreach (var p in DirectPrecedentsOf(dependant))
{
stack.Push(p);
while (stack.Count > 0)
{
var visited = stack.Pop();
yield return visited;
foreach (var grandp in DirectPrecedentsOf(visited))
stack.Push(grandp);
}
}
}
}
/// <summary>
/// Enumerate through all the direct dependants and indirect dependants of the specified IP_Task
/// </summary>
/// <param name="precendent"></param>
/// <returns></returns>
public IEnumerable<T> DependantsOf(T precendent)
{
if (!_mDependantsOfPrecedent.ContainsKey(precendent)) yield break;
var stack = new Stack<T>(20);
foreach (var d in _mDependantsOfPrecedent[precendent])
{
stack.Push(d);
while (stack.Count > 0)
{
var visited = stack.Pop();
yield return visited;
foreach (var grandd in _mDependantsOfPrecedent[visited])
stack.Push(grandd);
}
}
}
/// <summary>
/// Enumerate through all the direct precedents of the specified IP_Task
/// </summary>
/// <param name="dependants"></param>
/// <returns></returns>
public IEnumerable<T> DirectPrecedentsOf(T dependants)
{
return _mDependantsOfPrecedent.Where(x => x.Value.Contains(dependants)).Select(x => x.Key);
}
/// <summary>
/// Enumerate through all the direct dependants of the specified IP_Task
/// </summary>
/// <param name="precedent"></param>
/// <returns></returns>
public IEnumerable<T> DirectDependantsOf(T precedent)
{
if (precedent == null) yield break;
if (_mDependantsOfPrecedent.TryGetValue(precedent, out HashSet<T> dependants))
{
var iter = dependants.GetEnumerator();
while (iter.MoveNext()) yield return iter.Current;
}
}
/// <summary>
/// Enumerate through all IP_Tasks that is a precedent, having dependants.
/// </summary>
public IEnumerable<T> Precedents
{
get { return _mDependantsOfPrecedent.Where(x => _mDependantsOfPrecedent[x.Key].Count > 0).Select(x => x.Key); }
}
/// <summary>
/// Enumerate list of critical paths in Project
/// </summary>
public IEnumerable<IEnumerable<T>> CriticalPaths
{
get
{
Dictionary<TimeSpan, List<T>> endtimelookp = new Dictionary<TimeSpan, List<T>>(1024);
TimeSpan max_end = TimeSpan.MinValue;
foreach (var IP_Task in this.IP_Tasks)
{
if (!endtimelookp.TryGetValue(IP_Task.End, out List<T> list))
endtimelookp[IP_Task.End] = new List<T>(10);
endtimelookp[IP_Task.End].Add(IP_Task);
if (IP_Task.End > max_end) max_end = IP_Task.End;
}
if (max_end != TimeSpan.MinValue)
{
foreach (var IP_Task in endtimelookp[max_end])
{
yield return new T[] { IP_Task }.Concat(PrecedentsOf(IP_Task));
}
}
}
}
/// <summary>
/// Get whether the specified IP_Task is a group
/// </summary>
/// <param name="IP_Task"></param>
/// <returns></returns>
public bool IsGroup(T IP_Task)
{
if (_mMembersOfGroup.TryGetValue(IP_Task, out List<T> list))
return list.Count > 0;
else
return false;
}
/// <summary>
/// Get whether the specified IP_Task is a member
/// </summary>
/// <param name="IP_Task"></param>
/// <returns></returns>
public bool IsMember(T IP_Task)
{
return this.DirectGroupOf(IP_Task) != null;
}
/// <summary>
/// Get whether the specified IP_Task has relations, either has dependants or has precedents connecting to it.
/// </summary>
/// <param name="IP_Task"></param>
/// <returns></returns>
public bool HasRelations(T IP_Task)
{
if (_mRegister.Contains(IP_Task) && _mDependantsOfPrecedent.ContainsKey(IP_Task))
{
return _mDependantsOfPrecedent[IP_Task].Count > 0 || DirectPrecedentsOf(IP_Task).FirstOrDefault() != null;
}
else
{
return false;
}
}
/// <summary>
/// Set a relation between the precedent and dependant IP_Task
/// </summary>
/// <param name="precedent"></param>
/// <param name="dependant"></param>
public void Relate(T precedent, T dependant)
{
if (_mRegister.Contains(precedent)
&& _mRegister.Contains(dependant)
)
{
if (_mSplitIP_TaskOfPart.ContainsKey(precedent)) precedent = _mSplitIP_TaskOfPart[precedent];
if (_mSplitIP_TaskOfPart.ContainsKey(dependant)) dependant = _mSplitIP_TaskOfPart[dependant];
if (!precedent.Equals(dependant)
&&!this.DependantsOf(dependant).Contains(precedent)
&& !this.IsGroup(precedent)
&& !this.IsGroup(dependant)
)
{
_mDependantsOfPrecedent[precedent].Add(dependant);
_RecalculateDependantsOf(precedent);
_RecalculateAncestorsSchedule();
_RecalculateSlack();
}
}
}
/// <summary>
/// Unset the relation between the precedent and dependant IP_Task, if any.
/// </summary>
/// <param name="precedent"></param>
/// <param name="dependant"></param>
public void Unrelate(T precedent, T dependant)
{
if (_mRegister.Contains(precedent) && _mRegister.Contains(dependant))
{
if (_mSplitIP_TaskOfPart.ContainsKey(precedent)) precedent = _mSplitIP_TaskOfPart[precedent];
if (_mSplitIP_TaskOfPart.ContainsKey(dependant)) dependant = _mSplitIP_TaskOfPart[dependant];
_mDependantsOfPrecedent[precedent].Remove(dependant);
_RecalculateSlack();
}
}
/// <summary>
/// Remove all dependant IP_Task from specified precedent IP_Task
/// </summary>
/// <param name="precedent"></param>
public void Unrelate(T precedent)
{
if (_mRegister.Contains(precedent))
{
if (_mSplitIP_TaskOfPart.ContainsKey(precedent))
precedent = _mSplitIP_TaskOfPart[precedent];
_mDependantsOfPrecedent[precedent].Clear();
_RecalculateSlack();
}
}
/// <summary>
/// Assign the specified resource to the specified IP_Task
/// </summary>
/// <param name="IP_Task"></param>
/// <param name="resource"></param>
public void Assign(T IP_Task, R resource)
{
if (_mRegister.Contains(IP_Task) && !_mResourcesOfIP_Task[IP_Task].Contains(resource))
_mResourcesOfIP_Task[IP_Task].Add(resource);
}
/// <summary>
/// Unassign the specified resource from the specfied IP_Task
/// </summary>
/// <param name="IP_Task"></param>
/// <param name="resource"></param>
public void Unassign(T IP_Task, R resource)
{
_mResourcesOfIP_Task[IP_Task].Remove(resource);
}
/// <summary>
/// Unassign the all resources from the specfied IP_Task
/// </summary>
/// <param name="IP_Task"></param>
public void Unassign(T IP_Task)
{
if(_mRegister.Contains(IP_Task))
_mResourcesOfIP_Task[IP_Task].Clear();
}
/// <summary>
/// Unassign the specified resource from all IP_Tasks that has this resource assigned
/// </summary>
/// <param name="resource"></param>
public void Unassign(R resource)
{
foreach (var r in _mResourcesOfIP_Task.Where(x => x.Value.Contains(resource)))
r.Value.Remove(resource);
}
/// <summary>
/// Enumerate through all the resources that has been assigned to some IP_Task.
/// </summary>
public IEnumerable<R> Resources
{
get
{
return _mResourcesOfIP_Task.SelectMany(x => x.Value).Distinct();
}
}
/// <summary>
/// Enumerate through all the resources that has been assigned to the specified IP_Task.
/// </summary>
/// <param name="IP_Task"></param>
/// <returns></returns>
public IEnumerable<R> ResourcesOf(T IP_Task)
{
if (IP_Task == null || !_mRegister.Contains(IP_Task))
yield break;
if (_mResourcesOfIP_Task.TryGetValue(IP_Task, out HashSet<R> list))
{
foreach (var item in list)
yield return item;
}
}
/// <summary>
/// Enumerate through all the IP_Tasks that has the specified resource assigned to it.
/// </summary>
/// <param name="resource"></param>
/// <returns></returns>
public IEnumerable<T> IP_TasksOf(R resource)
{
return _mResourcesOfIP_Task.Where(x => x.Value.Contains(resource)).Select(x => x.Key);
}
/// <summary>
/// Set the start value. Affects group start/end and dependants start time.
/// </summary>
public void SetStart(T IP_Task, TimeSpan value)
{
if (_mRegister.Contains(IP_Task) && value != IP_Task.Start && !this.IsGroup(IP_Task))
{
_SetStartHelper(IP_Task, value);
_RecalculateAncestorsSchedule();
_RecalculateSlack();
}
// Set start for a group IP_Task
else if (_mRegister.Contains(IP_Task) && value != IP_Task.Start && this.IsGroup(IP_Task))
{
_SetGroupStartHelper(IP_Task, value);
_RecalculateAncestorsSchedule();
_RecalculateSlack();
}
}
/// <summary>
/// Set the end time. Affects group end and dependants start time.
/// </summary>
public void SetEnd(T IP_Task, TimeSpan value)
{
if (_mRegister.Contains(IP_Task) && value != IP_Task.End && !this.IsGroup(IP_Task))
{
this._SetEndHelper(IP_Task, value);
_RecalculateAncestorsSchedule();
_RecalculateSlack();
}
}
/// <summary>
/// Set the duration of the specified IP_Task from start to end.
/// </summary>
/// <param name="IP_Task"></param>
/// <param name="duration">Number of timescale units between ProjectManager.Start</param>
public void SetDuration(T IP_Task, TimeSpan duration)
{
this.SetEnd(IP_Task, IP_Task.Start + duration);
}
/// <summary>
/// Set the percentage complete of the specified IP_Task from 0.0f to 1.0f.
/// No effect on group IP_Tasks as they will get the aggregated percentage complete of all child IP_Tasks
/// </summary>
/// <param name="IP_Task"></param>
/// <param name="complete"></param>
public void SetComplete(T IP_Task, float complete)
{
if (_mRegister.Contains(IP_Task)
&& complete != IP_Task.Complete
&& !this.IsGroup(IP_Task) // not a group
&& !_mPartsOfSplitIP_Task.ContainsKey(IP_Task) // not a split IP_Task
)
{
_SetCompleteHelper(IP_Task, complete);
_RecalculateComplete();
}
}
/// <summary>
/// Set whether to collapse the specified group IP_Task. No effect on regular IP_Tasks.
/// </summary>
/// <param name="IP_Task"></param>
/// <param name="collasped"></param>
public void SetCollapse(T IP_Task, bool collasped)
{
if (_mRegister.Contains(IP_Task) && this.IsGroup(IP_Task))
{
IP_Task.IsCollapsed = collasped;
}
}
/// <summary>
/// Split the specified IP_Task into consecutive parts part1 and part2.
/// </summary>
/// <param name="IP_Task">The regular IP_Task to split which has duration of at least 2 to make two parts of 1 time unit duration each.</param>
/// <param name="part1">New IP_Task part (1) of the split IP_Task, with the start time of the original IP_Task and the specified duration value.</param>
/// <param name="part2">New IP_Task part (2) of the split IP_Task, starting 1 time unit after part (1) ends and having the remaining of the duration of the origina IP_Task.</param>
/// <param name="duration">The duration of part (1) will be set to the specified duration value but will also be adjusted to approperiate value if necessary.</param>
public void Split(T IP_Task, T part1, T part2, TimeSpan duration)
{
if (IP_Task != null
&& part1 != null
&& part2 != null
&& !part1.Equals(part2) // parts cannot be the same
&& _mRegister.Contains(IP_Task) // IP_Task must be registered
&& !_mPartsOfSplitIP_Task.ContainsKey(IP_Task) // IP_Task must not already be a split IP_Task
&& !_mSplitIP_TaskOfPart.ContainsKey(IP_Task) // IP_Task must not be a IP_Task part
&& _mMembersOfGroup[IP_Task].Count == 0 // IP_Task cannot be a group
&& !_mRegister.Contains(part1) // part1 and part2 must have never existed
&& !_mRegister.Contains(part2)
)
{
_mRegister.Add(part1); // register part1
_mResourcesOfIP_Task[part1] = new HashSet<R>(); // create container for holding resource
// add part1 to split IP_Task
IP_Task.Complete = 0.0f; // reset the complete status
var parts = _mPartsOfSplitIP_Task[IP_Task] = new List<T>(2);
parts.Add(part1);
_mSplitIP_TaskOfPart[part1] = IP_Task; // make a reverse lookup
// allign the schedule
if (duration <= TimeSpan.Zero || duration >= IP_Task.Duration) duration = TimeSpan.FromTicks(IP_Task.Duration.Ticks / 2);
part1.Start = IP_Task.Start;
part1.End = IP_Task.End;
part1.Duration = IP_Task.Duration;
// split part1 to give part2
this.Split(part1, part2, duration);
}
}
/// <summary>
/// Split the specified part and obtain another part from it.
/// </summary>
/// <param name="part">The IP_Task part to split which has duration of at least 2 to make two parts of 1 time unit duration each. Its duration will be set to the specified duration value.</param>
/// <param name="other">New IP_Task part of the original part, starting 1 time unit after it ends and having the remaining of the duration of the original part.</param>
/// <param name="duration">The duration of part (1) will be set to the specified duration value but will also be adjusted to approperiate value if necessary.</param>
public void Split(T part, T other, TimeSpan duration)
{
if (part != null
&& other != null
&& _mSplitIP_TaskOfPart.ContainsKey(part) // part must be an existing part
&& !_mRegister.Contains(other) // other must not have existed
)
{
_mRegister.Add(other); // register other part
_mResourcesOfIP_Task[other] = new HashSet<R>(); // create container for holding resource
var split = _mSplitIP_TaskOfPart[part]; // get the split IP_Task
var parts = _mPartsOfSplitIP_Task[split]; // get the list of ordered parts
parts.Insert(parts.IndexOf(part) + 1, other); // insert the other part after the existing part
_mSplitIP_TaskOfPart[other] = split; // set the reverse lookup
System.Diagnostics.Debug.Write("Project::Split(T part, T other, TimeSpan duration): Need to define minimum duration for splitting.");
// limit the duration point within the split IP_Task duration
if (duration <= TimeSpan.Zero || duration >= part.Duration) duration = TimeSpan.FromTicks(part.Duration.Ticks / 2);
// the real split
var one_duration = duration;
var two_duration = part.Duration - duration;
part.Duration = one_duration;
part.End = part.Start + one_duration;
other.Duration = two_duration;
other.Start = part.End;
other.End = other.Start + two_duration;
_PackPartsForward(parts);
split.Start = parts.First().Start; // recalculate the split IP_Task
split.End = parts.Last().End;
split.Duration = split.End - split.Start;
_RecalculateDependantsOf(split);
_RecalculateAncestorsSchedule();
}
}
/// <summary>
/// Join part1 and part2 in a split IP_Task into a single part represented by part1, and part2 will be deleted from the ProjectManager.
/// The resulting part will have a duration total of the two parts.
/// Part1 and part2 must be actual parts and must be consecutive parts in the split IP_Task.
/// If the join results in only one part remaining, the all parts will be deleted and the split IP_Task will promote to a regular IP_Task
/// Schedule of other parts will not be affected.
/// TODO: Join option: EarlyStartLateEnd, EarlyStartEarlyEnd, LateStartLateEnd
/// </summary>
/// <param name="part1">The part to keep in the ProjectManager after the join completes successfully.</param>
/// <param name="part2">The part to join into part1 and be deleted afterwards from the ProjectManager.</param>
public void Join(T part1, T part2)
{
if (part1 != null
&& part2 != null
&& _mSplitIP_TaskOfPart.ContainsKey(part1) // part1 and part2 must already be existing parts
&& _mSplitIP_TaskOfPart.ContainsKey(part2)
&& _mSplitIP_TaskOfPart[part1] == _mSplitIP_TaskOfPart[part2] // part1 and part2 must be of the same split IP_Task
)
{
var split = _mSplitIP_TaskOfPart[part1];
var parts = _mPartsOfSplitIP_Task[split];
if (parts.Count > 2)
{
// Aggregate part2 into part1, and determine join type
TimeSpan min; bool join_backwards;
if (part1.Start < part2.Start) { min = part1.Start; join_backwards = true; }
else { min = part2.Start; join_backwards = false; }
TimeSpan duration = part1.Duration + part2.Duration;
part1.Start = min;
part1.Duration = duration;
part1.End = min + duration;
// aggregate resouces
// TODO: Ask whether to aggregate resources?
foreach (var r in this.ResourcesOf(part2))
this.Assign(part1, r);
this.Unassign(part2);
// remove all traces of part2
parts.Remove(part2);
_mResourcesOfIP_Task.Remove(part2);
_mSplitIP_TaskOfPart.Remove(part2);
_mRegister.Remove(part2);
// pack the remaining parts
if (join_backwards) _PackPartsForward(parts);
else _PackPartsBackwards(parts);
// set the duration
split.End = parts.Last().End;
split.Duration = split.End - split.Start;
split.Start = parts.First().Start;
_RecalculateAncestorsSchedule();
}
else
{
this.Merge(split);
}
}
}
/// <summary>
/// Merge all the parts of the splitted IP_Task back into one IP_Task, having duration equal to sum of total duration of individual IP_Task parts, and aggregating the resources onto the resulting IP_Task.
/// </summary>
/// <param name="split">The split IP_Task to merge</param>
public void Merge(T split)
{
if (split != null
&& _mPartsOfSplitIP_Task.ContainsKey(split) // must be existing split IP_Task
)
{
TimeSpan duration = TimeSpan.Zero;
_mPartsOfSplitIP_Task[split].ForEach(x => {
// sum durations
duration += x.Duration;
// merge resources onto split IP_Task
foreach (var r in _mResourcesOfIP_Task[x])
this.Assign(split, r);
// remove traces of all parts
_mSplitIP_TaskOfPart.Remove(x);
_mRegister.Remove(x);
_mResourcesOfIP_Task.Remove(x);
});
_mPartsOfSplitIP_Task.Remove(split); // remove split as a split IP_Task
// set the duration
this.SetDuration(split, duration);
}
}
/// <summary>
/// Get the parts of the split IP_Task
/// </summary>
/// <param name="split"></param>
/// <returns></returns>
public IEnumerable<T> PartsOf(T split)
{
if (split != null
&& _mPartsOfSplitIP_Task.ContainsKey(split) // must be existing split IP_Task
)
{
return _mPartsOfSplitIP_Task[split].Select(x => x);
}
else
{
return new T[0];
}
}
/// <summary>
/// Get the split IP_Task that the specified part belogs to.
/// </summary>
/// <param name="part"></param>
/// <returns></returns>
public T SplitIP_TaskOf(T part)
{
if (_mSplitIP_TaskOfPart.ContainsKey(part))
return _mSplitIP_TaskOfPart[part];
return null;
}
/// <summary>
/// Get whether the specified IP_Task is a split IP_Task
/// </summary>
/// <param name="IP_Task"></param>
/// <returns></returns>
public bool IsSplit(T IP_Task)
{
return IP_Task != null && _mPartsOfSplitIP_Task.ContainsKey(IP_Task);
}
/// <summary>
/// Get whether the specified IP_Task is a part of a split IP_Task
/// </summary>
/// <param name="IP_Task"></param>
/// <returns></returns>
public bool IsPart(T IP_Task)
{
return IP_Task != null && _mSplitIP_TaskOfPart.ContainsKey(IP_Task);
}
/// <summary>
/// Detach the specified IP_Task from ProjectManager.IP_Tasks (i.e. remove from its parent group, or if not it goes not have a parent group, unregister from root IP_Task status).
/// The specified IP_Task will remain registered in ProjectManager.
/// After execution of this helper method, the IP_Task is expected to be re-attached to ProjectManager.IP_Tasks by regaining root IP_Task status, or joining a new group.
/// </summary>
/// <param name="IP_Task"></param>
private void _DetachIP_Task(T IP_Task)
{
var group = this.DirectGroupOf(IP_Task);
if (group == null) // member is actually not in any group, so it must be in _mRootIP_Tasks
_mRootIP_Tasks.Remove(IP_Task);
else
{
_mMembersOfGroup[group].Remove(IP_Task);
_mGroupOfMember[IP_Task] = null;
}
}
private void _SetStartHelper(T IP_Task, TimeSpan value)
{
if (IP_Task.Start != value)
{
if (_mSplitIP_TaskOfPart.ContainsKey(IP_Task))
{
// IP_Task part belonging to a split IP_Task needs special treatment
_SetPartStartHelper(IP_Task, value);
}
else // regular IP_Task or a split IP_Task, which we will treat normally
{
// check out of bounds
if (value < TimeSpan.Zero) value = TimeSpan.Zero;
if (this.DirectPrecedentsOf(IP_Task).Any())
{
var max_end = this.DirectPrecedentsOf(IP_Task).Max(x => x.End);
if (value <= max_end) value = max_end; // + One;
}
// save offset just in case we need to use for moving IP_Task parts
var offset = value - IP_Task.Start;
// cache value
IP_Task.Duration = IP_Task.End - IP_Task.Start;
IP_Task.Start = value;
// affect self
IP_Task.End = IP_Task.Start + IP_Task.Duration;
// calculate dependants
_RecalculateDependantsOf(IP_Task);
// shift the IP_Task parts accordingly if IP_Task was a split IP_Task
if (_mPartsOfSplitIP_Task.ContainsKey(IP_Task))
{
_mPartsOfSplitIP_Task[IP_Task].ForEach(x => {
x.Start += offset;
x.End += offset;
});
}
}
}
}
/// <summary>
/// Set the start date for a group IP_Task. The relative dates between the IP_Tasks in the group will not be affected
/// </summary>
/// <param name="group"></param>
/// <param name="value"></param>
private void _SetGroupStartHelper(T group, TimeSpan value)
{
if (_mRegister.Contains(group) && value != group.Start && this.IsGroup(group))
{
bool earlier = value < group.Start;
TimeSpan offset = value - group.Start;
var decendants = earlier ? MembersOf(group).OrderBy((t) => t.Start) : MembersOf(group).OrderByDescending((t) => t.Start);
foreach(T decendant in decendants)
{
if (this.IsGroup(decendant)) continue;
decendant.Start += offset;
decendant.End += offset;
if (this.IsSplit(decendant))
{
var parts = _mPartsOfSplitIP_Task[decendant];
foreach (T part in parts)
{
part.Start += offset;
part.End += offset;
}
}
_RecalculateDependantsOf(decendant);
}
_RecalculateAncestorsSchedule();
_RecalculateSlack();
}
}
private void _SetEndHelper(T IP_Task, TimeSpan value)
{
if (IP_Task.End != value)
{
if (_mSplitIP_TaskOfPart.ContainsKey(IP_Task))
{
// IP_Task part belonging to a split IP_Task needs special treatment
_SetPartEndHelper(IP_Task, value);
}
else // regular IP_Task or a split IP_Task, which we will treat normally
{
// check bounds
bool isSplitIP_Task = _mPartsOfSplitIP_Task.ContainsKey(IP_Task);
T last_part = null;
if (isSplitIP_Task)
{
last_part = _mPartsOfSplitIP_Task[IP_Task].Last();
if (value <= last_part.Start) value = last_part.Start + TimeSpan.FromMinutes(30);
}
if (value <= IP_Task.Start) value = IP_Task.Start + TimeSpan.FromMinutes(30); // end cannot be less than start
// assign end value
IP_Task.End = value;
IP_Task.Duration = IP_Task.End - IP_Task.Start;
_RecalculateDependantsOf(IP_Task);
if (isSplitIP_Task)
{
last_part.End = value;
last_part.Duration = last_part.End - last_part.Start;
}
}
}
}
private void _SetPartStartHelper(T part, TimeSpan value)
{
var split = _mSplitIP_TaskOfPart[part];
var parts = _mPartsOfSplitIP_Task[split];
// check bounds
if (this.DirectPrecedentsOf(split).Any())
{
var max_end = this.DirectPrecedentsOf(split).Max(x => x.End);
if (value < max_end) value = max_end;
}
if (value < TimeSpan.Zero) value = TimeSpan.Zero;
// flag whether we need to pack parts forward or backwards
bool backwards = value < part.Start;
// assign start value, maintining duration and modifying end
var duration = part.End - part.Start;
part.Start = value;
part.End = value + duration;
// pack packs
if (backwards) _PackPartsBackwards(parts);
else _PackPartsForward(parts);
// recalculate the split
split.Start = parts.First().Start; // recalculate the split IP_Task
split.End = parts.Last().End;
split.Duration = split.End - split.Start;
_RecalculateDependantsOf(split);
}
private void _SetPartEndHelper(T part, TimeSpan value)
{
var split = _mSplitIP_TaskOfPart[part];
var parts = _mPartsOfSplitIP_Task[split];
// check for bounds
if (value <= part.Start) value = part.Start + TimeSpan.FromMinutes(30);
// flag whether duration is increased or reduced
bool increased = value > part.End;
// set end value and duration
part.End = value;
part.Duration = part.End - part.Start;
// pack parts
if (increased) _PackPartsForward(parts);
// recalculate the split
split.Start = parts.First().Start; // recalculate the split IP_Task
split.End = parts.Last().End;
split.Duration = split.End - split.Start;
_RecalculateDependantsOf(split);
}
private void _PackPartsBackwards(List<T> parts)
{
// pack backwards first before packing forward again
for (int i = parts.Count - 2; i > 0; i--) // Cannot pack beyond first part (i > 0)
{
var earlier = parts[i];
var later = parts[i + 1];
if (later.Start <= earlier.End)
{
earlier.End = later.Start;
earlier.Start = earlier.End - earlier.Duration;
}
}
_PackPartsForward(parts);
}
private void _PackPartsForward(List<T> parts)
{
for (int i = 1; i < parts.Count; i++)
{
var current = parts[i];
var previous = parts[i - 1];
if (previous.End >= current.Start)
{
current.Start = previous.End;
current.End = current.Start + current.Duration;
}
}
}
private void _SetCompleteHelper(T IP_Task, float value)
{
if (IP_Task.Complete != value)
{
if (value > 1) value = 1;
else if (value < 0) value = 0;
IP_Task.Complete = value;
if (_mSplitIP_TaskOfPart.ContainsKey(IP_Task))
{
var split = _mSplitIP_TaskOfPart[IP_Task];
var parts = _mPartsOfSplitIP_Task[split];
float complete = 0;
TimeSpan duration = TimeSpan.Zero;
foreach (var part in parts)
{
complete += part.Complete * part.Duration.Ticks;
duration += part.Duration;
}
split.Complete = complete / duration.Ticks;
}
}
}
private void _RecalculateComplete()
{
Stack<T> groups = new Stack<T>();
foreach (var IP_Task in _mRootIP_Tasks.Where(x => this.IsGroup(x)))
{
_RecalculateCompletedHelper(IP_Task);
}
}
private float _RecalculateCompletedHelper(T groupOrSplit)
{
float t_complete = 0;
TimeSpan t_duration = TimeSpan.Zero;
if (_mPartsOfSplitIP_Task.ContainsKey(groupOrSplit))
{
foreach (var part in _mPartsOfSplitIP_Task[groupOrSplit])
{
t_complete += part.Complete * part.Duration.Ticks;
t_duration += part.Duration;
}
}
else
{
foreach (var member in this.DirectMembersOf(groupOrSplit))
{
t_duration += member.Duration;
if (this.IsGroup(member)) t_complete += _RecalculateCompletedHelper(member) * member.Duration.Ticks;
else t_complete += member.Complete * member.Duration.Ticks;
}
}
groupOrSplit.Complete = t_complete / t_duration.Ticks;
return groupOrSplit.Complete;
}
private void _RecalculateDependantsOf(T precedent)
{
// affect decendants
foreach (var dependant in this.DirectDependantsOf(precedent))
{
if (dependant.Start < precedent.End)
this._SetStartHelper(dependant, precedent.End);
}
}
private void _RecalculateAncestorsSchedule()
{
// affects parent group
foreach(var group in _mRootIP_Tasks.Where(x => this.IsGroup(x)))
{
_RecalculateAncestorsScheduleHelper(group);
}
}
private void _RecalculateAncestorsScheduleHelper(T group)
{
float t_complete = 0;
TimeSpan t_duration = TimeSpan.Zero;
var start = TimeSpan.MaxValue;
var end = TimeSpan.MinValue;
foreach (var member in this.DirectMembersOf(group))
{
if (this.IsGroup(member))
_RecalculateAncestorsScheduleHelper(member);
t_duration += member.Duration;
t_complete += member.Complete * member.Duration.Ticks;
if (member.Start < start) start = member.Start;
if (member.End > end) end = member.End;
}
this._SetStartHelper(group, start);
this._SetEndHelper(group, end);
this._SetCompleteHelper(group, t_complete / t_duration.Ticks);
}
private void _RecalculateSlack()
{
var max_end = this.IP_Tasks.Max(x => x.End);
foreach (var IP_Task in this.IP_Tasks)
{
// affects slack for current IP_Task
if (this.DirectDependantsOf(IP_Task).Any())
{
// slack until the earliest dependant needs to start
var min = this.DirectDependantsOf(IP_Task).Min(x => x.Start);
IP_Task.Slack = min - IP_Task.End;
}
else
{
// no dependants, so we have all the time until the last IP_Task ends
IP_Task.Slack = max_end - IP_Task.End;
}
}
}
}
}