// [BLite.UI]
// - Javascript user interface implementatation on top of BLite library.

// ===========================================================================
// [Dependencies]
// ===========================================================================

if (!BLite) throw new Error("Dependency Check Failed");

// ===========================================================================
// [BLite.UI.Tree
// ===========================================================================

//! Tree widget.
BLite.Object.define("BLite.UI.Tree",
{
  extend: BLite.Object,

  construct: function(element, cookie)
  {
    this.base(arguments);

    // properties
    this._css = "tree";
    this._root = null;
    this._cookie = cookie || null;

    // variables
    this._element = null;
    this._idtouid = {};
    this._cookieSyncing = false;

    // dom
    if (element)
      this._adoptElement(element);
    else
      this._createElement();
    BLite.Dom.addListener(this._element, "click", this._onclick, this);
  },

  destruct: function()
  {
    delete this._root;
    delete this._element;
    delete this._idtouid;
  },

  properties:
  {
    css:       { apply: "_applyCss"       },
    root:      { apply: "_applyRoot"      },
    cookie:    { apply: "_applyCookie"    }
  },

  members:
  {
    // ------------------------------------------------------------------------
    // [Create]
    // ------------------------------------------------------------------------

    _createElement: function()
    {
      this._element = BLite.Dom.DIV();
      this._element.$$uid = this._uid;
    },

    _adoptElement: function(element)
    {
      this._element = element;
      this._element.$$uid = this._uid;

      // adopt root item
      var first;
      if ((first = BLite.Dom.first(this._element, "div")))
      {
        this.setRoot(new BLite.UI.TreeItem(first));
      }

      if (this._cookie) this._fromCookieString(BLite.Cookie.get(this._cookie));
    },

    // ------------------------------------------------------------------------
    // [Apply]
    // ------------------------------------------------------------------------

    _applyCss: function(value)
    {

    },

    _applyRoot: function(newRoot, oldRoot)
    {
      if (oldRoot)
      {
        oldRoot.setTree(null);
        oldRoot.setIsRoot(false);
        this._element.removeChild(oldRoot._element);
      }

      if (newRoot)
      {
        this._element.appendChild(newRoot._element);
        newRoot.setTree(this);
        newRoot.setIsRoot(true);
      }
    },

    _applyCookie: function()
    {
      this._updateCookie();
    },

    // ------------------------------------------------------------------------
    // [Item Manipulation]
    // ------------------------------------------------------------------------

    getItem: function(id)
    {
      var uid = this._idtouid[id];
      return BLite.Object.getByUid(uid);
    },

    // ------------------------------------------------------------------------
    // [Cookies]
    // ------------------------------------------------------------------------

    _fromCookieString: function(str)
    {
      // create dictionary of open IDs for fast lookup
      var a = (str || "").split(","), map = {};
      for (var i = 0; i < a.length; i++) map[a[i]] = true;

      this._fromCookieMap(map);
    },

    _fromCookieMap: function(map)
    {
      this._cookieSyncing = true;

      var root = this.getRoot();
      if (root)
      {
        root.traverse(function(item)
        {
          if (item._id && item != root) item.setIsOpen(map[item._id] || false);
        }, this, false, true);
      }

      this._cookieSyncing = false;
    },

    _toCookieString: function()
    {
      var root = this.getRoot();
      var a = [];

      if (root)
      {
        root.traverse(function(item)
        {
          if (item._isOpen && item._id) a.push(item._id);
        }, this, false, true);
      }
      return a.join(",");
    },

    _updateCookie: function()
    {
      // Avoid infinite recursion
      if (this._cookieSyncing) return;

      if (this._cookie) BLite.Cookie.set(this._cookie, this._toCookieString(), 365);
    },

    // ------------------------------------------------------------------------
    // [Event Handlers]
    // ------------------------------------------------------------------------

    _onclick: function(e)
    {
      var et = e.eventTarget, item;

      while (et !== this._element)
      {
        if (typeof et.$$uid !== "undefined")
        {
          if ((item = BLite.Object.getByUid(et.$$uid)) && item instanceof BLite.UI.TreeItem)
          {
            this._onitemclick(item, e);
            break;
          }
        }
        et = et.parentNode;
      }
    },

    _onitemclick: function(item, e)
    {
      if (e.eventTarget === item._contentButton)
      {
        item.setIsOpen(!item._isOpen);
      }
    }
  },

  statics:
  {
    // ------------------------------------------------------------------------
    // [DIV Pool]
    // ------------------------------------------------------------------------
    __divs: [],

    createDiv: function()
    {
      if (this.__divs.length)
      {
        return this.__divs.pop();
      }
      else
      {
        div = BLite.Dom.DIV();
        return div;
      }
    },

    recycleDiv: function(e)
    {
      if (e.parentNode) parendNode.remove(e);
      this.__divs.push(e);
    }
  }
});

// ===========================================================================
// [BLite.UI.TreeItem
// ===========================================================================

//! Tree item implementation.
//!
//! Tree item uses 2 or 3 DIVs to represent itself.
//! DIV (element - main container)
//! - DIV (content, may contain other DIVs)
//!   - INDENT, IMAGE, LINK
//! - DIV (children container, only created for children items if tree has children)
//!   - CHILDREN DIVs
BLite.Object.define("BLite.UI.TreeItem",
{
  extend: BLite.Object,

  construct: function(element)
  {
    this.base(arguments);

    // properties
    this._tree = null;
    this._isRoot = false;
    this._isOpen = false;
    this._id = null;
    this._parent = null;
    this._label = "";
    this._icon = "";
    this._url = "";
    this._css = "tree";
    this._root = null;

    // variables
    this._element = null;
    this._content = null;
    this._container = null;
    this._children = [];

    // dom
    if (element)
      this._adoptElement(element);
    else
      this._createElement();
  },

  properties:
  {
    tree:      { apply: "_applyTree"      },
    isRoot:    { apply: "_applyIsRoot"    },
    isOpen:    { apply: "_applyIsOpen"    },
    id:        { apply: "_applyId"        },
    parent:    { apply: "_applyParent"    },
    label:     { apply: "_applyLabel"     },
    icon:      { apply: "_applyIcon"      },
    url:       { apply: "_applyUrl"       },
    css:       { apply: "_applyCss"       }
  },

  members:
  {
    // ------------------------------------------------------------------------
    // [Create]
    // ------------------------------------------------------------------------

    _createElement: function()
    {
      // create element
      this._element = BLite.Dom.DIV();
      this._element.$$uid = this._uid;

      // create content
      this._content = BLite.Dom.DIV();
      this._element.appendChild(this._content);

      this._contentButton = BLite.UI.Tree.createDiv();
      this._contentIcon = BLite.UI.Tree.createDiv();
      this._contentIcon.style.display = "none";
      this._contentLink = BLite.Dom.A();
      this._content.appendChild(this._contentButton);
      this._content.appendChild(this._contentIcon);
      this._content.appendChild(this._contentLink);

      // don't create container, because it's not guaranted that this item will
      // contain children. _createContainer() will be called if needed.

      // apply css
      this._applyCss(this._css);
    },

    _adoptElement: function(element)
    {
      // adopt element
      this._element = element;
      this._element.$$uid = this._uid;

      // adopt id
      var id = BLite.Dom.act(element.className, "id-");
      if (id) this.setId(id);

      // adopt content
      var content = BLite.Dom.first(this._element, "div");
      if (content)
      {
        this._content = content;
        this._contentButton = BLite.Dom.first(content, "div");
        this._contentIcon = BLite.Dom.next(this._contentButton, "div");
        this._contentLink = BLite.Dom.next(this._contentIcon, "a");
      }

      // adopt container
      var container = BLite.Dom.next(content, "div");
      if (container)
      {
        this._container = container;
        this._isOpen = container.style.display !== "none";

        // adopt children
        var node = BLite.Dom.first(container, "div");
        while (node)
        {
          var item = new BLite.UI.TreeItem(node);
          item._parent = this;
          this._children.push(item);

          node = BLite.Dom.next(node, "div");
        }
      }
    },

    _createContainer: function()
    {
      if (!this._container)
      {
        this._container = BLite.Dom.DIV();
        this._container.style.display = this._isOpen ? "" : "none";
        this._applyCss(this._css);

        BLite.Dom.insertAfter(this._content, this._container);
      }
    },

    _refresh: function()
    {
      var parent = this._parent;
      var isRoot = this._isRoot;
      var isLast = (parent && parent._children[parent._children.length-1] === this)

      var css = this._css;
      var mod = isLast ? "-bottom" : "-center";

      this._contentIcon.className = css + "-cell" + " " + css + "-icon";

      if (this._children.length && !isRoot)
      {
        this._contentButton.className = css + "-cell " + css + mod + (this._isOpen ? "-opened" : "-closed");
        this._contentButton.style.cursor = "pointer";
      }
      else
      {
        this._contentButton.className = css + "-cell " + css + mod + "-nobutton";
        this._contentButton.style.cursor = "";
      }

      if (this._container)
      {
        this._container.className =
          css + ((isRoot) ? "-container-root" : "-container") + " " +
          css + ((isLast || isRoot) ? "-none" : "-line");
      }
    },

    // ------------------------------------------------------------------------
    // [Apply]
    // ------------------------------------------------------------------------

    _applyTree: function(newTree, oldTree)
    {
      if (oldTree)
      {
        if (this._id !== null) delete oldTree._idtouid[this._id];
      }
      if (newTree)
      {
        if (this._id !== null) newTree._idtouid[this._id] = this._uid;
        this._refresh();
      }

      for (var i = 0, len = this._children.length; i < len; i++)
      {
        this._children[i].setTree(newTree);
      }
    },

    _applyIsRoot: function(isRoot)
    {
      this._contentButton.style.display = isRoot ? "none" : "";
      this._refresh();
    },

    _applyIsOpen: function(isOpen)
    {
      if (this._container)
      {
        this._container.style.display = (isOpen) ? "" : "none";
        this._refresh();

        if (this._tree && this._tree._cookie) this._tree._updateCookie();
      }
    },

    _applyId: function(newId, oldId)
    {
      var tree;
      if ((tree = this._tree))
      {
        if (oldId != null) delete tree._idtouid[oldId];
        if (newId != null) tree._idtouid[newId] = this._uid;
      }
    },

    _applyParent: function(parent)
    {
      if (parent && parent._tree)
        this.setTree(parent._tree);
      else
        this.setTree(null);
    },

    _applyLabel: function(label)
    {
      BLite.Dom.setText(this._contentLink, label);
    },

    _applyIcon: function(icon)
    {
      if (icon)
      {
        this._contentIcon.style.background = 'url("' + icon + '") 1px 1px no-repeat';
        this._contentIcon.style.display = "";
      }
      else
      {
        this._contentIcon.style.display = "none";
      }
    },

    _applyUrl: function(url)
    {
      if (url)
      {

      }
      else
      {

      }
    },

    _applyCss: function(value)
    {
      this._element.className = value + "-element";
      this._content.className = value + "-content";

      this._refresh();
    },

    // ------------------------------------------------------------------------
    // [Item Manipulation]
    // ------------------------------------------------------------------------

    indexOf: function(item)
    {
      for (var i = this._children.length-1; i >= 0; i--) if (this._children[i] === item) return i;
      return -1;
    },

    add: function(item, index)
    {
      if (item._parent === this && index === undefined) return;
      if (item._parent) item.unlink();

      var children = this._children;
      index = (index < 0 || index >= children.length) ? children.length-1 : index;

      // create container if needed
      if (children.length === 0) this._createContainer();

      if (index !== -1 && index < children.length)
      {
        BLite.Dom.insertBefore(children[index]._element, item._element);
        children.splice(index, 0, item);
        item.setParent(this);
      }
      else
      {
        BLite.Dom.append(this._container, item._element);
        children.push(item);
        item.setParent(this);
      }
    },

    remove: function(item)
    {
      var i;
      if ((i = this.indexOf(item)) !== -1)
      {
        this._container.removeChild(item._element)
        this._children.splice(i, 1);
        item.setParent(null);
        item._refresh();

        // refresh last node
        if (i && this._children.length === i) this._children[i-1]._refresh();
      }
    },

    unlink: function()
    {
      if (this._parent) this._parent.remove(this);
    },

    traverse: function(fn, ctx, callSelf, recursive)
    {
      if (callSelf) fn.call(ctx, this);

      var ch = this._children;
      for (var i = 0; i < ch.length; i++)
        ch[i].traverse(fn, ctx, true, recursive);
    }
  }
});

