'use strict'
const INIT = 'new'
const UPDATE = 'update'
const REMOVE = 'remove'
const merge = require('lodash.merge')

module.exports = function (target, subscription, update, tree) {
  if (!tree) { tree = {} }
  target.on('subscription', function (data, event) {
    handleUpdate(target, subscription, update, tree, event, tree)
  })
  target._subscriptions = true
  handleUpdate(target, subscription, update, tree, void 0, tree)
  return tree
}

function handleUpdate (target, subscription, update, tree, event, roottree) {
  for (let key in subscription) {
    handleItem(key, target, subscription, subscription[key], update, tree, event, roottree)
  }
}

function handleItem (key, target, subscription, subs, update, tree, event, roottree) {
  if (key !== 'val') {
    if (key === '~') {
      root(subs, target, subscription, update, tree, event, roottree)
    } else if (key === '*') {
      collection(subs, target, subscription, update, tree, event, roottree)
    } else if (subs === true) {
      leaf(key, target, subscription, update, tree, event)
    } else {
      struct(subs, key, target, subscription, update, tree, event, roottree)
    }
    // can use the same for remove (cleanup)
  }
}

function leaf (key, target, subscription, update, tree, event) {
  var keyTarget = target[key]
  var treeKey = tree[key]
  if (keyTarget && keyTarget.__input !== null) {
    let stamp = generateStamp(keyTarget, treeKey)
    if (!tree[key]) {
      tree[key] = stamp
      update.call(keyTarget, INIT, event, subscription)
    } else if (treeKey !== stamp) {
      tree[key] = stamp
      update.call(keyTarget, UPDATE, event, subscription)
    }
  } else if (treeKey) {
    update.call(keyTarget, REMOVE, event, subscription)
    delete tree[key]
  }
}

function struct (subs, key, target, subscription, update, tree, event, roottree) {
  var keyTarget = target[key]
  var treeKey = tree[key]
  if (keyTarget && keyTarget.__input !== null) {
    let stamp = generateStamp(keyTarget, treeKey, key)
    if (!tree[key]) {
      tree[key] = { $: stamp }
      tree[key].$ = stamp
      if (subs.val) {
        update.call(keyTarget, INIT, event, subs)
      }
      handleUpdate(target[key], subs, update, tree[key], event, roottree)
    } else if (tree[key].$ !== stamp) {
      tree[key].$ = stamp
      if (subs.val) {
        update.call(keyTarget, UPDATE, event, subs)
      }
      handleUpdate(target[key], subs, update, tree[key], event, roottree)
    }
  } else if (treeKey) {
    update.call(keyTarget, REMOVE, event, subs)
    delete tree[key]
  }
}

function collection (subs, target, subscription, update, tree, event, roottree) {
  if (target && target.__input !== null) {
    let keys = target.keys()
    if (keys) {
      for (let i = 0, len = keys.length; i < len; i++) {
        handleItem(keys[i], target, subs, subs, update, tree, event, roottree)
      }
    }
  } else if (tree) {
    update.call(target, REMOVE, event, subs)
  }
}

function root (subs, target, subscription, update, tree, event, roottree) {
  // lots of reuse with struct of course
  if (!tree.$r) {
    for (let key in subs) {
      if (subs[key] === true) {
        subs[key] = { val: true }
      }
    }
    let i = 0
    let path = target.syncPath
    let len = path.length
    let segment = roottree[path[i]]
    // let segmentTarget = target[]
    while (segment && i < len) {
      if (segment.$r) {
        merge(segment.$r, subs)
      } else {
        segment.$r = merge({}, subs)
      }
      // need tp update stamps for each segment
      segment = segment[path[++i]]
    }
  }
  // have to check if it needs to update!
}

function generateStamp (target, tree, key) {
  if (tree && tree.$r) {
    console.log('generateStamp', target, key)
  }
  return target._lstamp
}

/*
handleUpdate (target, subscription, update, tree, event, roottree) {
  for (let key in subscription) {
    handleItem(key, target, subscription, subscription[key], update, tree, event, roottree)
  }
}
*/

@@ -3304,19 +3304,16 @@
 %5Bi%5D%5D%0A   
- //
  let seg
@@ -3331,17 +3331,34 @@
 = target
-%5B
+.getRoot()%5Bpath%5Bi%5D
 %5D%0A    wh
@@ -3380,24 +3380,58 @@
  i %3C len) %7B%0A
+      let pathSegment = path%5B++i%5D%0A
       if (se
@@ -3546,16 +3546,8 @@
   //
- need tp
  upd
@@ -3565,54 +3565,190 @@
 for 
-each segment%0A      segment = segment%5Bpath%5B++i%5D
+all segments%0A      segment.$ = generateStamp(segmentTarget, segment)%0A%0A      // select new segment%0A      segment = segment%5BpathSegment%5D%0A      segmentTarget = segmentTarget%5BpathSegment
 %5D%0A  
@@ -3891,25 +3891,31 @@
 og('generate
-S
+ root-s
 tamp', targe

update stamps on creation