using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SunlightCentralizedControlManagement_SCCM_.UserClass { /// /// Wrapper ProjectManager class /// [Serializable] public class ProjectManager : ProjectManager { } /// /// Concrete ProjectManager class for the IProjectManager interface /// /// /// [Serializable] public class ProjectManager : IProjectManager where T : IP_Task where R : class { HashSet _mRegister = new HashSet(); List _mRootIP_Tasks = new List(); Dictionary> _mMembersOfGroup = new Dictionary>(); // Map group to list of members Dictionary> _mDependantsOfPrecedent = new Dictionary>(); // Map precendent to list of dependents Dictionary> _mResourcesOfIP_Task = new Dictionary>(); // Map IP_Task to list of resources Dictionary> _mPartsOfSplitIP_Task = new Dictionary>(); // Map split IP_Task to list of IP_Task parts Dictionary _mSplitIP_TaskOfPart = new Dictionary(); // Map a IP_Task part to the original split IP_Task Dictionary _mGroupOfMember = new Dictionary(); // Map member IP_Task to parent group IP_Task Dictionary _mIP_TaskIndices = new Dictionary(); // Map the IP_Task to its zero-based index order position /// /// Create a new Project /// public ProjectManager() { Now = TimeSpan.Zero; Start = DateTime.Now; } /// /// Get or set the TimeSpan we are at now from Start DateTime /// public TimeSpan Now { get; set; } /// /// Get or set the starting date for this project /// public DateTime Start { get; set; } /// /// Get the date after the specified TimeSpan /// /// /// public DateTime GetDateTime(TimeSpan span) { return this.Start.Add(span); } /// /// Create a new T for this Project and add it to the T tree /// /// 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(); _mDependantsOfPrecedent[IP_Task] = new HashSet(); _mResourcesOfIP_Task[IP_Task] = new HashSet(); _mGroupOfMember[IP_Task] = null; } } /// /// Remove IP_Task from this Project /// /// 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); } } } /// /// Add the member T to the group T /// /// /// 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(); } } } /// /// Remove the member IP_Task from its group /// 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(); } } } /// /// 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. /// /// public void Ungroup(T group) { if (group != null //&& _mRegister.Contains(group) && _mMembersOfGroup.TryGetValue(group, out List 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(); } } /// /// Get the zero-based index of the IP_Task in this Project /// /// /// 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; } /// /// 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 /// /// /// 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(); } } } /// /// Get the T tree /// public IEnumerable IP_Tasks { get { var stack = new Stack(1024); var rstack = new Stack(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()); } } } } /// /// Enumerate upwards from member to and through all the parents and grandparents of the specified IP_Task /// public IEnumerable GroupsOf(T member) { T parent = DirectGroupOf(member); while (parent != null) { yield return parent; parent = DirectGroupOf(parent); } } /// /// Enumerate through all the children and grandchildren of the specified group /// /// /// public IEnumerable MembersOf(T group) { if (_mRegister.Contains(group)) { Stack stack = new Stack(20); Stack rstack = new Stack(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()); } } } } /// /// Get the parent group of the specified IP_Task /// /// /// public T DirectGroupOf(T member) { if (_mGroupOfMember.ContainsKey(member)) // _mRegister.Contains(IP_Task)) { return _mGroupOfMember[member]; } else { return null; } } /// /// Enumerate through all the direct children of the specified group /// /// /// public IEnumerable DirectMembersOf(T group) { if (group == null) yield break; if (_mMembersOfGroup.TryGetValue(group, out List list)) { var iter = list.GetEnumerator(); while (iter.MoveNext()) yield return iter.Current; } } /// /// Enumerate through all the direct precedents and indirect precedents of the specified IP_Task /// /// /// public IEnumerable PrecedentsOf(T dependant) { if (_mRegister.Contains(dependant)) { var stack = new Stack(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); } } } } /// /// Enumerate through all the direct dependants and indirect dependants of the specified IP_Task /// /// /// public IEnumerable DependantsOf(T precendent) { if (!_mDependantsOfPrecedent.ContainsKey(precendent)) yield break; var stack = new Stack(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); } } } /// /// Enumerate through all the direct precedents of the specified IP_Task /// /// /// public IEnumerable DirectPrecedentsOf(T dependants) { return _mDependantsOfPrecedent.Where(x => x.Value.Contains(dependants)).Select(x => x.Key); } /// /// Enumerate through all the direct dependants of the specified IP_Task /// /// /// public IEnumerable DirectDependantsOf(T precedent) { if (precedent == null) yield break; if (_mDependantsOfPrecedent.TryGetValue(precedent, out HashSet dependants)) { var iter = dependants.GetEnumerator(); while (iter.MoveNext()) yield return iter.Current; } } /// /// Enumerate through all IP_Tasks that is a precedent, having dependants. /// public IEnumerable Precedents { get { return _mDependantsOfPrecedent.Where(x => _mDependantsOfPrecedent[x.Key].Count > 0).Select(x => x.Key); } } /// /// Enumerate list of critical paths in Project /// public IEnumerable> CriticalPaths { get { Dictionary> endtimelookp = new Dictionary>(1024); TimeSpan max_end = TimeSpan.MinValue; foreach (var IP_Task in this.IP_Tasks) { if (!endtimelookp.TryGetValue(IP_Task.End, out List list)) endtimelookp[IP_Task.End] = new List(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)); } } } } /// /// Get whether the specified IP_Task is a group /// /// /// public bool IsGroup(T IP_Task) { if (_mMembersOfGroup.TryGetValue(IP_Task, out List list)) return list.Count > 0; else return false; } /// /// Get whether the specified IP_Task is a member /// /// /// public bool IsMember(T IP_Task) { return this.DirectGroupOf(IP_Task) != null; } /// /// Get whether the specified IP_Task has relations, either has dependants or has precedents connecting to it. /// /// /// 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; } } /// /// Set a relation between the precedent and dependant IP_Task /// /// /// 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(); } } } /// /// Unset the relation between the precedent and dependant IP_Task, if any. /// /// /// 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(); } } /// /// Remove all dependant IP_Task from specified precedent IP_Task /// /// public void Unrelate(T precedent) { if (_mRegister.Contains(precedent)) { if (_mSplitIP_TaskOfPart.ContainsKey(precedent)) precedent = _mSplitIP_TaskOfPart[precedent]; _mDependantsOfPrecedent[precedent].Clear(); _RecalculateSlack(); } } /// /// Assign the specified resource to the specified IP_Task /// /// /// 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); } /// /// Unassign the specified resource from the specfied IP_Task /// /// /// public void Unassign(T IP_Task, R resource) { _mResourcesOfIP_Task[IP_Task].Remove(resource); } /// /// Unassign the all resources from the specfied IP_Task /// /// public void Unassign(T IP_Task) { if(_mRegister.Contains(IP_Task)) _mResourcesOfIP_Task[IP_Task].Clear(); } /// /// Unassign the specified resource from all IP_Tasks that has this resource assigned /// /// public void Unassign(R resource) { foreach (var r in _mResourcesOfIP_Task.Where(x => x.Value.Contains(resource))) r.Value.Remove(resource); } /// /// Enumerate through all the resources that has been assigned to some IP_Task. /// public IEnumerable Resources { get { return _mResourcesOfIP_Task.SelectMany(x => x.Value).Distinct(); } } /// /// Enumerate through all the resources that has been assigned to the specified IP_Task. /// /// /// public IEnumerable ResourcesOf(T IP_Task) { if (IP_Task == null || !_mRegister.Contains(IP_Task)) yield break; if (_mResourcesOfIP_Task.TryGetValue(IP_Task, out HashSet list)) { foreach (var item in list) yield return item; } } /// /// Enumerate through all the IP_Tasks that has the specified resource assigned to it. /// /// /// public IEnumerable IP_TasksOf(R resource) { return _mResourcesOfIP_Task.Where(x => x.Value.Contains(resource)).Select(x => x.Key); } /// /// Set the start value. Affects group start/end and dependants start time. /// 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(); } } /// /// Set the end time. Affects group end and dependants start time. /// 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(); } } /// /// Set the duration of the specified IP_Task from start to end. /// /// /// Number of timescale units between ProjectManager.Start public void SetDuration(T IP_Task, TimeSpan duration) { this.SetEnd(IP_Task, IP_Task.Start + duration); } /// /// 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 /// /// /// 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(); } } /// /// Set whether to collapse the specified group IP_Task. No effect on regular IP_Tasks. /// /// /// public void SetCollapse(T IP_Task, bool collasped) { if (_mRegister.Contains(IP_Task) && this.IsGroup(IP_Task)) { IP_Task.IsCollapsed = collasped; } } /// /// Split the specified IP_Task into consecutive parts part1 and part2. /// /// The regular IP_Task to split which has duration of at least 2 to make two parts of 1 time unit duration each. /// New IP_Task part (1) of the split IP_Task, with the start time of the original IP_Task and the specified duration value. /// 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. /// The duration of part (1) will be set to the specified duration value but will also be adjusted to approperiate value if necessary. 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(); // 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(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); } } /// /// Split the specified part and obtain another part from it. /// /// 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. /// 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. /// The duration of part (1) will be set to the specified duration value but will also be adjusted to approperiate value if necessary. 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(); // 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(); } } /// /// 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 /// /// The part to keep in the ProjectManager after the join completes successfully. /// The part to join into part1 and be deleted afterwards from the ProjectManager. 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); } } } /// /// 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. /// /// The split IP_Task to merge 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); } } /// /// Get the parts of the split IP_Task /// /// /// public IEnumerable 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]; } } /// /// Get the split IP_Task that the specified part belogs to. /// /// /// public T SplitIP_TaskOf(T part) { if (_mSplitIP_TaskOfPart.ContainsKey(part)) return _mSplitIP_TaskOfPart[part]; return null; } /// /// Get whether the specified IP_Task is a split IP_Task /// /// /// public bool IsSplit(T IP_Task) { return IP_Task != null && _mPartsOfSplitIP_Task.ContainsKey(IP_Task); } /// /// Get whether the specified IP_Task is a part of a split IP_Task /// /// /// public bool IsPart(T IP_Task) { return IP_Task != null && _mSplitIP_TaskOfPart.ContainsKey(IP_Task); } /// /// 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. /// /// 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; }); } } } } /// /// Set the start date for a group IP_Task. The relative dates between the IP_Tasks in the group will not be affected /// /// /// 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 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 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 groups = new Stack(); 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; } } } } }