// Validation algorithm. Replays events, and depending on circumstances, sets flags which inform the caller how to save/reject/respond to the incoming
        // events.
        public void SimpleFullStateRebuildValidation(IList <GenericTodoEvent> newEvents, out IList <GenericTodoEvent> acceptedEvents, out IList <GenericTodoEvent> skippedEvents, out bool rejected, out bool shouldTriggerRefresh, out string errorMsg)
        {
            TaskList                tasklist    = new TaskList();
            Stack <UndoAction>      undoStack   = new Stack <UndoAction>();
            ISet <GenericTodoEvent> newEventSet = newEvents.ToHashSet(eventComparer);

            errorMsg             = "";
            acceptedEvents       = new List <GenericTodoEvent>(newEvents.Count);
            skippedEvents        = new List <GenericTodoEvent>(newEvents.Count);
            shouldTriggerRefresh = false;
            rejected             = false;
            GenericTodoEvent eventUnderQuestion = null;     // Used in catch scenarios.
            int eventNum = 0;                               // Used in catch scenarios.

            // Create the full event log. We will attempt to execute these events in order, validating the state each time.
            IList <GenericTodoEvent> fullEventLog = truthLog.Concat(newEvents).OrderBy(keySelector, keyComparer).ToList();

            try {
                foreach (GenericTodoEvent currEvent in fullEventLog)
                {
                    eventUnderQuestion = currEvent;
                    eventNum++;

                    // Try to apply the event, and examine the validation results.
                    tasklist = EventReplayer.Replay(currEvent, tasklist, undoStack, out bool saveIfNewEvent, out bool demandsRefresh);

                    // Only save the event if the event replay does not want to skip it, AND the event has not already been saved (i.e. is 'new')
                    if (saveIfNewEvent && newEventSet.Contains(currEvent))
                    {
                        acceptedEvents.Add(currEvent);
                    }
                    else if (!saveIfNewEvent && newEventSet.Contains(currEvent))
                    {
                        skippedEvents.Add(currEvent);
                    }

                    // Signal if validating this particular event means that the client should probably refresh their data. (E.g. they are clearly out of date).
                    if (demandsRefresh)
                    {
                        shouldTriggerRefresh = true;
                    }
                }
            }
            // If any InvalidOperationExceptions are thrown, that means the eventlog as a whole is invalid, and nothing should be saved.
            // This is our response if we deem that the posted events are so out-of-sync and inherently incompatible with the truth log, that
            // it would be dangerous to attempt any kind of saving from these events.
            catch (InvalidOperationException e) {
                errorMsg             = "The following error occurred when trying to replay event { " + eventUnderQuestion.EventType + ", " + eventUnderQuestion.Name + " }. The event was event " + eventNum + " out of " + fullEventLog.Count + " events in the event log. Error message: " + e.Message;
                errorMsg             = errorMsg + buildFullEventLogErrorMessage(fullEventLog);
                rejected             = true;
                shouldTriggerRefresh = true;
            }
        }
        // Holy mother of god this code is so f*****g terrible.
        private static void DoImplicitLinkingEventIfRequired(Task task, GenericTodoEvent e, TaskList tasklist, Stack <UndoAction> undoStack)
        {
            // CASE: Task is not started but we want to do 'completed'. We can therefore implicitly link the states by first doing a "taskStarted" operation.
            if (e.EventType == EventTypes.TaskCompleted && task.ProgressStatus == ProgressStatusVals.NotStarted)
            {
                Handlers[EventTypes.TaskStarted](e, tasklist, undoStack);
                return;
            }

            // CASE: We want to do 'task revived' but the "original task" is not failed yet. In this case, we should implicitly 'fail' the original task first.
            if (e.EventType == EventTypes.TaskRevived)
            {
                Task original = tasklist.ActiveTaskReader(e.Original.Value);
                if (original != null && original.Category != CategoryVals.Deferred)
                {
                    tasklist.FailTask(original, e.Timestamp);
                    undoStack.Clear();
                    return;
                }
            }
        }
        public static TaskList Replay(GenericTodoEvent e, TaskList tasklist, Stack <UndoAction> undoStack, out bool saveEvent, out bool triggerRefresh)
        {
            saveEvent      = true;
            triggerRefresh = false;

            // Acquire the task from the global task list, to determine it's current state.
            Task task = tasklist.AllTaskReader(e.Id);

            try {
                // Handle cases where the task does not yet exist. If the event is a creation event, (or undo deletion event) then this is obviously expected.
                // If the event is an 'undo' of a creation event, then we can safely dispose of the event, since it would just erase the expected task anyway. We will still save it though, in case of an out-or-order arrival
                // If the event is a 'Task activated' or 'Task Started' or 'Task Edited' event, then we will create the event implicitly, since we can do so with no harm done.
                // If the event is a 'Task revived' event, then this is expected, but only if the 'original' task acutally existed and is a failed state. Otherwise, we will have to implicitly 'fail' the original one first.
                if (task == null)
                {
                    if (e.EventType == EventTypes.TaskAdded || e.EventType == EventTypes.ChildTaskAdded || e.EventType == EventTypes.TaskDeletedUndo || (e.EventType == EventTypes.TaskRevived && tasklist.FailedTaskReader(e.Original.Value) != null))
                    {
                        return(Handlers[e.EventType](e, tasklist, undoStack));
                    }
                    else if (e.EventType == EventTypes.TaskRevived && tasklist.ActiveTaskReader(e.Original.Value) != null && tasklist.ActiveTaskReader(e.Original.Value).Category != CategoryVals.Deferred)
                    {
                        // Implicitly 'fail' the original task first
                        tasklist.FailTask(tasklist.AllTaskReader(e.Original.Value), e.Timestamp);
                        undoStack.Clear();
                        return(Handlers[e.EventType](e, tasklist, undoStack));
                    }
                    else if (e.EventType == EventTypes.TaskAddedUndo || e.EventType == EventTypes.ChildTaskAddedUndo || e.EventType == EventTypes.TaskEdited || e.EventType == EventTypes.TaskDeleted)
                    {
                        // Save the event, but do not actually do anything. We will still save this event incase a subsequent request 'fills in the gap' so to speak (I.e. out of order arrival).
                        saveEvent      = true;
                        triggerRefresh = false;
                        return(tasklist);
                    }
                    else if (e.EventType == EventTypes.TaskActivated || e.EventType == EventTypes.TaskStarted)
                    {
                        // If the task does not exist yet, we are happy to create it implicitly.
                        if (e.Parent.HasValue)
                        {
                            tasklist.CreateNewSubtask(e.Name, tasklist.AllTaskReader(e.Parent.Value), e.Category, e.Timestamp, e.Id);
                            undoStack.Push(new UndoAction(EventTypes.ChildTaskAdded, e.Id));
                        }
                        else
                        {
                            tasklist.CreateNewIndependentTask(e.Name, e.Category, e.Timestamp, e.ColourId, e.Id);
                            undoStack.Push(new UndoAction(EventTypes.TaskAdded, e.Id));
                        }
                        return(Handlers[e.EventType](e, tasklist, undoStack));
                    }
                    else if (e.EventType == EventTypes.TaskCompleted && e.Category != CategoryVals.Deferred)
                    {
                        // Create the task implicitly, then apply the 'linking' event, and then perform the taskCompleted handler itself. This is the only case of a "double link".
                        if (e.Parent.HasValue)
                        {
                            tasklist.CreateNewSubtask(e.Name, tasklist.AllTaskReader(e.Parent.Value), e.Category, e.Timestamp, e.Id);
                            undoStack.Push(new UndoAction(EventTypes.ChildTaskAddedUndo, e.Id));
                        }
                        else
                        {
                            tasklist.CreateNewIndependentTask(e.Name, e.Category, e.Timestamp, e.ColourId, e.Id);
                            undoStack.Push(new UndoAction(EventTypes.TaskAdded, e.Id));
                        }
                        Handlers[EventTypes.TaskStarted](e, tasklist, undoStack);
                        return(Handlers[e.EventType](e, tasklist, undoStack));
                    }
                    else
                    {
                        throw new InvalidOperationException("Illegal event applied to a task id with no-corresponding-existing task. Eventtype: " + e.EventType);
                    }
                }
                // Handle cases where the task is deferred.
                // If the event is a creation event, we can safely dispose of the event without saving it, because the task has already progressed to a state beyond the state that those incoming events cause.
                // If the event is an undo-activation event, or undo-deletion event, we can skip execution of the event, but it must still be saved in order to handle out-of-order arrival; for example an activation event could 'fill the gap'.
                // If the event is a 'start task' or 'fail task', we are happy to fill in the implicit activation action.
                else if (task.Category == CategoryVals.Deferred)
                {
                    if (e.EventType == EventTypes.TaskAdded || e.EventType == EventTypes.ChildTaskAdded)
                    {
                        // Throw this event away, it is harmless and uneeded in this case.
                        saveEvent      = false;
                        triggerRefresh = false;
                        return(tasklist);
                    }
                    else if (e.EventType == EventTypes.TaskActivatedUndo || e.EventType == EventTypes.TaskDeletedUndo)
                    {
                        // Save this event, but do not execute the replay of this event, since it would have no effect. We still save it to handle out of order arrival.
                        saveEvent      = true;
                        triggerRefresh = false;
                        return(tasklist);
                    }
                    else if (e.EventType == EventTypes.TaskStarted || e.EventType == EventTypes.TaskFailed)
                    {
                        tasklist.ActivateTask(task, e.Category, e.Timestamp);
                        return(Handlers[e.EventType](e, tasklist, undoStack));
                    }
                    else
                    {
                        return(Handlers[e.EventType](e, tasklist, undoStack));
                    }
                }
                // Handle the cases where the state is denoted by progress status of the task (the only remaining option)
                else
                {
                    if (IncomingEventsWhichTriggerRefreshForProgressStatusMappings[task.ProgressStatus].Contains(e.EventType))
                    {
                        triggerRefresh = true;
                    }
                    if (IncomingEventsToIgnoreForProgressStatusMappings[task.ProgressStatus].Contains(e.EventType))
                    {
                        saveEvent = false;
                        return(tasklist);
                    }
                    if (IncomingEventsToSaveButNotExecuteForProgressStatusMappings[task.ProgressStatus].Contains(e.EventType))
                    {
                        saveEvent = true;
                        return(tasklist);
                    }
                    DoImplicitLinkingEventIfRequired(task, e, tasklist, undoStack);

                    return(Handlers[e.EventType](e, tasklist, undoStack));
                }
            }
            catch (InvalidOperationException exception) {
                // Add additional loggable information about the error that was thrown.
                throw new InvalidOperationException(exception.Message + ". When this error was thrown, the task was in state: " +
                                                    ((task == null) ? "NON-EXISTENT (NULL)" : (task.Category == CategoryVals.Deferred) ? "Deferred" : ProgressStatusVals.getString(task.ProgressStatus)),
                                                    exception);
            }
        }