'use strict'

const subscriptions = new Map()
let globalState = {}

function trigger (target, stack) {
  let link = subscriptions.get(target)
  if (link) {
    stack.forEach((v, k) => link.has(k) && link.get(k).forEach(f => f(target[k])))
  }
}

// TODO: remove set from undo/redo actions (use update instead)
let undoRedo = {
  update (story, i) {
    let stack = story.stack,
        target = story.target
    stack.forEach((val, k) => {
      if (val[i] !== null) {
        target[k] = val[i]
      } else if (k in target) {
        delete target[k]
      }
    })
  }
}

function createStore (name, store = {}) {
  if (!name) throw new Error('boxes needs a name')

  globalState[name] = store
  let history = []
  let hIndex = 0

  function get () {
    return globalState[name]
  }

  function applySet (target, key, fresh) {
    if (target[key] !== fresh) {
      let old = key in target ? target[key] : null
      let stack = new Map().set(key, [old, fresh])
      history[hIndex++] = { target, stack, action: 'update' }
      target[key] = fresh
      trigger(target, stack)
    }
  }

  function set (key, value) {
    if (!key || typeof key !== 'string') {
      throw new Error('setAt requires a string key')
    }
    applySet(globalState[name], key, value)
  }

  function setIn (target, key, value) {
    if (!key || typeof key !== 'string') {
      throw new Error('setIn requires a string key')
    }
    if (!target || typeof target !== 'object') {
      throw new Error('setIn requires a object target')
    }
    applySet(target, key, value)
  }

  function update (props) {
    updateIn(globalState[name], props)
  }

  function updateIn (target, props) {
    if (typeof target !== 'object') throw new Error('update requires a object target')
    if (typeof props !== 'object') throw new Error('update requires a object props')

    let stack = new Map()
    Object.keys(props).forEach(k => {
      let fresh = props[k],
          old = target[k]
      if (old !== fresh) {
        if (fresh !== null) {
          stack.set(k, [old, fresh])
          target[k] = fresh
        } else if (k in target) {
          stack.set(k, [old, fresh])
          delete target[k]
        }
      }
    })

    if (stack.size) {
      history[hIndex++] = { target, stack, action: 'update' }
      trigger(target, stack)
    }
  }

  function clear (prop) {
    clearIn(globalState[name], prop)
  }

  function clearIn (target, prop) {
    let obj = target[prop]
    let stack = new Map()
    if (obj) {
      Object.keys(obj).forEach(k => {
        stack.set(k, [obj[k], null])
        delete obj[k]
      })
    }

    if (stack.size) {
      history[hIndex++] = { target: obj, stack, action: 'update' }
      trigger(obj, stack)
    }
  }

  function prevState () {
    if (hIndex) {
      let story = history[--hIndex]
      undoRedo[story.action](story, 0)
      trigger(story.target, story.stack)
    }
  }

  function nextState () {
    if (history[hIndex]) {
      let story = history[hIndex++]
      undoRedo[story.action](story, 1)
      trigger(story.target, story.stack)
    }
  }

  function subscribe (target, key, action) {
    let map = subscriptions.get(target) || subscriptions.set(target, new Map()).get(target)
    let link = map.get(key) || map.set(key, new Set()).get(key)
    link.add(action)
    return () => link.delete(action)
  }

  function getBox (target) {
    return {
      get () {
        return target
      },
      set (key, value) {
        if (!key || typeof key !== 'string') {
          throw new Error('set requires a string key')
        }
        applySet(target, key, value)
      },
      update (props) {
        updateIn(target, props)
      },
      setIn, updateIn, subscribe, getBox
    }
  }

  return {
    get: get,
    set: set, setIn,
    update, updateIn,
    clear, clearIn,
    getBox,
    prevState, nextState,
    subscribe }
}

function has (store) {
  return globalState[store] ? true : false
}

function remove (store) {
  delete globalState[store]
}

module.exports = { createStore, has, remove }

@@ -303,19 +303,24 @@
 nstead)%0A
-let
+function
  undoRed
@@ -325,21 +325,8 @@
 edo 
-= %7B%0A  update 
 (sto
@@ -330,26 +330,24 @@
 story, i) %7B%0A
-  
   let stack 
@@ -363,26 +363,24 @@
 tack,%0A      
-  
 target = sto
@@ -389,18 +389,16 @@
 .target%0A
-  
   stack.
@@ -419,26 +419,24 @@
 k) =%3E %7B%0A    
- 
- 
 if (val%5Bi%5D !
@@ -448,26 +448,24 @@
 ll) %7B%0A      
-  
 target%5Bk%5D = 
@@ -471,18 +471,16 @@
  val%5Bi%5D%0A
-  
     %7D el
@@ -503,26 +503,24 @@
 et) %7B%0A      
-  
 delete targe
@@ -528,26 +528,55 @@
 %5Bk%5D%0A
-  
     %7D%0A
-  
   %7D)%0A  
-%7D
+trigger(story.target, story.stack)
 %0A%7D%0A%0A
@@ -2846,72 +2846,17 @@
 Redo
-%5Bstory.action%5D(story, 0)%0A      trigger(story.target, story.stack
+(story, 0
 )%0A  
@@ -2971,72 +2971,17 @@
 Redo
-%5Bstory.action%5D(story, 1)%0A      trigger(story.target, story.stack
+(story, 1
 )%0A  

refactor undoRedo