app.Query = (function() {

    function constructFind(query) {
        Ox.print('cF', query)
        return /*encodeURI(*/$.map(query.conditions, function(v, i) {
            if (!Ox.isUndefined(v.conditions)) {
                return '[' + constructFind(v) + ']';
            } else {
                return v.value !== '' ? v.key + (v.key ? ':' : '') + constructValue(v.value, v.operator) : null;
            }
        }).join(query.operator)/*)*/;
    }

    function constructValue(value, operator) {
        operator = operator.replace('=', '^$');
        if (operator.indexOf('$') > -1) {
            value = operator.substr(0, operator.length - 1) + value + '$'
        } else {
            value = operator + value;
        }
        return value;
    }

    function mergeFind() {
    }

    function parseFind(str) {
        var find = {
                conditions: [],
                operator: ''
            },
            subconditions = str.match(/\[.*?\]/g) || [];
        $.each(subconditions, function(i, v) {
            subconditions[i] = v.substr(1, v.length - 2);
            str = str.replace(v, '[' + i + ']');
        });
        if (str.indexOf(',') > -1) {
            find.operator = '&';
        } else if (str.indexOf('|') > -1) {
            find.operator = '|';
        }
        Ox.print('pF', str, find.operator)
        find.conditions = $.map(find.operator === '' ? [str] : str.split(find.operator == '&' ? ',' : '|'), function(v, i) {
            Ox.print('v', v)
            var ret, kv;
            if (v[0] == '[') {
                Ox.print('recursion', subconditions)
                ret = parseFind(subconditions[parseInt(v.substr(1, v.length - 2))]);
            } else {
                kv = ((v.indexOf(':') > - 1 ? '' : ':') + v).split(':');
                ret = $.extend({
                    key: kv[0]
                }, parseValue(kv[1]));
            }
            return ret;
        });
        return find;
    }

    function parseValue(str) {
        var value = {
                value: decodeURI(str),
                operator: ''
            };
        if (value.value[0] == '!') {
            value.operator = '!'
            value.value = value.value.substr(1);
        }
        if ('^<>'.indexOf(value.value[0]) > -1) {
            value.operator += value.value[0];
            value.value = value.value.substr(1);
        }
        if (value.value.substr(-1) == '$') {
            value.operator += '$';
            value.value = value.value.substr(0, value.value.length - 1);
        }
        value.operator = value.operator.replace('^$', '=');
        return value;
    }

    return {

        fromString: function(str) {
            var query = Ox.unserialize(str),
                sort = [];
            if ('find' in query) {
                app.user.ui.findQuery = parseFind(query.find);
                Ox.print('user.ui.findQuery', app.user.ui.findQuery)
            }
            if ('sort' in query) {
                sort = query.sort.split(',')
                app.user.ui.sort = $.map(query.sort.split(','), function(v, i) {
                    var hasOperator = '+-'.indexOf(v[0]) > -1,
                        key = hasOperator ? query.sort.substr(1) : query.sort,
                        operator = hasOperator ? v[0].replace('+', '') : Ox.getObjectById(app.config.sortKeys, key).operator;
                    return {
                        key: key,
                        operator: operator
                    };
                });
            }
            if ('view' in query) {
                app.user.ui.listView = query.view;
            }
        },

        toObject: function(groupId) {
            Ox.print('tO', app.user.ui.findQuery.conditions)
            // the inner $.merge() creates a clone
            var conditions = $.merge($.merge([], app.user.ui.listQuery.conditions), app.user.ui.findQuery.conditions);
            $.merge(conditions, app.ui.groups ? $.map(app.ui.groups, function(v, i) {
                    if (v.id != groupId && v.query.conditions.length) {
                        return v.query.conditions.length == 1 ?
                            v.query.conditions : v.query;
                    }
                }) : []),
                operator = conditions.length < 2 ? '' : ','; // fixme: should be &
            Ox.print('>>', groupId, app.user.ui.find, conditions);
            return {
                conditions: conditions,
                operator: operator
            };
        },

        toString: function() {
            Ox.print('tS', app.user.ui.find)
            return Ox.serialize({
                find: constructFind(app.Query.toObject()),
                sort: app.user.ui.sort[0].operator + app.user.ui.sort[0].key,
                view: app.user.ui.listView
            });
        }

    };

})();

