private static ProjectData SetTaskResourceNamesOrInitials(ProjectData project, TaskId id, string value, bool isInitials)
        {
            var field = isInitials ? ResourceFields.Initials : ResourceFields.Name;

            value = value.Trim();

            var remainingAssignmentIds = project.GetAssignments(id).ToList();

            if (value.Length > 0)
            {
                var resourceParts = value.Split(',');

                foreach (var resourcePart in resourceParts)
                {
                    var initials    = resourcePart.Trim();
                    var units       = 1.0;
                    var openBracket = initials.IndexOf("[");
                    if (openBracket >= 0)
                    {
                        var closeBracket = initials.IndexOf("]");

                        if (closeBracket < openBracket)
                        {
                            throw new FormatException("Missing ']'");
                        }

                        var percentageText = initials.Substring(openBracket + 1, closeBracket - openBracket - 1).Trim();

                        if (percentageText.EndsWith("%"))
                        {
                            percentageText = percentageText.Substring(0, percentageText.Length - 1).Trim();
                        }

                        if (!double.TryParse(percentageText, out var percentage))
                        {
                            throw new FormatException($"'{percentageText}' isn't a valid percentage");
                        }

                        initials = initials.Substring(0, openBracket).Trim();
                        units    = percentage / 100.0;
                    }

                    var resourceId = project.GetResources(initials, isInitials).FirstOrDefault();
                    if (resourceId.IsDefault)
                    {
                        resourceId = ResourceId.Create();
                        project    = project.AddResource(resourceId).Set(field, resourceId, initials);
                    }

                    var assignmentId = project.GetAssignment(id, resourceId);
                    if (assignmentId.IsDefault)
                    {
                        assignmentId = AssignmentId.Create();
                        project      = project.AddAssignment(assignmentId, id, resourceId);
                    }

                    project = project.Set(AssignmentFields.Units, assignmentId, units);
                    remainingAssignmentIds.Remove(assignmentId);
                }
            }

            foreach (var assignmentId in remainingAssignmentIds)
            {
                project = project.RemoveAssignment(assignmentId);
            }

            return(project);
        }
 private static ProjectData ResetTaskResourceInitials(ProjectData project, TaskId id)
 {
     return(ResetTaskResourceNamesOrInitials(project, id, isInitials: true));
 }
 private static ProjectData SetTaskResourceInitials(ProjectData project, TaskId id, string value)
 {
     return(SetTaskResourceNamesOrInitials(project, id, value, isInitials: true));
 }
        private static ProjectData SetTaskPredecessorsOrSuccessors(ProjectData project, TaskId id, string value, bool isSuccessors)
        {
            var linkField = isSuccessors ? TaskFields.SuccessorLinks : TaskFields.PredecessorLinks;

            value = value.Trim();

            var predecessorsBuilder = ImmutableArray.CreateBuilder <TaskId>();
            var remainingTaskLinks  = project.Get(linkField, id).ToList();

            if (value.Length > 0)
            {
                var parts = value.Split(',');

                foreach (var part in parts)
                {
                    var partText = part.Trim();

                    var taskText = partText;
                    var sign     = Math.Max(partText.IndexOf('+'), partText.IndexOf('-'));
                    var lag      = TimeSpan.Zero;

                    if (sign >= 0)
                    {
                        var lagText = partText.Substring(sign).Trim();
                        lag      = project.Information.TimeConversion.ParseDuration(lagText);
                        taskText = partText.Substring(0, sign).Trim();
                    }

                    var taskOrdinalText = taskText;
                    var linkType        = (TaskLinkType?)null;

                    if (taskText.EndsWith("FS", StringComparison.OrdinalIgnoreCase))
                    {
                        linkType = TaskLinkType.FinishToStart;
                    }
                    else if (taskText.EndsWith("SS", StringComparison.OrdinalIgnoreCase))
                    {
                        linkType = TaskLinkType.StartToStart;
                    }
                    else if (taskText.EndsWith("FF", StringComparison.OrdinalIgnoreCase))
                    {
                        linkType = TaskLinkType.FinishToFinish;
                    }
                    else if (taskText.EndsWith("SF", StringComparison.OrdinalIgnoreCase))
                    {
                        linkType = TaskLinkType.StartToFinish;
                    }

                    if (linkType != null)
                    {
                        taskOrdinalText = taskText.Substring(0, taskText.Length - 2).Trim();
                    }
                    else if (sign < 0)
                    {
                        linkType = TaskLinkType.FinishToStart;
                    }
                    else
                    {
                        throw new FormatException($"'{partText}' isn't a valid value");
                    }

                    if (!int.TryParse(taskOrdinalText, out var taskOrdinal))
                    {
                        throw new FormatException($"'{taskOrdinalText}' isn't a valid int");
                    }

                    var taskId = project.GetTask(taskOrdinal);
                    if (taskId.IsDefault)
                    {
                        throw new FormatException($"'{taskOrdinal}' isn't a valid task");
                    }

                    var predecessorId = isSuccessors ? id : taskId;
                    var successorId   = isSuccessors ? taskId : id;

                    var taskLink = TaskLink.Create(predecessorId, successorId, linkType.Value, lag);

                    var existingLink = project.GetTaskLink(predecessorId, successorId);
                    if (existingLink != null)
                    {
                        project = project.RemoveTaskLink(existingLink);
                    }
                    else if (project.TaskLinkCausesCycle(taskLink))
                    {
                        var predecessorOrdinal = project.Get(TaskFields.Ordinal, predecessorId);
                        var successorOrdinal   = project.Get(TaskFields.Ordinal, successorId);
                        throw new InvalidOperationException($"Cannot add a link from task {predecessorOrdinal} to task {successorOrdinal} as this would cause a cycle.");
                    }

                    project = project.AddTaskLink(taskLink);

                    remainingTaskLinks.Remove(taskLink);
                }
            }

            foreach (var taskLink in remainingTaskLinks)
            {
                project = project.RemoveTaskLink(taskLink);
            }

            return(project);
        }
 private static ProjectData SetTaskSuccessors(ProjectData project, TaskId id, string value)
 {
     return(SetTaskPredecessorsOrSuccessors(project, id, value, isSuccessors: true));
 }
 private static ProjectData ResetTaskSuccessors(ProjectData project, TaskId id)
 {
     return(ResetTaskPredecessorsOrSuccessors(project, id, isSuccessors: true));
 }
        internal static ProjectChanges Compute(ProjectData oldProject, ProjectData newProject)
        {
            // Tasks

            var oldTasks = oldProject.TaskMap;
            var newTasks = newProject.TaskMap;

            var addedTasks = newTasks.Keys
                             .Where(id => !oldTasks.ContainsKey(id))
                             .ToImmutableArray();

            var removedTasks = oldTasks.Keys
                               .Where(id => !newTasks.ContainsKey(id))
                               .ToImmutableArray();

            var changedTasks = oldTasks.Where(kv => newTasks.ContainsKey(kv.Key))
                               .Select(kv => (oldTask: kv.Value, newTask: newTasks[kv.Key]))
                               .Where(t => t.oldTask != t.newTask)
                               .Select(t => TaskChanges.Compute(t.oldTask, t.newTask))
                               .Where(c => c.FieldChanges.Any())
                               .ToImmutableArray();

            // Resources

            var oldResources = oldProject.ResourceMap;
            var newResources = newProject.ResourceMap;

            var addedResources = newResources.Keys
                                 .Where(id => !oldResources.ContainsKey(id))
                                 .ToImmutableArray();

            var removedResources = oldResources.Keys
                                   .Where(id => !newResources.ContainsKey(id))
                                   .ToImmutableArray();

            var changedResources = oldResources.Where(kv => newResources.ContainsKey(kv.Key))
                                   .Select(kv => (oldResource: kv.Value, newResource: newResources[kv.Key]))
                                   .Where(t => t.oldResource != t.newResource)
                                   .Select(t => ResourceChanges.Compute(t.oldResource, t.newResource))
                                   .Where(c => c.FieldChanges.Any())
                                   .ToImmutableArray();

            // Assignments

            var oldAssignments = oldProject.AssignmentMapping;
            var newAssignments = newProject.AssignmentMapping;

            var addedAssignments = newAssignments.Keys
                                   .Where(id => !oldAssignments.ContainsKey(id))
                                   .ToImmutableArray();

            var removedAssignments = oldAssignments.Keys
                                     .Where(id => !newAssignments.ContainsKey(id))
                                     .ToImmutableArray();

            var changedAssignments = oldAssignments.Where(kv => newAssignments.ContainsKey(kv.Key))
                                     .Select(kv => (oldAssignment: kv.Value, newAssignment: newAssignments[kv.Key]))
                                     .Where(t => t.oldAssignment != t.newAssignment)
                                     .Select(t => AssignmentChanges.Compute(t.oldAssignment, t.newAssignment))
                                     .Where(c => c.FieldChanges.Any())
                                     .ToImmutableArray();

            return(new ProjectChanges(addedTasks, removedTasks, changedTasks,
                                      addedResources, removedResources, changedResources,
                                      addedAssignments, removedAssignments, changedAssignments));
        }
 private Project(ProjectData data)
 {
     Data = data;
 }