/* =============================================================
 * bootstrap-tree.js v0.3
 * http://twitter.github.com/cutterbl/Bootstrap-Tree
 * 
 * Inspired by Twitter Bootstrap, with credit to bits of code
 * from all over.
 * =============================================================
 * Copyright 2012 Cutters Crossing.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ============================================================ */

!function ($) {

  "use strict"; // jshint ;_;

  var loading = "<img src='assets/plugins/bootstrap-tree/bootstrap-tree/img/ajax-loader.gif' class='indicator' /> Loading ...";

  /* TREE CLASS DEFINITION
   * ========================= */

  var Tree = function (element, options) {
    
    this.$element = $(element)
    this.$tree = this.$element.closest(".tree")
    this.parentage = GetParentage(this.$element)
    this.options = $.extend({}, $.fn.tree.defaults, options)

    if (this.options.parent) {
      this.$parent = $(this.options.parent)
    }

    this.options.toggle && this.toggle()
  }

  Tree.prototype = {

    constructor: Tree

    , toggle: function () {
      
      var a, n, s
        , currentStatus = this.$element.hasClass("in")
        , eventName = (!currentStatus) ? "openbranch" : "closebranch"
          
      this.$parent[currentStatus ? "addClass" : "removeClass"]("closed")
      this.$element[currentStatus ? "removeClass" : "addClass"]("in")
      
      if (this.options.href) {
        this._load()
      }
      
      n = this.node()
      // 'Action' (open|close) event
      a = $.Event(eventName, {
        node: n
      })
      // 'Select' event
      s = $.Event("nodeselect", {
        node: n
      })
      
      this.$parent.trigger(a).trigger(s)
      
    }

    , _load: function () {
        var data = $.extend({}, this.options)
          , el = this.$element
          , $this = $(this)
          , options = this.options

        // some config data we don't need to pass in the post
        delete data.parent
        delete data.href
        delete data.callback

        $.post(options.href, data, function (d, s, x){
          
          var doc, type = "html"
            
          if (options.callback) { // If a callback was defined in the data parameters
            
            var cb = window[options.callback].apply(el, [d, s, x]) // callbacks must return an object with 'doc' and 'type' keys
            doc = cb.doc || d
            type = cb.type || type
            
          } else {
            
            try {
              doc = $.parseJSON(d)
              type = "json"
            } catch (err) {
              doc = d
            }
            
            if (type !== "json") {
              try {
                doc = $.parseXML(d)
                type = "xml"
              } catch (err) {
                doc = d
              }
            }
            
          }
          
          switch (type) {
            
            case "html":
              el.html(doc)
              break
              
            default:
              $this[0]._buildOutput(doc, type, el)
              break
              
          }
          
        }, "html")
    }
    
    , _buildOutput: function (doc, type, parent) {
      
      var nodes = this._buildNodes(doc, type)
      
      parent.empty().append(this._createNodes(nodes))
      
    }
    
    , _createNodes: function (nodes) {
      
      var els = []
        , $this = $(this)
      
      $.each(nodes, function (ind, el) {
        
        var node = $("<li>")
          , role = (el.leaf) ? "leaf" : "branch"
          , attributes = {}
          , anchor = $("<a>")
        
        attributes.role = role
        
        if (!el.leaf) {
          
          var branch = $("<ul>").addClass("branch")
          
          attributes['class'] = "tree-toggle closed" //fixed by keenthemes for ie8
          attributes["data-toggle"] = "branch"
            
        }
        
        if (el.value) attributes["data-value"] = el.value
          
        if (el.id) attributes["data-itemid"] = el.id
        
        for (var key in el) { // do we have some extras?
          
          if (key.indexOf("data-") !== -1) attributes[key] = el[key]
          
        }
        
        attributes.href = (el.href) ? el.href : "#"
          
        // trade the anchor for a span tag, if it's a leaf
        // and there's no href
        if (el.leaf && attributes.href === "#") {
          
          anchor = $("<span>")
          delete attributes.href
          
        }
        
        anchor.attr(attributes)
        
        if (el.cls) anchor.addClass(el.cls)
        if (!el.leaf && el.expanded && el.children.length) {
          
          anchor.removeClass("closed")
          branch.addClass("in")
          
        }
        
        anchor.html(el.text)
        node.append(anchor)
        
        if (!el.leaf && el.children && el.children.length) {
          
          branch.append($this[0]._createNodes(el.children))
          node.append(branch)
          
        }
        
        els.push(node)
        
      })
      
      return els
    }
    
    , _buildNodes: function (doc, type) {
      
      var nodes = []
        , $el = this.$element
      
      if (type === "json") {
        
        nodes = this._parseJsonNodes(doc)
        
      } else if (type === "xml") {
        
        nodes = this._parseXmlNodes($(doc).find("nodes").children())
        
      }
      
      return nodes
    }
    
    , _parseJsonNodes: function (doc) {
      
      var nodes = []
        , $this = $(this)
      
      $.each(doc, function (ind, el) {
        
        var opts = {}
          , boolChkArr = ["leaf","expanded","checkable","checked"]
        
        for (var item in el) {
          
          var nodeVal = (item !== "children") ? el[item] : $this[0]._parseJsonNodes(el.children)
              
          if (!$.isArray(nodeVal)) nodeVal = $.trim(nodeVal)
          if (nodeVal.length) opts[item] = ($.inArray(item, boolChkArr) > -1) ? SetBoolean(nodeVal) : nodeVal
              
        }
        
        nodes.push(new Node(opts))
      })
      
      return nodes
      
    }
    
    , _parseXmlNodes: function (doc) {
      
      var nodes = []
        , $this = $(this)
        , boolChkArr = ["leaf","expanded","checkable","checked"]
      
      $.each(doc, function (ind, el) {
        
        var opts = {}
          , $el = $(el)
        
        $.each($el.children(), function (x, i) {
          
          var $i = $(i)
            , tagName = $i[0].nodeName
            , nodeVal = (tagName !== "children") ? $i.text() : $this[0]._parseXmlNodes($i.children("node"))
                
          if (!$.isArray(nodeVal)) nodeVal = $.trim(nodeVal)
          if (nodeVal.length) opts[tagName] = ($.inArray(tagName, boolChkArr) > -1) ? SetBoolean(nodeVal) : nodeVal
              
        })
        
        nodes.push(new Node(opts))
      })
      
      return nodes
      
    }

    , getparentage: function () {
      
      return this.parentage
      
    }

    , node: function (el) {
      el = el || $(this)
      
      var node = $.extend(true, {}, (el[0] === $(this)[0]) ? $(this.$parent).data() : el.data())
      
      node.branch = this.$element
      node.parentage = this.parentage
      node.el = (el[0] === $(this)[0]) ? this.$parent : el
      
      delete node.parent
      
      return node
      
    }

  }
  
  var Node = function (options) {
    
    $.extend(true, this, {
      text: undefined,
      leaf: false,
      value: undefined,
      expanded: false,
      cls: undefined,
      id: undefined,
      href: undefined,
      checkable: false,
      checked: false,
      children: []
    }, options)
    
  }

  var GetParentBranch = function ($this) {
    
    return $this.closest("ul.branch").prev(".tree-toggle")
    
  }

  var GetParentage = function ($this) {
    
    var arr = [], tmp
    
    tmp = GetParentBranch($this)
    if (tmp.length) {
      arr = GetParentage(tmp)
      arr.push(tmp.attr("data-value")||tmp.text())
    }
    
    return arr
    
  }
  
  /**
   * FUNCTION SetBoolean
   * 
   * Takes any value, and returns it's boolean equivalent.
   * 
   * @param value (any)
   * @return (boolean)
   */
  var SetBoolean = function (value) {
    
    value = $.trim(value)
    
    if (typeof value === "undefined" || value === null) return false
    
    if (typeof value === "string" && !isNaN(value)) value = parseFloat(value)
    
    if (typeof value === "string") {
      switch (value.toLowerCase()) {
        case "true":
        case "yes":
          return true
        case "false":
        case "no":
          return false
      }
    }
    
    return Boolean(value)
  }


  /* COLLAPSIBLE PLUGIN DEFINITION
   * ============================== */

  $.fn.tree = function (option) {
    
    return this.each(function () {
      var $this = $(this)
        , data = $this.data("tree")
        , options = typeof option == "object" && option
        
      if (!data) $this.data("tree", (data = new Tree(this, options)))
      if (typeof option == "string") data[option]()
    })
    
  }

  $.fn.tree.defaults = {
      
    toggle: true
    
  }

  $.fn.tree.Constructor = Tree

  /* COLLAPSIBLE DATA-API
   * ==================== */

  $(function () {
    
    $("body").on("click.tree.data-api", "[data-toggle=branch]", function (e) {
      
      e.preventDefault()
      
      var $this = $(this)
        , target = $this.next(".branch")
        , href = $this.attr("href")
        , option = $(target).data("tree") ? "toggle" : $this.data()
        
      href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7
      
      if (!target.length) {
        target = $('<ul>').addClass('branch').append("<li>" + loading + "</li>").insertAfter($this)
      }
      
      option.parent = $this
      option.href = (href !== "#") ? href : undefined
          
      $(target).tree(option)
      
      return false
    })
    
    $("body").on("click.tree.data-api", "[data-role=leaf]", function (e) {
      
      var $this = $(this)
        , branch = $this.closest(".branch")
        
      // If not initialized, then create it
      if (!$(branch).data("tree")) {
        
        var $target = $(branch)
          , branchlink = $target.prev("[data-toggle=branch]")
          , branchdata = branchlink.data()
          , href = branchlink.attr("href")
        
        href.replace(/.*(?=#[^\s]+$)/, '')
        
        $target.tree($.extend({}, branchdata, {
          "toggle": false,
          "parent": branchlink,
          "href": (href !== "#") ? href : undefined
        }))
      }
      
      e = $.Event("nodeselect", {
        node: $(branch).data("tree").node($this)
      })
      
      $this.trigger(e)
      
    })
    
  })

}(window.jQuery);