'use strict'

var processNodes = require('./process_nodes')

module.exports = defineProperties

/**
 * Define getters & setters. This function does most of the heavy lifting.
 *
 * @param {Object} obj
 * @param {Object} def
 */
function defineProperties (obj, def) {
  // Using the closure here to store private object.
  var store = {}

  var properties = Object.keys(obj)
  var i, j, property

  for (i = 0, j = properties.length; i < j; i++) {
    property = properties[i]
    store[property] = obj[property]
    define(property)
  }

  function define (key) {
    var branch = def[key]
    var mount = branch.mount
    var unmount = branch.unmount
    var definition = branch.definition

    // Keeping state in this closure.
    var activeNodes = []
    var previousValues = []

    Object.defineProperty(obj, key, {
      get: getter, set: setter, enumerable: true
    })

    // For initialization, call this once.
    setter(store[key])

    function getter () {
      return store[key]
    }

    function setter (x) {
      var values = Array.isArray(x) ? x : [ x ]
      var i, j, isEmpty, values, value, previousValue,
        node, activeNode, parentNode

      store[key] = x

      if (Array.isArray(x)) {
        // Assign custom mutator methods on the array instance.
        if (!x.__hasMutators) {
          x.__hasMutators = true
          x.reverse = reverse
          x.sort = sort
          x.copyWithin = copyWithin
          x.fill = fill
        }

        values = x
      }
      else values = [ x ]

      // Handle rendering to the DOM.
      j = Math.max(previousValues.length, values.length)

      for (i = 0; i < j; i++) {
        value = values[i]
        activeNode = activeNodes[i]
        previousValue = previousValues[i]
        isEmpty = value === null || value === void 0
        parentNode = branch.marker.parentNode

        if (isEmpty) {
          delete previousValues[i]
          delete activeNodes[i]
          if (unmount) unmount(activeNode, value, previousValue, i)
          if (activeNode) parentNode.removeChild(activeNode)
          continue
        }

        if (previousValue === value) continue

        previousValues[i] = value

        if (unmount) unmount(activeNode, value, previousValue, i)
        if (activeNode) parentNode.removeChild(activeNode)

        if (mount) {
          node = branch.node.cloneNode(true)
          node = mount(node, value, previousValue, i) || node
        }

        else if (definition) {
          node = processNodes(branch.node.cloneNode(true), definition)
          defineProperties(value, definition)
        }

        activeNodes[i] = parentNode.insertBefore(node,
          activeNodes[i + 1] || branch.marker)
      }

      // Reset length to current values.
      previousValues.length = activeNodes.length = values.length
    }

    function reverse () {
      Array.prototype.reverse.call(this)
      setter(this)
    }

    function sort (fn) {
      Array.prototype.sort.call(this, fn)
      setter(this)
    }

    function fill (a, b, c) {
      Array.prototype.fill.call(this, a, b, c)
      setter(this)
    }

    function copyWithin (a, b, c) {
      Array.prototype.copyWithin.call(this, a, b, c)
      setter(this)
    }
  }
}

@@ -2825,16 +2825,32 @@
 s.length
+%0A%0A      return x
 %0A    %7D%0A%0A
@@ -2873,32 +2873,46 @@
 erse () %7B%0A      
+return setter(
 Array.prototype.
@@ -2929,34 +2929,16 @@
 ll(this)
-%0A      setter(this
 )%0A    %7D%0A
@@ -2961,32 +2961,46 @@
 rt (fn) %7B%0A      
+return setter(
 Array.prototype.
@@ -3018,34 +3018,16 @@
 his, fn)
-%0A      setter(this
 )%0A    %7D%0A
@@ -3055,32 +3055,46 @@
 , b, c) %7B%0A      
+return setter(
 Array.prototype.
@@ -3117,34 +3117,16 @@
 a, b, c)
-%0A      setter(this
 )%0A    %7D%0A
@@ -3168,16 +3168,30 @@
 %7B%0A      
+return setter(
 Array.pr
@@ -3232,26 +3232,8 @@
 , c)
-%0A      setter(this
 )%0A  

simplify array mutators