var Node = require('./node')
var Matrix = require('fd/matrix')
var Rect = require('fd/rect')
var extend = require('fd/extend')
var inherits = require('fd/inherits')

function DisplayNode(props, children) {
  this.transform = Matrix.identity
  this.opacity = 1
  this._hidden = false
  this.blendMode = 'normal'

  Node.call(this)

  if (Array.isArray(props)) {
    children = props
    props = null
  }
  if (props) extend(this, props)
  if (children) this.appendAll(children)
}
inherits(DisplayNode, Node)

Object.defineProperty(DisplayNode.prototype, 'localToGlobal', {get: function() {
  var transform = Matrix.identity
  var node = this
  do {
    transform = node.transform.concat(transform)
  } while (node = node.parent)
  return transform
}})
Object.defineProperty(DisplayNode.prototype, 'globalToLocal', {get: function() {
  return this.localToGlobal.inverse
}})

Object.defineProperty(DisplayNode.prototype, 'boundingBox', {
  get: function() {
    if (this.cachedBoundingBox) return this.cachedBoundingBox

    var bb = this.ownBoundingBox()
    for (var i = this.children.length; i--;) {
      var c = this.children[i]
      bb = bb.union(c.boundingBox.transform(c.transform))
    }
    return this.cachedBoundingBox = bb
  },
  set: function(bb) {
    if (this.cachedBoundingBox && bb.equals(this.cachedBoundingBox)) return
    this.updateBoundingBoxFrom(bb)
    this.boundingBoxChanged()
  }
})
DisplayNode.prototype.ownBoundingBox = function() {
  return Rect.zero
}
DisplayNode.prototype.boundingBoxChanged = function() {
  if (!this.cachedBoundingBox) return
  this.cachedBoundingBox = null
  if (this.parent) this.parent.boundingBoxChanged()
  this.didChange()
}
DisplayNode.prototype.updateBoundingBoxFrom = function(bb) {
  var old = this.boundingBox
  for (var i = this.children.length; i--;) {
    var c = this.children[i]
    c.boundingBox = c.boundingBox.rescale(old, bb)
  }
}

DisplayNode.prototype.structureChanged = function() {
  this.boundingBoxChanged()
  this.root.emitThrottled('invalidate')
}
DisplayNode.prototype.didChange = function() {
  this.emitThrottled('change')
  this.root.emitThrottled('invalidate')
}
DisplayNode.invalidatingProperty = function(object, name) {
  var data = '_' + name
  Object.defineProperty(object, name, {
    get: function() {return this[data]},
    set: function(value) {
      this[data] = value
      this.didChange()
    }
  })
}
DisplayNode.invalidatingProperty(DisplayNode.prototype, 'hidden')

DisplayNode.prototype.pathOn = function(cx) {
  this.boundingBox.pathOn(cx)
}
DisplayNode.prototype.isSolidAt = function(p) {
  return this.boundingBox.includes(p)
}
DisplayNode.prototype.drawOn = function(cx) {}
DisplayNode.prototype.drawTreeOn = function(cx) {
  if (this.hidden) return
  cx.save()
  this.transform.transformContext(cx)
  this.drawOn(cx)
  for (var i = 0, l = this.children.length; i < l; i++) {
    this.children[i].drawTreeOn(cx)
  }
  cx.restore()
}

DisplayNode.prototype.nodeAt = function(p) {
  if (this.hidden) return null
  for (var i = this.children.length; i--;) {
    var c = this.children[i]
    if (c.transform.determinant === 0) return false
    var q = p.transform(c.transform.inverse)
    var n = c.nodeAt(q)
    if (n) return n
  }
  return this.isSolidAt(p) ? this : null
}
DisplayNode.prototype.childAt = function(p) {
  for (var i = this.children.length; i--;) {
    var c = this.children[i]
    if (c.transform.determinant === 0) return false
    var q = p.transform(c.transform.inverse)
    if (c.isSolidAt(q)) return c
  }
  return null
}
DisplayNode.prototype.siblingAt = function(p) {
  if (!this.parent) return false

  var q = p.transform(this.transform)
  var children = this.parent.children
  for (var i = children.length; i--;) {
    var c = children[i]
    if (c.hidden) continue
    if (c === this && this.isSolidAt(p)) return null
    if (c.transform.determinant === 0) return false
    var r = q.transform(c.transform.inverse)
    if (c.isSolidAt(r)) return c
  }

  return null
}

DisplayNode.prototype.ancestorSiblingAt = function(p) {
  return this.siblingAt(p) || this.parent && this.parent.ancestorSiblingAt(p.transform(this.transform))
}

module.exports = DisplayNode

@@ -198,32 +198,33 @@
 ldren) %7B%0A  this.
+_
 transform = Matr
@@ -242,16 +242,17 @@
 %0A  this.
+_
 opacity 
@@ -285,16 +285,17 @@
 %0A  this.
+_
 blendMod
@@ -2453,22 +2453,227 @@
 otype, '
-hidden
+transform')%0ADisplayNode.invalidatingProperty(DisplayNode.prototype, 'opacity')%0ADisplayNode.invalidatingProperty(DisplayNode.prototype, 'hidden')%0ADisplayNode.invalidatingProperty(DisplayNode.prototype, 'blendMode
 ')%0A%0ADisp

Make DisplayNode properties invalidating