public static IConnectableObservable <CourseSelectorState> Create(CourseSelectorActions actions, IObservable <IList <CourseData> > courseUpdates,
                                                                          IObservable <bool> isCourseEditorOpen)
        {
            var commands = actions.SelectCourse.Select(StoreAction.Create <string>("selectCourse"))
                           .Merge(actions.DeselectCourse.Select(StoreAction.Create <Unit>("deselectCourse")));

            // Only route commands when the course editor is open
            var filteredCommands = isCourseEditorOpen
                                   .Select(isOpen => isOpen ? commands : Observable.Empty <StoreAction <object> >())
                                   .Switch()
                                   .StartWith(new StoreAction <object>("deselectCourse", Unit.Default));

            return(courseUpdates
                   .CombineLatest(filteredCommands, (courses, command) => {
                Maybe <string> selectedCourse = Maybe.Nothing <string>();
                if (command.Id.Equals("selectCourse"))
                {
                    var selectedCourseId = (string)command.Arguments;
                    selectedCourse = courses
                                     .Find(c => c.Id.Equals(selectedCourseId))
                                     .Select(c => c.Id);
                }

                return new CourseSelectorState(
                    availableCourses: courses,
                    selectedCourse: selectedCourse,
                    // TODO Enable delete
                    isUndoDeleteCoursePossible: false);
            })
                   .Replay(1));
        }
        public static IDisposable CourseUpdater(CourseStorageActions actions, string coursesDirectory)
        {
            var commands = actions.CreateCourse.Select(StoreAction.Create <Unit>("createCourse"))
                           .Merge(actions.UpdateCourse.Select(StoreAction.Create <CourseData>("updateCourse")))
                           .Merge(actions.DeleteCourse.Select(StoreAction.Create <string>("deleteCourse")))
                           .Merge(actions.UndoDeleteCourse.Select(StoreAction.Create <Unit>("undoDeleteCourse")));

            return(commands
                   .Subscribe(command => {
                if (command.Id.Equals("createCourse"))
                {
                    var newCourse = CourseData.CreateNew();
                    CreateCourse(coursesDirectory, newCourse);
                }
                else if (command.Id.Equals("updateCourse"))
                {
                    var updatedCourse = (CourseData)command.Arguments;
                    UpdateCourse(coursesDirectory, updatedCourse);
                }
                else if (command.Id.Equals("deleteCourse"))
                {
                    var deletedCourseId = (string)command.Arguments;
                    DeleteCourse(coursesDirectory, deletedCourseId);
                }
                else if (command.Id.Equals("undoDeleteCourse"))
                {
                    var deletedCourseId = (string)command.Arguments;
                    UndoDeleteCourse(coursesDirectory, deletedCourseId);
                }
            }));
        }
        public static IObservable <CourseEditorState> Create(CourseEditorActions actions, CourseData courseData,
                                                             Ref <ImmutableTransform> cameraTransform)
        {
            var commands = actions.CreateProp.Select(StoreAction.Create <Unit>("createProp"))
                           .Merge(actions.CreatePropOnLocation.Select(StoreAction.Create <ImmutableTransform>("createPropOnLocation")))
                           .Merge(actions.DeleteProp.Select(StoreAction.Create <PropId>("deleteProp")))
                           .Merge(actions.DeleteSelectedProp.Select(StoreAction.Create <Unit>("deleteSelectedProp")))
                           .Merge(actions.UpdateProp.Select(StoreAction.Create <Tuple <PropId, ImmutableTransform> >("updateProp")))
                           .Merge(actions.HighlightProp.Select(StoreAction.Create <Maybe <PropId> >("highlightProp")))
                           .Merge(actions.SelectProp.Select(StoreAction.Create <Maybe <PropId> >("selectProp")))
                           .Merge(actions.SelectPropType.Select(StoreAction.Create <PropType>("selectPropType")))
                           .Merge(actions.ReorderProps.Select(StoreAction.Create <IImmutableList <PropId> >("reorderProps")))
                           .Merge(actions.Undo.Select(StoreAction.Create <Unit>("undo")))
                           .Merge(actions.Redo.Select(StoreAction.Create <Unit>("redo")));

            var initialHistoricalState = courseData.ToCourseEditorState();
            var updatesWithHistory     = commands
                                         .Scan(new AppState <HistoricalCourseEditorState>(initialHistoricalState), (currentAppState, command) => {
                var currentState = currentAppState.CurrentState;
                AppState <HistoricalCourseEditorState> newAppState = currentAppState;
                if (command.Id.Equals("createProp"))
                {
                    var propTransform = cameraTransform.Deref().TranslateLocally(new Vector3(0, 0, 30));
                    newAppState       = History.AddNewState(currentAppState, currentState.AddProp(propTransform));
                }
                else if (command.Id.Equals("createPropOnLocation"))
                {
                    newAppState = History.AddNewState(currentAppState, currentState.AddProp((ImmutableTransform)command.Arguments));
                }
                else if (command.Id.Equals("updateProp"))
                {
                    var args = (Tuple <PropId, ImmutableTransform>)command.Arguments;
                    var prop = currentState.Props[args._1];
                    var isTransformChanged = !prop.Transform.Equals(args._2);
                    if (isTransformChanged)
                    {
                        var updatedProp = prop.UpdateTransform(args._2);
                        newAppState     = History.AddNewState(currentAppState, currentState.UpdateProp(updatedProp));
                    }
                    else
                    {
                        newAppState = currentAppState;
                    }
                }
                else if (command.Id.Equals("deleteProp"))
                {
                    var propId = (PropId)command.Arguments;
                    if (currentState.PropOrder.Contains(propId))
                    {
                        newAppState = History.AddNewState(currentAppState, currentState.DeleteProp(propId));
                    }
                }
                else if (command.Id.Equals("deleteSelectedProp"))
                {
                    if (currentState.SelectedProp.IsJust)
                    {
                        newAppState = History.AddNewState(currentAppState, currentState.DeleteProp(currentState.SelectedProp.Value));
                    }
                }
                else if (command.Id.Equals("selectProp"))
                {
                    var selectedPropId = (Maybe <PropId>)command.Arguments;
                    if (selectedPropId.IsNothing ||
                        (selectedPropId.IsJust && currentState.Props.ContainsKey(selectedPropId.Value)))
                    {
                        newAppState = History.AddNewState(currentAppState, currentState.SelectProp(selectedPropId));
                    }
                }
                else if (command.Id.Equals("selectPropType"))
                {
                    newAppState = History.AddNewState(currentAppState, currentState.SelectPropType((PropType)command.Arguments));
                }
                else if (command.Id.Equals("reorderProps"))
                {
                    newAppState = History.AddNewState(currentAppState, currentState.UpdatePropOrder((IImmutableList <PropId>)command.Arguments));
                }
                else if (command.Id.Equals("undo"))
                {
                    newAppState = History.Undo(currentAppState);
                }
                else if (command.Id.Equals("redo"))
                {
                    newAppState = History.Redo(currentAppState);
                }
                else
                {
                    newAppState = currentAppState;
                }

                return(newAppState);
            })
                                         .Select(appState => {
                return(appState.CurrentState.UpdateUndoRedoAvailability(
                           !appState.UndoStack.IsEmpty, !appState.RedoStack.IsEmpty));
            })
                                         .StartWith(initialHistoricalState)
                                         .Publish();

            var initialTransientState = new TransientCourseEditorState(courseData.Name, Maybe.Nothing <PropId>(), TransformTool.Move);
            var transientUpdates      = actions.HighlightProp.Select(StoreAction.Create <Maybe <PropId> >("highlightProp"))
                                        .Merge(actions.SelectTransformTool.Select(StoreAction.Create <TransformTool>("selectTransformTool")))
                                        .Merge(actions.UpdateName.Select(StoreAction.Create <string>("updateName")))
                                        .Scan(initialTransientState, (state, command) => {
                if (command.Id.Equals("highlightProp"))
                {
                    return(state.HighlightProp((Maybe <PropId>)command.Arguments));
                }
                else if (command.Id.Equals("selectTransformTool"))
                {
                    // TODO Prevent transform tool selection when no prop is selected
                    return(state.SelectTransformTool((TransformTool)command.Arguments));
                }
                else if (command.Id.Equals("updateName"))
                {
                    return(state.UpdateName((string)command.Arguments));
                }
                return(state);
            })
                                        .DistinctUntilChanged()
                                        .StartWith(initialTransientState)
                                        .CombineLatest(updatesWithHistory, (transientState, histState) => {
                // Check if the highlighted prop still exists
                var highlightedProp          = transientState.HighlightedProp;
                var isHighlightedItemDeleted = highlightedProp.IsJust &&
                                               !histState.Props.ContainsKey(highlightedProp.Value);

                if (isHighlightedItemDeleted)
                {
                    return(transientState.HighlightProp(Maybe.Nothing <PropId>()));
                }
                return(transientState);
            });

            var updates = updatesWithHistory.CombineLatest(
                transientUpdates, (histState, transientState) => new CourseEditorState(histState, transientState))
                          .DistinctUntilChanged()
                          .Replay(1);

            updates.Connect();
            updatesWithHistory.Connect();

            return(updates);
        }