/* =============================================================
 * 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);