import { isFunction, forEach, uuid, substanceGlobals } from '../util'
import { DefaultDOMElement } from '../dom'
import VirtualElement from './VirtualElement'
import Component from './Component'

/*

  ## Rendering Algorithm

  TODO: document the algorithm

  ## Findings

  What makes our rendering algorithm so difficult?

  - Dependency Injection requires a (direct) parent to be allow constructor injection, i.e. that injected dependencies
    are available in the constructor already. As a consequence a component tree must to be constructed from top to down.

  - The earliest time to evaluate `$$(MyComponent)`, is when it has been attached to an existing component.
    I.e., to run `MyComponent.render()` an instance of `MyComponent` is needed, which can only be created with an existing
    parent component.

  - In general, it is *not* possible to have a naturally descending rendering algorithm, i.e. a simple recursion calling
    `render()` and creating or updating Components on the way, preserving a simple stack-trace.
    Instead, it requires calling `render()` on one level, then doing comparisons with the existing tree
    to be able to reuse components, and then descend into the sub-tree.

  - If components are passed down via props, things get even more difficult.
    For example, consider a situation where components are passed via props:
    ```
      render($$) {
        return $$('div').append(
          $$(Wrapper, {
            foo: $$(MyComponent)
          })
        )
      }
    ```
    At the time when this component gets rendered, `MyComponent` can not be instantiated, as it is not known what `Wrapper`
    actually does with it.
    While the general approach is working from top-to-down, in this case it has a bottom-to-up nature, i.e., the child needs
    to be rendered to know what to do with the passed component.

    Particularly, this is problematic when the passed component has a reference:
    ```
      render($$) {
        return $$('div').append(
          $$(Wrapper, {
            foo: $$(MyComponent).ref('foo')
          })
        )
      }
    ```
    As nothing is known at the time of descending about the content of `Wrapper` the rendering algorithm
    can not tell that it ought to be preserved. For now, the correct way to deal with this situation is
    to use a reference for the wrapper as well:
    ```
      render($$) {
        return $$('div').append(
          $$(Wrapper, {
            foo: $$(MyComponent).ref('foo')
          }).ref('wrapper')
        )
      }
    ```

  ## TODO

  There is a lot of hacks in the current implementation, which should be cleaned up when we have time.
  - remove means to change props of VirtualComponent dynamically: currently, it is possible to do something like
    ```
      $$(MyComponent).attr({ "data-id": "foo" }).append('foo')
    ```
    ATM, `attr()` and `append()` represent a means to change the `props` of the component.
    This is pretty inconsistent and creates confusion about the responsibility for rendering the element.
    Only the Component's `render()` should be responsible for that. If necessary, children or anything like that must
    be passed down via props.
  - remove outlets: outlets are just another way to change props.
  - try to fuse `virtualComponent._content` into virtualComponent: ATM, `VirtualComponent` uses a `VirtualHTMLElement`
    instance to store the result of `render()`. This makes understanding the virtual tree after rendering difficult,
    as there is another layer via `virtualComponent._content`.
  - Rethink strategy for 'reusing' components: ATM we consider refs as the key indicator whether to preserve a component
    or not. This makes sense essentially, but could be extended to an opportunistic strategy where components are reused
    implicitly when they are at the right place.
*/
export default
class RenderingEngine {

  constructor(options = {}) {
    this.elementFactory = options.elementFactory || DefaultDOMElement.createDocument('html')
  }

  _render(comp, oldProps, oldState) {
    // var t0 = Date.now()
    var vel = _createWrappingVirtualComponent(comp)
    var state = new RenderingEngine.State(this.elementFactory)
    if (oldProps) {
      state.setOldProps(vel, oldProps)
    }
    if (oldState) {
      state.setOldState(vel, oldState)
    }
    try {
      // capture: this calls the render() method of components, creating a virtual DOM
      // console.log('### capturing...')
      // let t0 = Date.now()
      _capture(state, vel, 'forceCapture')
      // console.log('### ... finished in %s ms', Date.now()-t0)

      // console.log('### rendering...')
      // t0 = Date.now()
      _render(state, vel)
      // console.log('### ... finished in %s ms', Date.now()-t0)

      _triggerUpdate(state, vel)

    } finally {
      state.dispose()
    }
    // console.log("RenderingEngine: finished rendering in %s ms", Date.now()-t0)
  }

  // this is used together with the incremental Component API
  _renderChild(comp, vel) {
    // HACK: to make this work with the rest of the implementation
    // we ingest a fake parent
    var state = new RenderingEngine.State(this.elementFactory)
    vel.parent = { _comp: comp }
    try {
      _capture(state, vel)
      _render(state, vel)
      return vel._comp
    } finally {
      state.dispose()
    }
  }
}


function _create(state, vel) {
  var comp = vel._comp
  console.assert(!comp, "Component instance should not exist when this method is used.")
  var parent = vel.parent._comp
  // making sure the parent components have been instantiated
  if (!parent) {
    parent = _create(state, vel.parent)
  }
  if (vel._isVirtualComponent) {
    console.assert(parent, "A Component should have a parent.")
    comp = new vel.ComponentClass(parent, vel.props)
    // HACK: making sure that we have the right props
    vel.props = comp.props
    comp.__htmlConfig__ = vel._copyHTMLConfig()
  } else if (vel._isVirtualHTMLElement) {
    comp = new Component.Element(parent, vel)
  } else if (vel._isVirtualTextNode) {
    comp = new Component.TextNode(parent, vel)
  }
  if (vel._ref) {
    comp._ref = vel._ref
  }
  if (vel._owner) {
    comp._owner = vel._owner._comp
  }
  vel._comp = comp
  return comp
}

function _capture(state, vel, forceCapture) {
  if (state.isCaptured(vel)) {
    return vel
  }
  // a captured VirtualElement has a component instance attached
  var comp = vel._comp
  if (!comp) {
    comp = _create(state, vel)
    state.setNew(vel)
  }
  if (vel._isVirtualComponent) {
    var needRerender
    // NOTE: forceCapture is used for the first entrance
    // from this.render(comp) where we want to fource capturing
    // as it has already been cleared that a rerender is necessary
    if (forceCapture) {
      needRerender = true
    } else {
      // NOTE: don't ask shouldRerender if no element is there yet
      needRerender = !comp.el || comp.shouldRerender(vel.props, comp.state)
      comp.__htmlConfig__ = vel._copyHTMLConfig()
      state.setOldProps(vel, comp.props)
      state.setOldState(vel, comp.state)
      // updates prop triggering willReceiveProps
      comp._setProps(vel.props)
      if (!state.isNew(vel)) {
        state.setUpdated(vel)
      }
    }
    if (needRerender) {
      var context = new CaptureContext(vel)
      var content = comp.render(context.$$)
      if (!content || !content._isVirtualHTMLElement) {
        throw new Error("Component.render must return VirtualHTMLElement")
      }

      if (comp.__htmlConfig__) {
        content._mergeHTMLConfig(comp.__htmlConfig__)
      }
      content._comp = comp
      vel._content = content
      if (!state.isNew(vel) && comp.isMounted()) {
        state.setUpdated(vel)
      }
      // Mapping: map virtual elements to existing components based on refs
      _prepareVirtualComponent(state, comp, content)
      // Descending
      // TODO: only do this in DEBUG mode
      if (substanceGlobals.DEBUG_RENDERING) {
        // in this case we use the render() function as iterating function, where
        // $$ is a function which creates components and renders them recursively.
        // first we can create all element components that can be reached
        // without recursion
        var stack = content.children.slice(0)
        while (stack.length) {
          var child = stack.shift()
          if (state.isCaptured(child)) continue
          // virtual components are addressed via recursion, not captured here
          if (child._isVirtualComponent) continue
          if (!child._comp) {
            _create(state, child)
          }
          if (child._isVirtualHTMLElement && child.children.length > 0) {
            stack = stack.concat(child.children)
          }
          state.setCaptured(child)
        }
        state.setCaptured(content)
        // then we run comp.render($$) with a special $$ that captures VirtualComponent's
        // recursively
        var descendingContext = new DescendingContext(state, context)
        while (descendingContext.hasPendingCaptures()) {
          descendingContext.reset()
          comp.render(descendingContext.$$)
        }
      } else {
        // a VirtualComponent has its content as a VirtualHTMLElement
        // which needs to be captured recursively
        _capture(state, vel._content)
      }
    } else {
      state.setSkipped(vel)
    }
  } else if (vel._isVirtualHTMLElement) {
    for (var i = 0; i < vel.children.length; i++) {
      _capture(state, vel.children[i])
    }
  }
  state.setCaptured(vel)
  return vel
}

function _render(state, vel) {
  if (state.isSkipped(vel)) return
  // console.log('... rendering', vel._ref)

  // before changes can be applied, a VirtualElement must have been captured
  // FIXME: with DEBUG_RENDERING we are having troubles with this assumption.
  // It happens when the rerendered component is having children injected from its parent.
  // Then the parent is no rerendered this, these injected components are not recaptured, and this assertion does not hold.
  // However, it seems not to be critical, as these components don't need to be rerendered
  // Still we should find a consistent way

  let comp = vel._comp
  console.assert(comp && comp._isComponent, "A captured VirtualElement must have a component instance attached.")

  // VirtualComponents apply changes to its content element
  if (vel._isVirtualComponent) {
    _render(state, vel._content)

    // store refs and foreignRefs
    const context = vel._content._context
    let refs = {}
    let foreignRefs = {}
    forEach(context.refs, (vel, ref) => {
      refs[ref] = vel._comp
    })
    forEach(context.foreignRefs, (vel, ref) => {
      foreignRefs[ref] = vel._comp
    })
    comp.refs = refs
    comp.__foreignRefs__ = foreignRefs
    return
  }

  // render the element
  if (!comp.el) {
    comp.el = _createElement(state, vel)
    comp.el._comp = comp
  }
  _updateElement(comp, vel)

  // structural updates are necessary only for HTML elements (without innerHTML set)
  if (vel._isVirtualHTMLElement && !vel.hasInnerHTML()) {
    var newChildren = vel.children
    var oldComp, virtualComp, newComp
    var pos1 = 0; var pos2 = 0

    // HACK: removing all childNodes that are not owned by a component
    // this happened in Edge every 1s. Don't know why.
    // With this implementation all external DOM mutations will be eliminated
    var oldChildren = []
    comp.el.getChildNodes().forEach(function(node) {
      var childComp = node._comp

      // TODO: to allow mounting a prerendered DOM element
      // we would need to allow to 'take ownership' instead of removing
      // the element. This being a special situation, we should only
      // do that when mounting

      // remove orphaned nodes and relocated components
      if (!childComp || state.isRelocated(childComp)) {
        comp.el.removeChild(node)
      } else {
        oldChildren.push(childComp)
      }
    })

    while(pos1 < oldChildren.length || pos2 < newChildren.length) {
      // skip detached components
      // Note: components get detached when preserved nodes
      // are found in a swapped order. Then the only way is
      // to detach one of them from the DOM, and reinsert it later at the new position
      do {
        oldComp = oldChildren[pos1++]
      } while (oldComp && (state.isDetached(oldComp)))

      virtualComp = newChildren[pos2++]
      // remove remaining old ones if no new one is left
      if (oldComp && !virtualComp) {
        while (oldComp) {
          _removeChild(state, comp, oldComp)
          oldComp = oldChildren[pos1++]
        }
        break
      }

      // Try to reuse TextNodes to avoid unnecesary DOM manipulations
      if (oldComp && oldComp.el.isTextNode() &&
          virtualComp && virtualComp._isVirtualTextNode &&
          oldComp.el.textContent === virtualComp.text ) {
        continue
      }

      // deep-render the virtual component if not yet done
      if (!state.isRendered(virtualComp)) {
        _render(state, virtualComp)
      }

      newComp = virtualComp._comp

      // update the parent for relocated components
      // ATTENTION: relocating a component does not update its context
      if (state.isRelocated(newComp)) {
        newComp._setParent(comp)
      }

      console.assert(newComp, 'Component instance should now be available.')

      // append remaining new ones if no old one is left
      if (virtualComp && !oldComp) {
        _appendChild(state, comp, newComp)
        continue
      }
      // Differential update
      else if (state.isMapped(virtualComp)) {
        // identity
        if (newComp === oldComp) {
          // no structural change
        } else if (state.isMapped(oldComp)) {
          // the order of elements with ref has changed
          state.setDetached(oldComp)
          _removeChild(state, comp, oldComp)
          pos2--
        }
        // the old one could not be mapped, thus can be removed
        else {
          _removeChild(state, comp, oldComp)
          pos2--
        }
      }
      else if (state.isMapped(oldComp)) {
        _insertChildBefore(state, comp, newComp, oldComp)
        pos1--
      }
      else {
        // both elements are not mapped
        // TODO: we could try to reuse components if they are of same type
        // However, this needs a better mapping strategy, not only
        // based on refs.
        _replaceChild(state, comp, oldComp, newComp)
      }
    }
  }

  state.setRendered(vel)
}

function _triggerUpdate(state, vel) {
  if (vel._isVirtualComponent) {
    if (!state.isSkipped(vel)) {
      vel._content.children.forEach(_triggerUpdate.bind(null, state))
    }
    if (state.isUpdated(vel)) {
      vel._comp.didUpdate(state.getOldProps(vel), state.getOldState(vel))
    }
  } else if (vel._isVirtualHTMLElement) {
    vel.children.forEach(_triggerUpdate.bind(null, state))
  }
}

function _appendChild(state, parent, child) {
  parent.el.appendChild(child.el)
  _triggerDidMount(state, parent, child)
}

function _replaceChild(state, parent, oldChild, newChild) {
  parent.el.replaceChild(oldChild.el, newChild.el)
  if (!state.isDetached(oldChild)) {
    oldChild.triggerDispose()
  }
  _triggerDidMount(state, parent, newChild)
}

function _insertChildBefore(state, parent, child, before) {
  parent.el.insertBefore(child.el, before.el)
  _triggerDidMount(state, parent, child)
}

function _removeChild(state, parent, child) {
  parent.el.removeChild(child.el)
  if (!state.isDetached(child)) {
    child.triggerDispose()
  }
}

function _triggerDidMount(state, parent, child) {
  if (!state.isDetached(child) &&
      parent.isMounted() && !child.isMounted()) {
    child.triggerDidMount(true)
  }
}

/*
  Prepares a new virtual component by comparing it with
  the old version.

  It sets the _comp references in the new version where its ancestors
  can be mapped to corresponding virtual components in the old version.
*/
function _prepareVirtualComponent(state, comp, vc) {
  var newRefs = {}
  var foreignRefs = {}
  // TODO: iron this out. refs are stored on the context
  // though, it would be cleaner if they were on the VirtualComponent
  // Where vc._owner would need to be a VirtualComponent and not a
  // component.
  if (vc._context) {
    newRefs = vc._context.refs
    foreignRefs = vc._context.foreignRefs
  }
  var oldRefs = comp.refs
  var oldForeignRefs = comp.__foreignRefs__
  // map virtual components to existing ones
  forEach(newRefs, function(vc, ref) {
    var comp = oldRefs[ref]
    if (comp) _mapComponents(state, comp, vc)
  })
  forEach(foreignRefs, function(vc, ref) {
    var comp = oldForeignRefs[ref]
    if (comp) _mapComponents(state, comp, vc)
  })
}

/*
  This tries to map the virtual component to existing component instances
  by looking at the old and new refs, making sure that the element type is
  compatible.
  This is then applied to the ancestors leading to an implicit
  mapping of parent elements, which makes
*/

function _mapComponents(state, comp, vc) {
  if (!comp && !vc) return true
  if (!comp || !vc) return false
  // Stop if one them has been mapped already
  // or the virtual element has its own component already
  // or if virtual element and component do not match semantically
  // Note: the owner component is mapped at very first, so this
  // recursion will stop at the owner at the latest.
  if (state.isMapped(vc) || state.isMapped(comp)) {
    return vc._comp === comp
  }
  if (vc._comp) {
    if (vc._comp === comp) {
      state.setMapped(vc)
      state.setMapped(comp)
      return true
    } else {
      return false
    }
  }
  if (!_isOfSameType(comp, vc)) {
    return false
  }

  vc._comp = comp
  state.setMapped(vc)
  state.setMapped(comp)

  var canMapParent
  var parent = comp.getParent()
  if (vc.parent) {
    canMapParent = _mapComponents(state, parent, vc.parent)
  }
  // to be able to support implicit retaining of elements
  // we need to propagate mapping through the 'preliminary' parent chain
  // i.e. not taking the real parents as rendered, but the Components into which
  // we have passed children (via vel.append() or vel.outlet().append())
  else if (vc._preliminaryParent) {
    while (parent && parent._isElementComponent) {
      parent = parent.getParent()
    }
    canMapParent = _mapComponents(state, parent, vc._preliminaryParent)
  }
  if (!canMapParent) {
    state.setRelocated(vc)
    state.setRelocated(comp)
  }
  return canMapParent
}

function _isOfSameType(comp, vc) {
  return (
    (comp._isElementComponent && vc._isVirtualHTMLElement) ||
    (comp._isComponent && vc._isVirtualComponent && comp.constructor === vc.ComponentClass) ||
    (comp._isTextNodeComponent && vc._isVirtualTextNode)
  )
}

function _createElement(state, vel) {
  var el
  if (vel._isVirtualTextNode) {
    el = state.elementFactory.createTextNode(vel.text)
  } else {
    el = state.elementFactory.createElement(vel.tagName)
  }
  return el
}

function _updateElement(comp, vel) {
  if (comp._isTextNodeComponent) {
    comp.setTextContent(vel.text)
    return
  }
  var el = comp.el
  console.assert(el, "Component's element should exist at this point.")
  var tagName = el.getTagName()
  if (vel.tagName.toLowerCase() !== tagName) {
    el.setTagName(vel.tagName)
  }
  _updateHash({
    oldHash: el.getAttributes(),
    newHash: vel.getAttributes(),
    update: function(key, val) {
      el.setAttribute(key, val)
    },
    remove: function(key) {
      el.removeAttribute(key)
    }
  })
  _updateHash({
    oldHash: el.htmlProps,
    newHash: vel.htmlProps,
    update: function(key, val) {
      el.setProperty(key, val)
    },
    remove: function(key) {
      el.removeProperty(key)
    }
  })
  _updateListeners({
    el: el,
    oldListeners: el.getEventListeners(),
    newListeners: vel.getEventListeners()
  })

  // special treatment of HTML elements having custom innerHTML
  if (vel.hasInnerHTML()) {
    if (!el._hasInnerHTML) {
      el.empty()
      el.setInnerHTML(vel.getInnerHTML())
    } else {
      var oldInnerHTML = el.getInnerHTML()
      var newInnerHTML = vel.getInnerHTML()
      if (oldInnerHTML !== newInnerHTML) {
        el.setInnerHTML(newInnerHTML)
      }
    }
    el._hasInnerHTML = true
  }
}

function _updateHash(args) {
  const newHash = args.newHash
  const oldHash = args.oldHash || {}
  const update = args.update
  const remove = args.remove
  let updatedKeys = {}
  for (let key in newHash) {
    if (newHash.hasOwnProperty(key)) {
      var oldVal = oldHash[key]
      var newVal = newHash[key]
      updatedKeys[key] = true
      if (oldVal !== newVal) {
        update(key, newVal)
      }
    }
  }
  // HACK: we have a horrible mixture of Objects and
  // Maps here
  if (isFunction(oldHash.keys) && oldHash.size > 0) {
    let keys = Array.from(oldHash.keys())
    keys.forEach((key) => {
      if (!updatedKeys[key]) {
        remove(key)
      }
    })
  } else {
    for (let key in oldHash) {
      if (oldHash.hasOwnProperty(key) && !updatedKeys[key]) {
        remove(key)
      }
    }
  }
}

function _updateListeners(args) {
  var el = args.el
  // NOTE: considering the low number of listeners
  // it is quicker to just remove all
  // and add again instead of computing the minimal update
  var newListeners = args.newListeners || []
  el.removeAllEventListeners()
  for (var i=0; i<newListeners.length;i++) {
    el.addEventListener(newListeners[i])
  }
}


/*
  Descending Context Used by RenderingEngine
*/

class DescendingContext {
  constructor(state, captureContext) {
    this.state = state
    this.owner = captureContext.owner
    this.refs = {}
    this.foreignRefs = {}
    this.elements = captureContext.elements
    this.pos = 0
    this.updates = captureContext.components.length
    this.remaining = this.updates

    this.$$ = this._createComponent.bind(this)
  }

  _createComponent() {
    var state = this.state
    var vel = this.elements[this.pos++]
    // only capture VirtualComponent's with a captured parent
    // all others have been captured at this point already
    // or will either be captured by a different owner
    if (!state.isCaptured(vel) && vel._isVirtualComponent &&
         vel.parent && state.isCaptured(vel.parent)) {
      _capture(state, vel)
      this.updates++
      this.remaining--
    }
    // Note: we return a new VirtualElement so that the render method does work
    // as expected.
    // TODO: instead of creating a new VirtualElement each time, we could return
    // an immutable wrapper for the already recorded element.
    vel = VirtualElement.createElement.apply(this, arguments)
    // these variables need to be set make the 'ref()' API work
    vel._context = this
    vel._owner = this.owner
    // Note: important to deactivate these methods as otherwise the captured
    // element will be damaged when calling el.append()
    vel._attach = function() {}
    vel._detach = function() {}
    return vel
  }

  hasPendingCaptures() {
    return this.updates > 0 && this.remaining > 0
  }

  reset() {
    this.pos = 0
    this.updates = 0
    this.refs = {}
  }

  _ancestorsReady(vel) {
    while (vel) {
      if (this.state.isCaptured(vel) ||
          // TODO: iron this out
          vel === this.owner || vel === this.owner._content) {
        return true
      }
      vel = vel.parent
    }
    return false
  }


}

RenderingEngine._internal = {
  _capture: _capture,
  _wrap: _createWrappingVirtualComponent,
}

class CaptureContext {
  constructor(owner) {
    this.owner = owner
    this.refs = {}
    this.foreignRefs = {}
    this.elements = []
    this.components = []
    this.$$ = this._createComponent.bind(this)
    this.$$.capturing = true
  }

  _createComponent() {
    var vel = VirtualElement.createElement.apply(this, arguments)
    vel._context = this
    vel._owner = this.owner
    if (vel._isVirtualComponent) {
      // virtual components need to be captured recursively
      this.components.push(vel)
    }
    this.elements.push(vel)
    return vel
  }
}


function _createWrappingVirtualComponent(comp) {
  var vel = new VirtualElement.Component(comp.constructor)
  vel._comp = comp
  if (comp.__htmlConfig__) {
    vel._mergeHTMLConfig(comp.__htmlConfig__)
  }
  return vel
}

RenderingEngine.createContext = function(comp) {
  var vel = _createWrappingVirtualComponent(comp)
  return new CaptureContext(vel)
}


class RenderingState {

  constructor(elementFactory) {
    this.elementFactory = elementFactory
    this.poluted = []
    this.id = "__"+uuid()
  }

  dispose() {
    var id = this.id
    this.poluted.forEach(function(obj) {
      delete obj[id]
    })
  }

  set(obj, key, val) {
    var info = obj[this.id]
    if (!info) {
      info = {}
      obj[this.id] = info
      this.poluted.push(obj)
    }
    info[key] = val
  }

  get(obj, key) {
    var info = obj[this.id]
    if (info) {
      return info[key]
    }
  }

  setMapped(c) {
    this.set(c, 'mapped', true)
  }


  isMapped(c) {
    return Boolean(this.get(c, 'mapped'))
  }

  // 'relocated' means a node with ref
  // has been attached to a new parent node
  setRelocated(c) {
    this.set(c, 'relocated', true)
  }

  isRelocated(c) {
    return Boolean(this.get(c, 'relocated'))
  }

  setDetached(c) {
    this.set(c, 'detached', true)
  }

  isDetached(c) {
    return Boolean(this.get(c, 'detached'))
  }

  setCaptured(vc) {
    this.set(vc, 'captured', true)
  }

  isCaptured(vc) {
    return Boolean(this.get(vc, 'captured'))
  }

  setNew(vc) {
    this.set(vc, 'created', true)
  }

  isNew(vc) {
    return Boolean(this.get(vc, 'created'))
  }

  setUpdated(vc) {
    this.set(vc, 'updated', true)
  }

  isUpdated(vc) {
    return Boolean(this.get(vc, 'updated'))
  }

  setSkipped(vc) {
    this.set(vc, 'skipped', true)
  }

  isSkipped(vc) {
    return Boolean(this.get(vc, 'skipped'))
  }

  setRendered(vc) {
    this.set(vc, 'rendered', true)
  }

  isRendered(vc) {
    return Boolean(this.get(vc, 'rendered'))
  }

  setOldProps(vc, oldProps) {
    this.set(vc, 'oldProps', oldProps)
  }

  getOldProps(vc) {
    return this.get(vc, 'oldProps')
  }

  setOldState(vc, oldState) {
    this.set(vc, 'oldState', oldState)
  }

  getOldState(vc) {
    return this.get(vc, 'oldState')
  }
}

RenderingEngine.State = RenderingState