# model/UndoRedoManager.js

    import * as InkModel from '../model/InkModel'
    import * as UndoRedoContext from '../model/UndoRedoContext'
    import { modelLogger as logger } from '../configuration/LoggerConfig'
    import Constants from '../configuration/Constants'

    /**
     * Undo/redo manager
     * @typedef {Object} UndoRedoManager
     * @property {function} updateModel Push the current model into the undo/redo context.
     * @property {function} undo Undo.
     * @property {function} redo Redo.
     * @property {function} clear Clear.
     */

    /**
     * Get current model in stack
     * @param {UndoRedoContext} undoRedoContext Current undo/redo context
     * @param {Boolean} [clone=true] Whether or not to clone the model
     * @param {...String} types
     */
    export function getModel (undoRedoContext, clone = true, ...types) {
      const model = undoRedoContext.stack[undoRedoContext.currentPosition]
      const val = {
        res: clone ? InkModel.cloneModel(model) : model,
        types
      }
      return Promise.resolve(val)
    }

    /**
     * Mutate the undoRedo stack by adding a new model to it.
     * @param {UndoRedoContext} undoRedoContext Current undo/redo context.
     * @param {Model} model Current model.
     */
    export function updateModel (undoRedoContext, model) {
      // Used to update the model with the recognition result if relevant
      const modelIndex = undoRedoContext.stack.findIndex(item => (item.modificationTime === model.modificationTime) && (item.rawStrokes.length === model.rawStrokes.length))

      const modelReference = model
      modelReference.modificationTime = new Date().getTime()

      const types = []
      if (modelIndex > -1) {
        undoRedoContext.stack.splice(modelIndex, 1, InkModel.cloneModel(modelReference))
        logger.debug('model updated', modelReference)
      } else {
        const undoRedoContextReference = undoRedoContext
        undoRedoContextReference.currentPosition += 1
        undoRedoContextReference.stack = undoRedoContextReference.stack.slice(0, undoRedoContextReference.currentPosition)
        undoRedoContextReference.stack.push(InkModel.cloneModel(modelReference))
        if (undoRedoContextReference.stack.length > undoRedoContextReference.maxSize) {
          undoRedoContextReference.stack.shift()
          undoRedoContextReference.currentPosition--
        }
        logger.debug('model pushed', modelReference)
        types.push(Constants.EventType.CHANGED)
      }
      UndoRedoContext.updateUndoRedoState(undoRedoContext)
      logger.debug('undo/redo stack updated', undoRedoContext)
      return getModel(undoRedoContext, false, ...types)
    }

    /**
     * Undo
     * @param {UndoRedoContext} undoRedoContext Current undo/redo context.
     * @param {Model} model Current model.
     */
    export function undo (undoRedoContext, model) {
      const undoRedoContextReference = undoRedoContext
      if (undoRedoContextReference.currentPosition > 0) {
        undoRedoContextReference.currentPosition -= 1
        UndoRedoContext.updateUndoRedoState(undoRedoContext)
        logger.debug('undo index', undoRedoContextReference.currentPosition)
      }
      return getModel(undoRedoContext, true, Constants.EventType.CHANGED, Constants.EventType.EXPORTED)
    }

    /**
     * Redo
     * @param {UndoRedoContext} undoRedoContext Current undo/redo context.
     * @param {Model} model Current model.
     */
    export function redo (undoRedoContext, model) {
      const undoRedoContextReference = undoRedoContext
      if (undoRedoContextReference.currentPosition < undoRedoContextReference.stack.length - 1) {
        undoRedoContextReference.currentPosition += 1
        UndoRedoContext.updateUndoRedoState(undoRedoContext)
        logger.debug('redo index', undoRedoContextReference.currentPosition)
      }
      return getModel(undoRedoContext, true, Constants.EventType.CHANGED, Constants.EventType.EXPORTED)
    }