/**
 * This base class is used to handle data preparation (e.g., sorting, filtering and
 * group summary).
 */
Ext.define('Ext.ux.ajax.DataSimlet', function() {
    function makeSortFn(def, cmp) {
        var order = def.direction,
            sign = (order && order.toUpperCase() === 'DESC') ? -1 : 1;

        return function(leftRec, rightRec) {
            var lhs = leftRec[def.property],
                rhs = rightRec[def.property],
                c = (lhs < rhs) ? -1 : ((rhs < lhs) ? 1 : 0);

            if (c || !cmp) {
                return c * sign;
            }

            return cmp(leftRec, rightRec);
        };
    }

    function makeSortFns(defs, cmp) {
        var sortFn, i;

        for (sortFn = cmp, i = defs && defs.length; i;) {
            sortFn = makeSortFn(defs[--i], sortFn);
        }

        return sortFn;
    }

    return {
        extend: 'Ext.ux.ajax.Simlet',

        buildNodes: function(node, path) {
            var me = this,
                nodeData = {
                    data: []
                },
                len = node.length,
                children, i, child, name;

            me.nodes[path] = nodeData;

            for (i = 0; i < len; ++i) {
                nodeData.data.push(child = node[i]);
                name = child.text || child.title;

                child.id = path ? path + '/' + name : name;
                children = child.children;

                if (!(child.leaf = !children)) {
                    delete child.children;

                    me.buildNodes(children, child.id);
                }
            }
        },

        deleteRecord: function(pos) {
            if (this.data && typeof this.data !== 'function') {
                Ext.Array.removeAt(this.data, pos);
            }
        },

        fixTree: function(ctx, tree) {
            var me = this,
                node = ctx.params.node,
                nodes;

            if (!(nodes = me.nodes)) {
                me.nodes = nodes = {};
                me.buildNodes(tree, '');
            }

            node = nodes[node];

            if (node) {
                if (me.node) {
                    me.node.sortedData = me.sortedData;
                    me.node.currentOrder = me.currentOrder;
                }

                me.node = node;
                me.data = node.data;
                me.sortedData = node.sortedData;
                me.currentOrder = node.currentOrder;
            }
            else {
                me.data = null;
            }
        },

        getData: function(ctx) {
            var me = this,
                params = ctx.params,
                order = (params.filter || '') + (params.group || '') + '-' + (params.sort || '') +
                        '-' + (params.dir || ''),
                tree = me.tree,
                dynamicData, data, fields, sortFn, filters;

            if (tree) {
                me.fixTree(ctx, tree);
            }

            data = me.data;

            if (typeof data === 'function') {
                dynamicData = true;
                data = data.call(this, ctx);
            }

            // If order is '--' then it means we had no order passed, due to the string concat above
            if (!data || order === '--') {
                return data || [];
            }

            if (!dynamicData && order === me.currentOrder) {
                return me.sortedData;
            }

            ctx.filterSpec = params.filter && Ext.decode(params.filter);
            ctx.groupSpec = params.group && Ext.decode(params.group);

            fields = params.sort;

            if (params.dir) {
                fields = [{ direction: params.dir, property: fields }];
            }
            else if (params.sort) {
                fields = Ext.decode(params.sort);
            }
            else {
                fields = null;
            }

            if (ctx.filterSpec) {
                filters = new Ext.util.FilterCollection();

                filters.add(this.processFilters(ctx.filterSpec));
                data = Ext.Array.filter(data, filters.getFilterFn());
            }

            sortFn = makeSortFns((ctx.sortSpec = fields));

            if (ctx.groupSpec) {
                sortFn = makeSortFns([ctx.groupSpec], sortFn);
            }

            // If a straight Ajax request, data may not be an array.
            // If an Array, preserve 'physical' order of raw data...
            data = Ext.isArray(data) ? data.slice(0) : data;

            if (sortFn) {
                Ext.Array.sort(data, sortFn);
            }

            me.sortedData = data;
            me.currentOrder = order;

            return data;
        },

        processFilters: Ext.identityFn,

        getPage: function(ctx, data) {
            var ret = data,
                length = data.length,
                start = ctx.params.start || 0,
                end = ctx.params.limit ? Math.min(length, start + ctx.params.limit) : length;

            if (start || end < length) {
                ret = ret.slice(start, end);
            }

            return ret;
        },

        getGroupSummary: function(groupField, rows, ctx) {
            return rows[0];
        },

        getSummary: function(ctx, data, page) {
            var me = this,
                groupField = ctx.groupSpec.property,
                accum,
                todo = {},
                summary = [],
                fieldValue,
                lastFieldValue;

            Ext.each(page, function(rec) {
                fieldValue = rec[groupField];
                todo[fieldValue] = true;
            });

            function flush() {
                if (accum) {
                    summary.push(me.getGroupSummary(groupField, accum, ctx));
                    accum = null;
                }
            }

            // data is ordered primarily by the groupField, so one pass can pick up all
            // the summaries one at a time.
            Ext.each(data, function(rec) {
                fieldValue = rec[groupField];

                if (lastFieldValue !== fieldValue) {
                    flush();
                    lastFieldValue = fieldValue;
                }

                if (!todo[fieldValue]) {
                    // if we have even 1 summary, we have summarized all that we need
                    // (again because data and page are ordered by groupField)
                    return !summary.length;
                }

                if (accum) {
                    accum.push(rec);
                }
                else {
                    accum = [rec];
                }

                return true;
            });

            flush(); // make sure that last pesky summary goes...

            return summary;
        }
    };
}());
