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 HistoricalCourseEditorState ToCourseEditorState(this CourseData courseData)
        {
            var selectedPropType = PropType.RingNormal;
            var propOrder        = ImmutableList <PropId> .Empty;
            var props            = ImmutableDictionary <PropId, EditorProp> .Empty;

            for (int i = 0; i < courseData.Props.Count; i++)
            {
                var prop       = courseData.Props[i];
                var editorProp = new EditorProp {
                    Id = PropId.CreateRandomId(), PropType = prop.PropType, Transform = prop.Transform
                };

                propOrder = propOrder.Add(editorProp.Id);
                props     = props.Add(editorProp.Id, editorProp);

                selectedPropType = editorProp.PropType;
            }
            // Automatically select the first prop if possible
            //var selectedProp = propOrder.Count > 0 ? Maybe.Just(propOrder[0]) : Maybe.Nothing<PropId>();
            var selectedProp = Maybe.Nothing <PropId>();

            return(new HistoricalCourseEditorState(courseData.Id, selectedPropType, selectedProp, props,
                                                   propOrder, false, false));
        }
        public static void CreateCourse(string coursesDirectory, CourseData courseData)
        {
            var tempFilePath = "~" + FileName(courseData) + ".temp." + Guid.NewGuid();

            tempFilePath = Path.Combine(coursesDirectory, tempFilePath);
            Write2Disk(tempFilePath, courseData);
            var coursePath = Path.Combine(coursesDirectory, FileName(courseData));

            File.Delete(coursePath);
            File.Move(tempFilePath, coursePath);
        }
        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);
        }
 public static void UpdateCourse(string coursesDirectory, CourseData courseData)
 {
     PermanentlyDeleteCourse(coursesDirectory, courseData.Id);
     CreateCourse(coursesDirectory, courseData);
 }
 public static void Write2Disk(string courseFileName, CourseData courseData)
 {
     using (var writer = File.CreateText(courseFileName)) {
         CourseSerialization.Serializer.Serialize(writer, courseData);
     }
 }
 public static string FileName(CourseData courseData)
 {
     return(FileName(courseData.Id, courseData.Name));
 }