app = angular.module 'plunker', [
  "fa.directive.borderLayout"
]

app.controller 'MainCtrl', ($scope) ->
  $scope.name = 'World'
  $scope.panes = ["north", "south"]

<!DOCTYPE html>
<html ng-app="plunker">

  <head>
    <meta charset="utf-8" />
    <title>AngularJS Plunker</title>
    <link data-require="bootstrap-css@*" data-semver="3.0.2" rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css" />
    <script>document.write('<base href="' + document.location + '" />');</script>
    <link rel="stylesheet" href="borderLayout.css" />
    <link rel="stylesheet" href="style.css" />
    <script data-require="angular.js@1.2.x" src="http://code.angularjs.org/1.2.3/angular.js" data-semver="1.2.4"></script>
    <script src="borderLayout.js"></script>
    <script src="app.js"></script>
  </head>

  <body ng-controller="MainCtrl">
    <div fa-pane="">
      <div fa-pane pane-anchor="{{pane}}" pane-size="200px" pane-handle="4" ng-repeat="pane in panes">
          <pre ng-bind="$pane.getOptions() | json"></pre>
      </div>
      <div fa-pane>
        <div fa-pane pane-anchor="west" pane-size="50% + 2px" pane-min="200px" pane-max="50% + 2px" pane-handle="4">
          This is a 'west' pane nested inside the center of the outer pane.
          
          <pre ng-bind="$pane.getOptions() | json"></pre>
          
          <button ng-click="$pane.toggle()">Toggle</button>
        </div>
        <div fa-pane pane-anchor="south" pane-size="50& + 2px" pane-handle="4">
          This is a 'south' pane nested inside the center of the outer layout with lower priority than the west pane.
          
          <pre ng-bind="$pane.getOptions() | json"></pre>
        </div>
        <div fa-pane>
          <pre ng-bind="$pane.getOptions() | json"></pre>
        </div>
      </div>
    </div>
  </body>

</html>
.fa-pane-handle {
  background: #ccc;
}

.fa-pane:hover>.fa-pane-handle>.fa-pane-toggle {
  background: #666;
}

.fa-pane-resizing>.fa-pane-handle, .fa-pane-handle:hover {
  background-color: #C4E1A4;
}

.fa-pane-scroller {
  border: 1px solid #999;
}

.fa-pane-parent>.fa-pane-scroller {
  border: none;
}

.fa-pane-constrained.fa-pane-resizing>.fa-pane-overlay {
  background-color: rgba(255, 0, 0, 0.2);
}
.fa-pane {
  overflow: visible;
}

.fa-pane, .fa-pane-overlay, .fa-pane-handle, .fa-pane-scroller {
  display: block;
  position: absolute;
  box-sizing: border-box;
}

.fa-pane, .fa-pane-overlay, .fa-pane-scroller {
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}
.fa-pane-scroller {
  overflow: auto;
}

.fa-pane.fa-pane-closed>.fa-pane-scroller {
  display: none;
}

.fa-pane-overlay {
  display: none;
  z-index: 9999;
}

.fa-pane-resizing>.fa-pane-overlay {
  display: block;
}

.fa-pane-orientation-vertical>.fa-pane-handle {
  cursor: row-resize;
}

.fa-pane-orientation-horizontal>.fa-pane-handle {
  cursor: col-resize;
}

.fa-pane-orientation-horizontal>.fa-pane-handle>.fa-pane-toggle {
  margin: auto;
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  height: 100px;
  max-height: 20%;
  cursor: pointer;
}

.fa-pane-orientation-vertical>.fa-pane-handle>.fa-pane-toggle {
  margin: auto;
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  width: 100px;
  max-width: 20%;
  cursor: pointer;
}

.fa-pane-closed>.fa-pane-scroller {
  display: none;
}
module = angular.module "fa.directive.borderLayout", [
]

module.factory "paneManager", ->
  panes: {}
  get: (paneId) -> @panes[paneId]
  set: (paneId, pane) -> @panes[paneId] = pane
  remove: (paneId) -> delete @panes[paneId]
  

module.directive "faPane", [ "$window", "$rootScope", "paneManager", ($window, $rootScope, paneManager) ->
  
  class Region
    constructor: (@width = 0, @height = 0, @top = 0, @right = 0, @bottom = 0, @left = 0) ->

    clone: -> new Region(@width, @height, @top, @right, @bottom, @left)
      
    calculateSize: (orientation, target = 0) ->
      total = @getSize(orientation)
      available = @getAvailableSize(orientation)
      
      if angular.isNumber(target)
        if target >= 1 then return Math.round(target)
        if target >= 0 then return Math.round(target * total)
        
        return 0
      
      # Kill whitespace
      target = target.replace /\s+/mg, ""
      
      # Allow for complex sizes, e.g.: 50% - 4px
      if (terms = target.split("-")).length > 1 then return @calculateSize(orientation, terms.shift()) - @calculateSize(orientation, terms.join("+"))
      if (terms = target.split("+")).length > 1 then return @calculateSize(orientation, terms.shift()) + @calculateSize(orientation, terms.join("+"))
        
      if matches = target.match /^(\d+)px$/ then return parseInt(matches[1], 10)
      if matches = target.match /^(\d+(?:\.\d+)?)&$/ then return Math.round(available * parseFloat(matches[1]) / 100)
      if matches = target.match /^(\d+(?:\.\d+)?)%$/ then return Math.round(total * parseFloat(matches[1]) / 100)
      
      throw new Error("Unsupported size: #{target}")
    
    consume: (anchor, size = 0) ->
      switch anchor
        when "north"
          style = { top: "#{@top}px", right: "#{@right}px", bottom: "auto", left: "#{@left}px", height: "#{size}px", width: "auto" }
          @top += size
        when "east"
          style = { top: "#{@top}px", right: "#{@right}px", bottom: "#{@bottom}px", left: "auto", width: "#{size}px", height: "auto" }
          @right += size
        when "south"
          style = { top: "auto", right: "#{@right}px", bottom: "#{@bottom}px", left: "#{@left}px", height: "#{size}px", width: "auto" }
          @bottom += size
        when "west"
          style = { top: "#{@top}px", right: "auto", bottom: "#{@bottom}px", left: "#{@left}px", width: "#{size}px", height: "auto" }
          @left += size
      
      style.display = "none" if size is 0
        
      style
      
    getInnerRegion: ->
      new Region @width - @right - @left, @height - @top - @bottom
    
    getSize: (orientation) ->
      switch orientation
        when "vertical" then @height
        when "horizontal" then @width
    
    getAvailableSize: (orientation) ->
      switch orientation
        when "vertical" then @height - @top - @bottom
        when "horizontal" then @width - @right - @left
    
    toString: -> "{#{@top}, #{@right}, #{@bottom}, #{@left}}, {#{@width}, #{@height}}"  
  
  
  
  
  ## BEGIN UTILITY FUNCTIONS
  
  getOrientation = (anchor) ->
    switch anchor
      when "north", "south" then "vertical"
      when "east", "west" then "horizontal"  
      
  getScrollerStyle = (anchor, size) ->
    style =
      top: 0
      right: 0
      bottom: 0
      left: 0
    
    if size
      switch anchor
        when "north"
          style.bottom = "auto"
          style.height = "#{size}px"
        when "east"
          style.left = "auto"
          style.width = "#{size}px"
        when "south"
          style.top = "auto"
          style.height = "#{size}px"
        when "west"
          style.right = "auto"
          style.width = "#{size}px"
    
    style
      
  getHandleStyle = (anchor, region, handleSize) ->
    
    switch anchor
      when "north"
        height: "#{region.calculateSize('vertical', handleSize)}px"
        right: 0
        left: 0
        bottom: 0
      when "south" 
        height: "#{region.calculateSize('vertical', handleSize)}px"
        right: 0
        left: 0
        top: 0
      when "east"
        width: "#{region.calculateSize('horizontal', handleSize)}px"
        top: 0
        bottom: 0
        left: 0
      when "west" 
        width: "#{region.calculateSize('horizontal', handleSize)}px"
        top: 0
        bottom: 0
        right: 0
  
  generateSerialId = do ->
    counter = 0
    fun = -> counter++
    fun.peek = -> counter
    fun
  
      
  ## BEGIN DIRECTIVE DECLARATION
  
  restrict: "A"
  replace: true
  require: "faPane"
  priority: 1
  transclude: "element"
  scope:
    anchor: "@paneAnchor"
    paneId: "@faPane"
    size: "@paneSize"
    min: "@paneMin"
    max: "@paneMax"
    handle: "@paneHandle"
    closed: "=paneClosed"
    closable: "@paneNoToggle"
  template: """
    <div class="fa-pane pane-{{$pane.id}}">
      <div class="fa-pane-overlay"></div>
      <div class="fa-pane-handle" fa-pane-resizer>
        <div ng-if="!paneNotoggle" class="fa-pane-toggle" ng-click="$pane.toggle()"></div>
      </div>
    </div>
  """
  controllerAs: "$pane"
  controller: ->
    $pane = new class Pane
      constructor: ->
        @children = []
        
        @closed = false
        @max = Number.MAX_VALUE
        @min = 0

      
      # Schedule a reflow later in the digest cycle, but do not reflow more than
      # necessary
      $scheduleReflow: ->
        if $pane.parent then $pane.parent.$scheduleReflow()
        else unless $pane.$reflowScheduled
          $pane.$reflowScheduled = true
          
          $rootScope.$evalAsync ->
            if $pane.$reflowScheduled then $pane.reflow()
            
            $pane.$reflowScheduled = false
      
      getOptions: ->
        anchor: @anchor
        targetSize: @targetSize
        size: @size
        min: @min
        max: @max
        handle:
          open: @handleSizeOpen or 0
          closed: @handleSizeClosed or 0
        closed: @closed
      
      setOptions: (options = {}) ->
        @setAnchor(options.anchor) if options.anchor?
        @setTargetSize(options.size) if options.size?
        @setMinSize(options.min) if options.min?
        @setMaxSize(options.max) if options.max?
        @setHandleSize(options.handle) if options.handle?
        @toggle(!options.closed) if options.closed?
        
      setAnchor: (@anchor) -> @$scheduleReflow()
      setTargetSize: (@targetSize) -> @$scheduleReflow()
      setMinSize: (@min) -> @$scheduleReflow()
      setMaxSize: (@max) -> @$scheduleReflow()
      setHandleSize: (handleSize) ->
        if handleSize?.open or handleSize?.closed
          @handleSizeOpen = handleSize.open or 0
          @handleSizeClosed = handleSize.closed or 0
        else
          @handleSizeOpen = @handleSizeClosed = parseInt(handleSize, 10)
        @$scheduleReflow()

      
      addChild: (child) ->
        child.parent = $pane
        @children.push child
        
        $pane.$containerEl.addClass("fa-pane-parent") if @children.length
        
        $pane.$scheduleReflow()
      
      getOrientation: -> getOrientation($pane.anchor)
      onHandleDown: -> $pane.$containerEl.addClass("active")
      onHandleUp: ->
        $pane.$containerEl.removeClass("active")
        $pane.$scheduleReflow()
        
      removeChild: (child) ->
        @children.splice idx, 1 unless 0 > idx = @children.indexOf(child)
        
        $pane.$containerEl.removeClass("fa-pane-parent") unless @children.length
        
        $pane.$scheduleReflow()
      
      reflow: (region) ->
        width = $pane.$containerEl[0].offsetWidth
        height = $pane.$containerEl[0].offsetHeight
      
        region ||= new Region(width, height)
        
        if $pane.anchor in ["north", "east", "south", "west"]
          $pane.$containerEl.removeClass("fa-pane-orientation-vertical")
          $pane.$containerEl.removeClass("fa-pane-orientation-horizontal")
          
          orientation = getOrientation($pane.anchor)
  
          $pane.$containerEl.addClass("fa-pane-orientation-#{orientation}")

          handleSize = region.calculateSize(orientation, !$pane.closed and $pane.handleSizeOpen or $pane.handleSizeClosed)
          
          if $pane.closed
            size = handleSize
          else
            size = region.calculateSize(orientation, !$pane.closed and $pane.targetSize or handleSize)
            
            size = Math.min(size, region.calculateSize(orientation, $pane.max))
            size = Math.max(size, region.calculateSize(orientation, $pane.min))
            size = Math.min(size, region.getAvailableSize(orientation))
            size = Math.max(size, handleSize)
          
          @size = size
          
          styleContainer = region.consume($pane.anchor, size)
          styleScroller = getScrollerStyle($pane.anchor, size - handleSize)
          styleHandle = getHandleStyle($pane.anchor, region, handleSize)
         
          $pane.$containerEl.attr("style", "").css(styleContainer)
          $pane.$overlayEl.attr("style", "").css(styleScroller)
          $pane.$handleEl.attr("style", "").css(styleHandle)
          $pane.$scrollerEl.attr("style", "").css(styleScroller)

        else
          $pane.$containerEl.css
            top: "#{region.top}px"
            right: "#{region.right}px"
            bottom: "#{region.bottom}px"
            left: "#{region.left}px"
            width: "auto"
            height: "auto"
            
          
        $pane.$region = region.clone()
        $pane.reflowChildren region.getInnerRegion()
      
      reflowChildren: (region) ->
        region ||= $pane.$region
        
        $pane.children.sort (a, b) -> a.order - b.order
        child.reflow(region) for child in $pane.children

      # Attempt to resize the 
      resize: (size = $pane.targetSize) ->
        $pane.targetSize = size
        $pane.parent.reflowChildren($pane.parent.$region.getInnerRegion())
        
        if size != $pane.size then $pane.$containerEl.addClass("fa-pane-constrained")
        else $pane.$containerEl.removeClass("fa-pane-constrained")
      
      toggle: (open = !!$pane.closed) ->
        $pane.closed = !open
        
        reflow = ->
          if $pane.parent then $pane.parent.$scheduleReflow() 
          else $pane.$scheduleReflow()
        
        if $pane.closed then $pane.$containerEl.addClass "fa-pane-closed"
        else $pane.$containerEl.removeClass "fa-pane-closed"
        
        reflow()

  compile: ($el, $attrs, $transclude) ->
    # Tool used to force elements into their compile order
    serialId = generateSerialId()

    link = ($scope, $el, $attrs, $pane) ->
      $directiveScope = $scope.$parent.$new()
      $directiveScope.$pane = $scope.$pane = $pane
      
      $transcludeScope = $directiveScope.$new()
      
      $pane.order ?= serialId
      
      $pane.$isolateScope = $scope
      $pane.$directiveScope = $directiveScope
      $pane.$transcludeScope = $transcludeScope
      
      $transclude $transcludeScope, (clone) ->
        clone.addClass("fa-pane-scroller")
        
        $el.append(clone)
        
        $pane.$containerEl = $el
        $pane.$overlayEl = $el.children().eq(0)
        $pane.$handleEl = $el.children().eq(1)
        $pane.$scrollerEl = $el.children().eq(2)

        $scope.$watch "anchor", (anchor) -> $pane.setAnchor(anchor)
        $scope.$watch "size", (targetSize) -> $pane.setTargetSize(targetSize)
        $scope.$watch "closed", (closed) -> $pane.toggle(!closed)
        $scope.$watch "min", (min) -> $pane.setMinSize(if min? then min else 0)
        $scope.$watch "max", (max) -> $pane.setMaxSize(if max? then max else Number.MAX_VALUE)
        $scope.$watch "paneId", (paneId, prevPaneId) ->
          paneManager.remove(prevPaneId) if prevPaneId
          paneManager.set(paneId, $pane)

          $pane.id = paneId
        
        $scope.$watch "handle", (handle) -> $pane.setHandleSize(handle)
        
        $pane.$directiveScope.$on "fa-pane-attach", (e, child) ->
          if child != $pane
            e.stopPropagation()
            
            $pane.addChild(child)
        
        $pane.$directiveScope.$on "fa-pane-detach", (e, child) ->
          if child != $pane
            e.stopPropagation()
            
            $pane.removeChild(child)
        
        $window.addEventListener "resize", (e) ->
          e.stopPropagation()
          $pane.$scheduleReflow()
          
        $pane.$directiveScope.$on "$stateChangeSuccess", ->
          $pane.$scheduleReflow()
          

        $pane.$directiveScope.$emit "fa-pane-attach", $pane
        $pane.$directiveScope.$on "$destroy", ->
          $pane.$directiveScope.$emit "fa-pane-detach", $pane
]

module.directive "faPaneToggle", [ "paneManager", (paneManager) ->
  link: ($scope, $el, $attrs) ->
    $attrs.$observe "faPaneToggle", (paneId) ->
]

module.directive "faPaneResizer", [ "$window", ($window) ->
  throttle = (delay, fn) ->
    throttled = false
    ->
      return if throttled
      
      throttled = true
      setTimeout ->
        throttled = false
      , delay
      
      fn.call(@, arguments...)


  restrict: "A"
  #require: "^faPane"
  link: ($scope, $element, $attrs, $pane) ->
    #return unless $pane
    
    $pane ||= $scope.$pane
    
    el = $element[0]
    
    clickRadius = 5
    clickTime = 300
    
    $scope.$watch ( -> $pane.getOrientation() ), (orientation) ->
      $element.removeClass("vertical")
      $element.removeClass("horizontal")
      
      switch orientation
        when "vertical" then $element.addClass("vertical")
        when "horizontal" then $element.addClass("horizontal")
    
    el.addEventListener "mousedown", (e) ->
      return unless e.button is 0
      
      anchor = $pane.anchor
      
      if anchor in ["north", "south"] then coord = "screenY"
      else if anchor in ["west", "east"] then coord = "screenX"

      if anchor in ["north", "west"] then scale = 1
      else if anchor in ["south", "east"] then scale = -1
    
      startPos = {x: e.screenX, y: e.screenY}
      startCoord = e[coord]
      startSize = $pane.size
      startTime = Date.now()
      
      ##pane.onHandleDown()
      
      # Not sure if this really adds value, but added for compatibility
      el.unselectable = "on"
      el.onselectstart = -> false
      el.style.userSelect = el.style.MozUserSelect = "none"
      
      # Null out the event to re-use e and prevent memory leaks
      #e.setCapture()
      e.preventDefault()
      e.defaultPrevented = true
      e = null

      handleMouseMove = (e) ->
        $pane.$containerEl.addClass("fa-pane-resizing")
      
        # Inside Angular's digest, determine the ideal size of the element
        # according to movements then determine if those movements have been
        # constrained by boundaries, other panes or min/max clauses
        $scope.$apply ->
          targetSize = startSize + scale * (e[coord] - startCoord)
          
          $pane.resize targetSize

        # Null out the event in case of memory leaks
        #e.setCapture()
        e.preventDefault()
        e.defaultPrevented = true
        e = null
        
      handleMouseUp = (e) ->
        displacementSq = Math.pow(e.screenX - startPos.x, 2) + Math.pow(e.screenY - startPos.y, 2)
        timeElapsed = Date.now() - startTime

        $window.removeEventListener "mousemove", handleMouseMoveThrottled, true
        $window.removeEventListener "mouseup", handleMouseUp, true
        
        cleanup = ->
          $pane.$containerEl.removeClass("fa-pane-resizing")
        
          # Null out the event in case of memory leaks
          #e.releaseCapture()
          e.preventDefault()
          e.defaultPrevented = true
          e = null
        
        if displacementSq <= Math.pow(clickRadius, 2) and timeElapsed <= clickTime
          cleanup()
          return
          
        # In case the mouse is released at the end of a throttle period
        handleMouseMove(e)
        
        cleanup()

      
      # Prevent the reflow logic from happening too often
      handleMouseMoveThrottled = throttle(10, handleMouseMove)
    
      $window.addEventListener "mouseup", handleMouseUp, true
      $window.addEventListener "mousemove", handleMouseMoveThrottled, true
]