'use strict';

/*@
Ox.TreeList <f:Ox.Element> TreeList Object
    () ->              <f> TreeList Object
    (options) ->       <f> TreeList Object
    (options, self) -> <f> TreeList Object
    options <o> Options object
        data <f|null> data to be parsed to items, needs documentation
        items <a|[]> array of items
        max <n|-1> maximum number of items that can be selected, -1 unlimited
        min <n|0> minimum number of items that have to be selected
        selected <a|[]> selected ids
        width <n|256> list width
    self    <o> shared private variable
@*/

Ox.TreeList = function(options, self) {

    // fixme: expanding the last item should cause some scroll

    self = self || {};
    var that = Ox.Element({}, self)
            .defaults({
                data: null,
                items: [],
                max: 1,
                min: 0,
                selected: [],
                width: 'auto'
            })
            .options(options || {});

    if (self.options.data) {
        self.options.items = [];
        Ox.forEach(Ox.sort(Ox.keys(self.options.data)), function(key) {
            self.options.items.push(parseData(key, self.options.data[key]));
        });
    }

    that.setElement(
        self.$list = Ox.List({
                _tree: true,
                construct: constructItem,
                itemHeight: 16,
                items: parseItems(),
                itemWidth: self.options.width,
                keys: ['expanded', 'id', 'items', 'level', 'title'],
                max: self.options.max,
                min: self.options.min,
                unique: 'id'
            }, Ox.copy(self))
            .addClass('OxTextList OxTreeList')
            .css({
                width: self.options.width + 'px',
                overflowY: 'scroll'
            })
            .bindEvent({
                anyclick: clickItem,
                select: selectItem,
                toggle: toggleItems
            })
    );

    self.options.selected.length && selectItem({ids: self.options.selected});

    function clickItem(e) {
        var $target = $(e.target),
            $item, id, item;
        if ($target.is('.OxToggle')) {
            $item = $target.parent().parent();
            id = $item.data('id');
            item = getItemById(id);
            toggleItem(item, !item.expanded);
        }
    }

    function constructItem(data) {
        var $item = $('<div>').css({width: self.options.width + 'px'}),
            padding = (data.level + !data.items) * 16 - 8;
        if (data.level || !data.items) {
            $('<div>')
                .addClass('OxCell OxTarget')
                .css({
                    width: padding + 'px',
                })
                .appendTo($item);
        }
        if (data.items) {
            $('<div>')
                .addClass('OxCell')
                .css({
                    width: '8px',
                })
                .append(
                    // fixme: need Ox.Icon()
                    $('<img>')
                        .addClass('OxToggle')
                        .attr({
                            src: Ox.UI.getImageURL(
                                'symbol' + (data.expanded ? 'Down' : 'Right')
                            )
                        })
                )
                .appendTo($item);
        }
        $('<div>')
            .addClass('OxCell OxTarget' + (!data.items ? ' OxSelectable' : ''))
            .css({
                width: (self.options.width - padding - 32 + !data.items * 16) + 'px'
            })
            .html(data.title || '')
            .appendTo($item);
        return $item;
    }

    function getItemById(id, items, level) {
        var ret = null;
        items = items || self.options.items;
        level = level || 0;
        Ox.forEach(items, function(item) {
            if (item.id == id) {
                ret = Ox.extend(item, {
                    level: level
                });
                return false;
            }
            if (item.items) {
                ret = getItemById(id, item.items, level + 1);
                if (ret) {
                    return false;
                }
            }
        });
        return ret;
    }

    function getParent(id, items) {
        var ret;
        Ox.forEach(items, function(item) {
            if (item.items) {
                if (Ox.getObjectById(item.items, id)) {
                    ret = item.id;
                } else {
                    ret = getParent(id, item.items);
                }
                if (ret) {
                    return false;
                }
            }
        });
        return ret;
    }

    function parseData(key, value) {
        var ret = {
                expanded: false,
                id: Ox.uid().toString(),
                title: key.toString() + ': '
            },
            type = Ox.typeOf(value);
        if (type == 'array' || type == 'object') {
            ret.title += Ox.toTitleCase(type)
                + ' <span class="OxLight">[' + Ox.len(value) + ']</span>';
            ret.items = Ox.map(Ox.sort(Ox.keys(value)), function(k) {
                return parseData(k, value[k]);
            });
        } else {
            ret.title += (
                type == 'function'
                ? value.toString().split('{')[0]
                : JSON.stringify(value)
                    .replace(/(^"|"$)/g, '<span class="OxLight">"</span>')
            );
        }
        return ret;
    }

    function parseItems(items, level) {
        var ret = [];
        items = items || self.options.items;
        level = level || 0;
        items.forEach(function(item, i) {
            var item_ = Ox.extend({
                    level: level
                }, item, item.items
                    ? {
                        items: !!item.expanded
                            ? parseItems(item.items, level + 1)
                            : []
                    }
                    : {}
                );
            ret.push(item_);
            item.items && Ox.merge(ret, item_.items);
        });
        return ret;
    }

    function selectItem(data) {
        var id = data.ids[0], parent = id, parents = [];
        while (parent = getParent(parent, self.options.items)) {
            parents.push(parent);
        }
        parents = parents.reverse();
        toggleItems({
            expanded: true,
            ids: parents
        });
        self.$list.options({selected: data.ids})
    }

    function toggleItem(item, expanded) {
        var $img, pos;
        item.expanded = expanded;
        that.find('.OxItem').each(function() {
            var $item = $(this);
            if ($item.data('id') == item.id) {
                $img = $item.find('.OxToggle');
                pos = $item.data('position');
                return false;
            }
        });
        //that.$element.value(item.id, 'expanded', expanded);
        $img.attr({
            src: Ox.UI.getImageURL(
                'symbol' + (expanded ? 'Down' : 'Right')
            )
        });
        expanded
            ? that.$element.addItems(
                pos + 1, parseItems(item.items, item.level + 1)
            )
            : that.$element.removeItems(
                pos + 1, parseItems(item.items, item.level + 1).length
            );
    }

    function toggleItems(data) {
        data.ids.forEach(function(id, i) {
            var item = getItemById(id);
            if (item.items && data.expanded != !!item.expanded) {
                toggleItem(item, data.expanded);
            }
        });
    }

    self.setOption = function(key, value) {
        if (key == 'data') {
            // ...
        } else if (key == 'selected') {
            //self.$list.options({selected: value});
            selectItem({ids: value});
            self.$list.scrollToSelection();
        } else if (key == 'width') {
            // ...
        }
    };

    /*@
    gainFocus <f> gainFocus
    @*/
    that.gainFocus = function() {
        self.$list.gainFocus();
        return that;
    };

    /*@
    hasFocus <f> hasFocus
    @*/
    that.hasFocus = function() {
        return self.$list.hasFocus();
    };

    /*@
    loseFocus <f> loseFocus
    @*/
    that.loseFocus = function() {
        self.$list.loseFocus();
        return that;
    };

    return that;

};