SVG.Box = SVG.invent({
  create: function(source) {
    var base = [0,0,0,0]
    source = typeof source === 'string' ?
        source.split(SVG.regex.delimiter).map(parseFloat) :
      Array.isArray(source) ?
        source :
      typeof source == 'object' ?
        [source.left != null ? source.left : source.x, source.top != null ? source.top : source.y, source.width, source.height] :
      arguments.length == 4 ?
        [].slice.call(arguments) :
        base

    this.x = source[0]
    this.y = source[1]
    this.width = source[2]
    this.height = source[3]

    // add center, right, bottom...
    fullBox(this)
  }
, extend: {
    // Merge rect box with another, return a new instance
    merge: function(box) {
      var x = Math.min(this.x, box.x)
        , y = Math.min(this.y, box.y)

      return new SVG.Box(
        x, y,
        Math.max(this.x + this.width,  box.x + box.width)  - x,
        Math.max(this.y + this.height, box.y + box.height) - y
      )
    }

  , transform: function(m) {
      var xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity, p, bbox

      var pts = [
        new SVG.Point(this.x, this.y),
        new SVG.Point(this.x2, this.y),
        new SVG.Point(this.x, this.y2),
        new SVG.Point(this.x2, this.y2)
      ]

      pts.forEach(function(p) {
        p = p.transform(m)
        xMin = Math.min(xMin,p.x)
        xMax = Math.max(xMax,p.x)
        yMin = Math.min(yMin,p.y)
        yMax = Math.max(yMax,p.y)
      })

      return new SVG.Box(
        xMin, yMin,
        xMax-xMin,
        yMax-yMin
      )
    }

  , addOffset: function() {
      // offset by window scroll position, because getBoundingClientRect changes when window is scrolled
      this.x += window.pageXOffset
      this.y += window.pageYOffset
      return this
    }
  , toString: function() {
      return this.x + ' ' + this.y + ' ' + this.width + ' ' + this.height
    }
  , morph: function(x, y, width, height){
      this.destination = new SVG.Box(x, y, width, height)
      return this
    }

  , at: function(pos) {
      if(!this.destination) return this

      return new SVG.Box(
          this.x + (this.destination.x - this.x) * pos
        , this.y + (this.destination.y - this.y) * pos
        , this.width + (this.destination.width - this.width) * pos
        , this.height + (this.destination.height - this.height) * pos
      )

    }
  }

    // Define Parent
, parent: SVG.Element

  // Constructor
, construct: {
    // Get bounding box
    bbox: function() {
      var box

      try {
        // find native bbox
        box = this.node.getBBox()

        if(isNulledBox(box) && !domContains(this.node)) {
          throw new Exception('Element not in the dom')
        }
      } catch(e) {
        try {
          var clone = this.clone(SVG.parser.draw.instance).show()
          box = clone.node.getBBox()
          clone.remove()
        } catch(e) {
          console.warn('Getting a bounding box of this element is not possible')
        }
      }

      return new SVG.Box(box)
    }

  , rbox: function(el) {
      // IE11 throws an error when element not in dom
      try{
        var box = new SVG.Box(this.node.getBoundingClientRect())
        if (el) return box.transform(el.screenCTM().inverse())
        return box.addOffset()
      } catch(e) {
        return new SVG.Box()
      }
    }
  }
})

SVG.extend(SVG.Doc, SVG.Nested, SVG.Symbol, SVG.Image, SVG.Pattern, SVG.Marker, SVG.ForeignObject, SVG.View, {
  viewbox: function(x, y, width, height) {
    // act as getter
    if(x == null) return new SVG.Box(this.attr('viewBox'))

    // act as setter
    return this.attr('viewBox', new SVG.Box(x, y, width, height))
  }
})