//TODO: hidden fields and dropdowns have clashing names, causing all kinds of havoc since the problem is merely circumvented

tumbler.create = {
    generic: function(input)
    {
        var children = [];
        var id = input.id;

        if(input.parent && tumbler.objects[parent]) {
            tumbler.objects[parent].addChild(id);
        }

        if(input.options.tab) {
            var tabHeader =  $('#' + input.options.tab +' ul.tabHeader')
            tabHeader.append(sprintf('<li data-element="%s">%s</li>', id, input.name));

            if(tabHeader.children().length == 1) {
                $('li', tabHeader).live('click', tabEvent).addClass('active');
                $('#' + id).show();
            }
        }
        
        function tabEvent(event)
        {
            var li = $(event.target).closest('li');
            if(!li.is('.active')) {
                var old = li.siblings('.active');
			    old.removeClass('active');
			    $('#' + old.attr('data-element')).hide();

			    li.addClass('active');
			    $('#' + li.attr('data-element')).show();
            }
        }

        tumbler.objects[id] = {
             addChild:      function(child) { children.push(child) }
            ,getID:         function()      { return id }
            ,getChildren:   function()      { return children }
            ,resetChildren: function()      {
                children.forEach(function(child){tumbler.objects[child].reset()})
            }
        }
        return tumbler.objects[id];
    },

    table: function(input)
    {
        var obj = tumbler.create.generic(input);

        var offset = 0;
        var activeRows = input.data || [];
		var limit = input.options.limit || 0;
		var sortedCol = input.options.sort || null;

        var header = $('<tr></tr>');
        for(var i=0, span=0, col; col = input.columns[i]; i++)
        {
            // If column name is false we don't create a heading
            if(col.name) {
                var colspan = '';
                if(++span > 1) {
                    colspan = _s(' colspan="%s"', span);
                }
                col.span = span;
                span = 0;

                sortedCol = sortedCol || i;
                var th = $(_s('<th%s>%s</th>', colspan, col.name));
                if(!col.unsortable && !input.options.unsortable) {
                    th.attr('data-column', i).click(headerEvent);
                }
                header.append(th);
            } else {
                ++span;
                col.span = 0;
            }
        }

        $('#' + input.id).html('<table><thead></thead><tbody></tbody></table>').find('thead').append(header);


        obj.paint = function()
        {
			var body = $('<tbody></tbody>');

			activeRows.slice(offset, offset + (limit || activeRows.length)).forEach(function(el) {
				body.append(rowFactory(el));
			});

			$('#' + input.id + ' tbody').replaceWith(body);

            if(navigator) navigator.paint();
        }

        obj.reset = function()
        {
            if(filter) filter.reset();
            obj.localReset();
            obj.resetChildren();
        }

        obj.localReset = function()
        {
            activeRows = input.data;
            sort(sortedCol);
            offset = 0;
            if(navigator) navigator.reset();
        }

        obj.getActiveRows = function()      { return activeRows }
        obj.getLimit =      function()      { return limit };
        obj.setActiveRows = function(rows)  {
            activeRows = rows;
            navigator.reset();
            offset = 0;
        }
        obj.setOffset =     function(num) {
            offset = num;
            obj.paint();
        }

        var filter = input.options.filter ? tumbler.create[input.options.filter](obj) : false;
        var navigator = limit ? tumbler.create.navbar(obj) : false;

        var rowFactory = function() {
            var cols = [];
            input.columns.forEach(function(col) {
                var formatter = tumbler.format[col.content] || function(x){return x[col.content]};
                var className = null;
                if(col.span == 0) {
                    className = 'merge';
                }
                else if(col.span > 1) {
                    className = 'merge_left';
                }

                cols.push([formatter, className]);
            });

            return function(row) {
                var tr = document.createElement('tr');
                cols.forEach(function(cell) {
                    var td = document.createElement('td');
                    td.className = cell[1] 
                    td.innerHTML = cell[0](row);
                    tr.appendChild(td);
                });
                return tr;
            }
        }();

        function headerEvent(e)
        {
			var index = $(e.target).closest('th').attr('data-column');
			if(index == sortedCol) {
				activeRows.reverse();
			} else {
                sort(index);
            }

			obj.paint();
        }

        function sort(index)
        {
		    var content = input.columns[index].content;
			var formatter = input.columns[index].sortCol ? function(x){return x[input.columns[index].sortCol]} :
                            input.data[0][content] !== undefined ? function(x){return x[content]} :
                            tumbler.format[content];
			var direction = input.columns[index].sortDirection || (typeof(input.data[0][content]) == 'number' ? -1 : 1);
	        activeRows.sort(function(a, b){
    			var x = formatter(a);
			    var y = formatter(b);
	    	    if((x > y)) return direction;
    			else if((y > x)) return direction*-1;
			    else return 0;
            });

			sortedCol = index;
        }

    	obj.paint();
    },

    textFilter: function(table)
    {
        var timer;
        var currentFilter = '';
        var cols = [];

        var input = $('<span class="textFilter"><label>Filter: <input type="text"></label></span>').
                    prependTo('#' + table.getID()).find('input').bind('keydown', keyEvent);

        // Determine which data elements are strings and save them
        var row = table.getActiveRows()[0];
        for(column in row) {
            if(typeof(row[column]) == 'string') {
                cols.push(column);
            }
        }

        function keyEvent(e)
        {
            clearTimeout(timer);
            timer = setTimeout(filter, 300);
        }

        function filter()
        {
            var filter = input.val();
            if(filter.length < 3) {
                if(currentFilter) {
                    currentFilter = null;
                    table.localReset();
                    table.paint();
                }
                return;
            }
            if(filter.indexOf(currentFilter) == -1) {
                table.localReset();
            }
            currentFilter = filter;

            var result = [];
            var re = new RegExp(currentFilter, 'i');
            table.getActiveRows().forEach(function(row){
                var string = cols.map(function(col){return row[col]}).join();
                if(re.test(string)) {
                    result.push(row);
                }
            });
            table.setActiveRows(result);
            table.paint();
        }

        return {
            reset: function() { currentFilter = null; input.val(''); }
        }
    },

    classFilter: function(table)
    {
        return {};
    },

    navbar: function(table)
    {
        var count, current, limit;
        var first = $('<a href="#first" title="First" class="navFirst">\u00AB First</a>').click(navEvent)
            ,prev = $('<a href="#previous" title="Previous" class="navPrev">&lsaquo; Previous</a>').click(navEvent)
            ,next = $('<a href="#next" title="Next" class="navNext">Next &rsaquo;</a>').click(navEvent)
            ,last = $('<a href="#last" title="Last" class="navLast">Last \u00BB</a>').click(navEvent)
            ,text = $('<span></span>')

        function navEvent(event) {
            filter(event);

            if(current+limit < count) {
                next.show();
                if(current+limit*2 < count)
                    last.show();
                else
                    last.hide();
            } else {
                next.hide();
                last.hide();
            }

            if(current > 0) {
                prev.show();
                if(current > limit)
                    first.show();
                else
                    first.hide();
            } else {
                first.hide();
                prev.hide();
            }

            table.setOffset(current);

            return false;
        };

        function filter(event) {
            switch(event.target.className) {
                case 'navFirst':
                    current = 0;
                    break;
                case 'navPrev':
                    current -= limit;
                    break;
                case 'navNext':
                    current = current+limit > count ? count - (count % limit) : current+limit;
                    break;
                case 'navLast':
                    current = count % limit != 0 ? count - (count % limit) : count  - limit;
                    break;
            }
        }

        function reset() {
            current = 0;
            count = table.getActiveRows().length;
            limit = table.getLimit();

            if(count > limit) {
                if(count < limit*2) {
                    last.hide();
                } else {
                    last.show();
                }
                next.show();
            } else {
                next.hide();
                last.hide();
            }

            first.hide();
            prev.hide();
        }

        function paint() {
            text.html(sprintf('<strong>%s</strong> - <strong>%s</strong> of <strong>%s</strong>', current + (count > 0 ? 1 : 0), limit+current > count ? count : limit+current, count));
        }

        // Initialize
        reset();
        $('#' + table.getID()).prepend(
            $('<div class="table-nav"></div>').append(first).append(prev).append(text).append(next).append(last)
        );

        return {
            reset: reset,
            paint: paint
        }
    },
    /**
     * Create character sheet
     *
     * @param input {object} Data to render in character sheet
     */
    charSheet: function(input) {
        var sheet = $('#' + input.element).clone();

        for(var i=0, item; item = input.data[i]; i++)
        {
            var slot = item.slot;
            if(item.weapon_hand != 0) {
                slot = item.weapon_hand == 1 ? 'Oh' : 'Mh';
            } else if(slot == 15 || slot == 25) {
                slot = 'R';
            }

            $('#slot' + slot, sheet).html(_s('<li class="wow-icon"><img src="wowhead_icons/%s.jpg"><a href="item.php?id=%s"></a></li>', item.icon, item.id));
        }

        $('#' + input.element).replaceWith(sheet);
    },



    selector: function(elementID, source, key, renderer, target, creation, load)
    {
        tumbler.create.generic.call(this, options);
        this.selectedID = null;
        this.parent = null;

        paint();

        this.add = function(data)
        {
            source.push(data);
            paint();
        }

        this.update = function(data)
        {
            for(var i=0, element; element = source[i]; i++)
            {
                if(element[key] == data[key])
                {
                    for(value in data) {
                        element[value] = data[value];
                    }
                }
            }
            paint();
        }

        this.remove = function(data)
        {
            for(var i=0, element; element = source[i]; i++)
            {
                if(element[key] == data[key])
                {
                    source.splice(i, 1);
                }
            }
            paint();
            target.forEach(function(tar) {
               tumbler.objects[tar].reset();
            });
        }

        this.display = function(data, parent)
        {
            self.parent = parent;
            self.selectedID = parent.selectedID;
            source = data;
            paint();
            target.forEach(function(tar) {
               tumbler.objects[tar].reset();
            });
        }

        function paint()
        {
            var ids = [];
            for(var i=0, element; element = source[i]; i++)
            {
                ids.push([element[key], i]);
            }
            var content = ids.map(function(id) {
                return sprintf('<li data-id="%s">%s</li>', id[0], renderer(source[id[1]]));
            });
            var header = creation ? '<li data-id="new">New</li><li>-------</li>' : '';

            $('#' + elementID).html(sprintf('<ul class="selector">%s%s</ul>', header, content.join('')));
        }

        $('#' + elementID).click(function(e) {
            var li = e.target.getAttribute('data-id') ? e.target : $(e.target).parents('li[@data-id]').get(0);
            if(li)
            {
                $('#' + elementID + ' li.selected').removeClass('selected');
                $(li).addClass('selected');

                var id = li.getAttribute('data-id');
                if(!self.parent) self.selectedID = id;

                if(id == 'new') {
                    target.forEach(function(tar) {
                        // show/hide new/update
                        tumbler.objects[tar].reset(self);
                        $('#' + tar).removeClass('update');

                        if(tumbler.objects[tar].elements) {
                            tumbler.objects[tar].elements.forEach(function(el) {el.disabled = false;});
                        }
                    });
                } else {
                    var selected = source.filter(function(el){return el[key] == id})[0];
                    for(var i=0; tar = target[i]; i++)
                    {
                        $('#' + tar).addClass('update');
                        // TODO, caching to prevent reloads
                        if(load && load[i]) {
                            tumbler.load(sprintf('%s&params=%s', load[i][0], load[i][1].map(function(el){
                                return selected[el]
                            }).join(',')), function(data, target) {
                                tumbler.objects[target].display(data, self)
                                }, tar
                            );
                        } else {
                            tumbler.objects[tar].display(selected, self);
                        }
                    }
                }
            }
        });

        return self;
    },

    // TODO: refactor
    form: function(elementID, type, model, foreignKey, callback)
    {
        var self = this;
        var form  = $('<form method="post" action=""></form>');
        this.elements = [];
        this.controller = null;

        for(var i=0, element; element = model[i]; i++)
        {
            var disabled = element.disabled ? ' disabled' : '';
            // Textbox
            if(element.content == 'text')
            {
                $(form).append('<fieldset><label>' + element.label + '</label><input type="text" name="' + element.label.toLowerCase() + '"' + disabled + ' /></fieldset>');
                this.elements.push($('*:last-child input', form).get(0));
            }
            // Select
            else if(typeof(element.content) == 'object')
            {
                var select = '<select name="' + element.key + '"' + disabled + '>';
                    select += element.header ? '<option value=0>' + element.header + '</option><option value="-1" disabled="disabled">---</option>' : '';

                if(typeof(element.content[0]) == 'object')
                {
                    if(element.content[0].map) {
                        // Array
                        select += element.content.map(function(el){return sprintf('<option value="%s">%s</option>', el[0], el[1])}).join('')
                    } else {
                        // Object
                        select += element.content.map(function(el){return sprintf('<option value="%s">%s</option>', el.id, el.name)}).join('')
                    }
                }
                else
                {
                    var j = 0;
                    select += element.content.map(function(el){return '<option value="' + j++ + '">' + el + '</option>'}).join('')
                }
                $(form).append('<fieldset><label>' + element.label + '</label>' + select + '</select></fieldset>');
                this.elements.push($('*:last-child select', form).get(0));

            }
            // Hidden
            else if(element.content == 'hidden')
            {
                $(form).append('<input type="hidden" name="' + element.label.toLowerCase() + '" />');
                this.elements.push(form.get(0).lastChild);
            }
            // Button
            else if(element.content == 'button')
            {
                var warning = element.warning ? sprintf(' data-warning="%s"', element.warning) : '';
                $(form).append(sprintf('<button type="%s" name="type" value="%s" class="%s"%s>%s</button>', element.type, sprintf('%s-%s', element.key, type), element.key, warning, element.label));
                this.elements.push(form.get(0).lastChild);
            }
            // Checkbox
            else if(element.content == 'checkbox')
            {
                $(form).append('<fieldset><label>' + element.label + '</label><input type="checkbox" name="' + element.label.toLowerCase() + '"' + disabled + ' /></fieldset>');
                this.elements.push($('*:last-child input', form).get(0));
            }
        }

        $('#' + elementID).append(form);
        $('button[type=submit]', form).click(function(e) {
            e.preventDefault();
            e.target.disabled = true;
            var formValues = $(form).serialize() + '&parent_key=' + self.controller.selectedID;

            if(e.target.getAttribute('data-warning') && !confirm(e.target.getAttribute('data-warning'))) {
                e.target.disabled = false;
                return;
            }

            tumbler.action.submit(formValues, e.target, function(response) {
                e.target.disabled = false;
                if(response.success)
                {
                    //opera.postError(response.query);
                    var object = {};
                    formValues.split('&').forEach(function(el) {
                        var split = el.split('=');
                        object[split[0]] = split[1];
                    });
                    if(response.data) {
                        for(key in response.data) {
                            object[key] = response.data[key];
                        }
                    }
                    if(e.target.className == 'remove') {
                        self.controller.remove(object);
                        self.reset();
                    } else if(e.target.className == 'update') {
                        self.controller.update(object);
                    } else {
                        self.controller.add(object);
                    }

                    if(callback) callback.call(self, true);
                }
                else
                {
                    alert('Request failed: ' + response.error);
                }
            });
        });

        this.display = function(data, controller)
        {
            self.controller = controller;
            for(var i=0, element; element = this.elements[i]; i++)
            {
                if(model[i].disabled) {
                    element.disabled = true;
                }

                if(element.tagName == 'SELECT')
                {
                    for(var j=0, option; option = element.options[j]; j++)
                    {
                        if(option.value == data[model[i].key]) {
                            element.selectedIndex = j;
                            break;
                        }
                    }
                }
                else if(element.type == 'checkbox')
                {
                    element.checked = data[model[i].key];
                }
                else if(element.type != 'submit')
                {
                    element.value = data[model[i].key];
                }
            }
        }

        this.reset = function(controller)
        {
            self.controller = controller;
            $('#' + elementID).removeClass('update')
            for(var i=0, element; element = this.elements[i]; i++)
            {
                if(model[i].disabled) {
                    element.disabled = true;
                }

                if(element.tagName == 'SELECT') {
                    element.selectedIndex = 0;
                } else if(element.type == 'checkbox') {
                    element.checked = false;
                } else if(element.type != 'submit'){
                    element.value = '';
                }
            }
        }

        return self;
    }
}

tumbler.action = {
    filterClass: function(e)
    {
        var el = e.target || e.srcElement;
        if(el.tagName != 'LI') return;

        var id = el.parentNode.parentNode.parentNode.id;
        var table = tumbler.tables[id];
        if(el.id == 'filterAll')
        {
            if($(el).is('.filtered'))
            {
                table.filtered.length = 0;
                $('li',el.parentNode).removeClass('filtered');
            }
            else
            {
                table.filtered = [null, true, true, true, true, true, true, true, true, true];
                $('li',el.parentNode).addClass('filtered');
            }
        }
        else
        {
            var classIndex = el.className.match(/c([\d]+)/)[1];
            table.filtered[classIndex] = !$(el).is('.filtered');
            $(el).toggleClass('filtered');
        }

        table.sortArray.length = 0;
        for(var i=0; i < tumbler.data[table.data].length; i++)
        {
            if(!table.filtered[tumbler.data[table.data][i]['charClass']])
            {
                table.sortArray.push(i);
            }
        }

        tumbler.render.table(id);
    },

    submit: function(formValues, element, callback)
    {
        var elementType = sprintf('%s%s=%s', formValues.length > 0 ? '&' : '', element.name, element.value);
        //opera.postError(formValues);
        $.post('json/post.php', formValues + elementType, callback, 'json');
    },
}

tumbler.format = {
    'raidatt':          function(cell) { return cell['raidatt'].toFixed(1); },
    'standbyatt':       function(cell) { return cell['standbyatt'].toFixed(1); },
    'totalatt':         function(cell) {
                            var colour = cell['totalatt'] > 66.6 ? 'positive' : cell['totalatt'] > 50 ? 'neutral' : 'negative';
                            return '<strong><span class="' + colour + '">' + cell['totalatt'].toFixed(1) + '%</span></strong>';
                        },
    'totaldkp':         function(cell) {
                            var colour = cell['totaldkp'] > 0 ? 'positive' : 'negative';
                            return '<strong><span class="' + colour  + '">' + cell['totaldkp'] + '</span></strong>';
                        },
    'itemname':         function(cell) { return _s('<a href="item.php?id=%s" class="q%s">%s</a>', cell['itemid'], cell['itemcolour'], cell['itemname']);},
    'item-icon':        function(cell) { return _s('<div class="wow-icon"><img src="wowhead_icons/%s.jpg"><a href="item.php?id=%s"></a></div>', cell['itemicon'], cell['itemid']);},
    'characterName':    function(cell) {return _s('<a href="character.php?name=%s">%s</a>', cell['name'], cell['name'])},
    'character':        function(cell) { return _s('<div class="classIcon c%s"></div><a href="character.php?name=%s">%s</a>', cell['char_class'], cell['name'], cell['name']);},
    'raid':             function(cell) {
                            var note = cell['note'] !== 'NULL' ? ' - <em>' + cell['note'] + '</em>' : '';
                            return '<a href="raid.php?id=' + cell['raidid'] + '">' + cell['date'] + '</a>' + note;
                        },
    'zone-icons':       function(cell) { return _s('<div class="wow-icon"><img src="image/zones/%s.jpg" alt="%s" title="%s"></div>', cell['zone_abbr'], cell['zone_name'], cell['zone_name']) },
	'none':             function(cell) { return 'none'}
}
