(function($) {
    var $t = $.telerik;
    var escapeQuoteRegExp = /'/ig;
    var filterFunctions = ['startswith', 'substringof', 'endswith'];
    var formatRegExp = /\{0(:([^\}]+))?\}/;

    var filterCommandsByType = {
        'String': {
            'Is equal to': 'eq',
            'Is not equal to': 'ne',
            'Starts with': 'startswith',
            'Contains': 'substringof',
            'Ends with': 'endswith'
        },
        'Number': {
            'Is equal to': 'eq',
            'Is not equal to': 'ne',
            'Is less than': 'lt',
            'Is less than or equal to': 'le',
            'Is greater than': 'gt',
            'Is greater than or equal to': 'ge'
        },
        'Date': {
            'Is equal to': 'eq',
            'Is not equal to': 'ne',
            'Is before': 'lt',
            'Is before or equal to': 'le',
            'Is after': 'gt',
            'Is after or equal to': 'ge'
        }
    };

    var fx = [$t.fx.slide.defaults()];

    $t.gridFilter = {};

    /* Here `this` is the Grid instance*/

    $t.gridFilter.implementation = {
        hideFilter: function(filterCallback) {
            filterCallback = filterCallback || function() { return true; };
            $('.t-filter-options', this.element)
                .filter(filterCallback)
                .each(function() {
                    $t.fx.rewind(fx, $(this), { direction: 'bottom' });
                });
        },
        
        createFilterMenu: function(column) {
            var filterMenuHtml = new $t.stringBuilder();
            var filters = filterCommandsByType[column.type];

            var appendFilteringUi =
				function(stringBuilder) {

				    stringBuilder.cat('<select>');

				    for (var filter in filters)
				        stringBuilder
							.cat('<option value="')
							.cat(filters[filter])
							.cat('">')
							.cat(filter)
							.cat('</option>');

				    stringBuilder
						.cat('</select>')
						.cat('<input type="text" />');
				};

            filterMenuHtml.cat('<div class="t-animation-container"><div class="t-filter-options t-group" style="display:none">')
					.cat('<button class="t-button t-state-default t-clear-button"><span class="t-icon t-clear-filter"></span>Clear Filter</button>')
					.cat('<label>Show rows with value that</label>');

            if (column.type != 'Boolean') {
                appendFilteringUi(filterMenuHtml);
                filterMenuHtml.cat('<label>And</label>');
                appendFilteringUi(filterMenuHtml);
            } else {
                var id = $(this.element).attr('id') + column.name;
                var appendBooleanUI = function(value) {
                    filterMenuHtml.cat('<div><input type="radio" style="width:auto;display:inline" id="')
						.cat(id + value)
						.cat('" name="')
						.cat(id)
						.cat('" value="')
						.cat(value)
						.cat('" />')
						.cat('<label style="display:inline" for="')
						.cat(id + value)
						.cat('">Is ')
						.cat(value)
						.cat('</label></div>');
                };
                appendBooleanUI(true);
                appendBooleanUI(false);
            }
            
            filterMenuHtml.cat('<button class="t-button t-state-default t-filter-button"><span class="t-icon t-filter"></span>Filter</button>')
				          .cat('</div></div>');

            return $(filterMenuHtml.string()).appendTo(this.element);
        },

        showFilter: function(e, element) {
            e.stopPropagation();
            this.hideFilter(function() {
                return this.parentNode != element;
            });
            
            var $element = $(element);
            var $filterMenu = $element.data('filter');

            if (!$filterMenu) {
                // filtering menu should be created
                var column = this.columns[$element.parent()[0].cellIndex];
            
                $filterMenu = this.createFilterMenu(column)
                        .data('column', column)
                        .click(function(e) { e.stopPropagation(); e.preventDefault(); })
                        .find('.t-filter-button').click($t.delegate(this, this.filterClick)).end()
                        .find('.t-clear-button').click($t.delegate(this, this.clearClick)).end()
                        .find('input[type=text]').keyup($t.delegate(this, this.filterKeyUp)).end();
                
                $element.data('filter', $filterMenu);
            }
            
            // position filtering menu
            var position = { top: $element.outerHeight() };
            var parent = $element.parent()[0];
            var headerRow = $element.parent().parent();
            var width = - this.$headerWrap.scrollLeft() - 1;
            
            headerRow.find('.t-header').each(function() {
                width += $(this).outerWidth();
                if (this == parent)
                    return false;
            });
            
            var left = width - $element.outerWidth();
            
            // constrain filtering menu within grid
            var outerWidth = $filterMenu.outerWidth() || $filterMenu.find('.t-group').outerWidth();
            
            if (left + outerWidth > headerRow.outerWidth())
                left = width - outerWidth + 1;
            
            if ($(this.element).hasClass('t-grid-rtl'))
                position['right'] = left + ($.browser.mozilla || $.browser.safari ? 18 : 0);
            else
                position['left'] = left;
            
            $filterMenu.css(position);
            
            $t.fx[$filterMenu.find('.t-filter-options').is(':visible') ? 'rewind' : 'play'](fx, $filterMenu.find('.t-filter-options'), { direction: 'bottom' });
        },

        getColumn: function($element) {
            return $element.closest('.t-animation-container').data('column');
        },

        clearClick: function(e, element) {
            var $element = $(element);
            var column = this.getColumn($element);

            column.filters = [];

            $element.siblings('input[type=text]')
                .val('')
                .removeClass('t-state-error');

            this.filter(this.filterExpr());
        },

        filterClick: function(e, element) {
            var $element = $(element);
            var column = this.getColumn($(element));
            column.filters = [];
            var hasErrors = false;

            $element.parent().children('input[type=text]').each($.proxy(function(index, input) {
                var $input = $(input);
                var value = $.trim($input.val());

                if (!value) {
                    $input.removeClass('t-state-error');
                    return true;
                }

                var valid = this.isValidFilterValue(column, value);

                $input.toggleClass('t-state-error', !valid);

                if (!valid) {
                    hasErrors = true;
                    return true;
                }

                var operator = $input.prev('select').val()
                column.filters.push({ operator: operator, value: value });
            }, this));
            
            $element.parent().find('input:checked').each($.proxy(function(index, input) {
                var $input = $(input);
                var value = $(input).attr('value');
                column.filters.push({ operator: 'eq', value: value });
            }, this));

            if (!hasErrors) {
                this.filter(this.filterExpr());
                this.hideFilter();
            }
        },

        filterKeyUp: function(e, element) {
            if (e.keyCode != 13)
                return;
            this.filterClick(e, element);
        },

        filterDocumentClick: function(e, element) {
            if (e.which == 3)
                return;

            this.hideFilter();
        },

        isValidFilterValue: function(column, value) {
            if (column.type == 'Date') {
                return !isNaN(Date.parse(value));
            } else if (column.type == 'Number') {
                return !isNaN(value);
            }

            return true;
        },

        parseDate: function(column, value) {
            return new Date(Date.parse(value));
        },

        getFormat: function(column) {
            var match = formatRegExp.exec(column.format);
            return match ? match[2] : $t.cultureInfo.shortDate;
        },

        encodeFilterValue: function(columnIndex, value) {
            var column = this.columns[columnIndex];
            switch (column.type) {
                case 'String':
                    return "'" + value.replace(escapeQuoteRegExp, "''") + "'";
                case 'Date':
                    var date = this.parseDate(column, value);
                    return "datetime'" + $t.formatString('{0:yyyy-MM-ddThh-mm-ss}', date) + "'";
            }

            return value;
        },

        filterExpr: function() {
            var result = [];
            for (var columnIndex = 0, length = this.columns.length; columnIndex < length; columnIndex++) {
                var column = this.columns[columnIndex];

                if (!column.filters)
                    continue;

                for (var filterIndex = 0; filterIndex < column.filters.length; filterIndex++) {
                    var filter = column.filters[filterIndex];
                    var expr = new $t.stringBuilder();
                    if ($.inArray(filter.operator, filterFunctions) > -1) {
                        expr.cat(filter.operator)
                        .cat('(')
                        .cat(column.name)
                        .cat(',')
                        .cat(this.encodeFilterValue(columnIndex, filter.value))
                        .cat(')');
                    } else {
                        expr.cat(column.name)
                        .cat('~')
                        .cat(filter.operator)
                        .cat('~')
                        .cat(this.encodeFilterValue(columnIndex, filter.value));
                    }
                    result.push(expr.string());
                }
            }

            return result.join('~and~');
        },

        filter: function(filterExpression) {
            this.currentPage = 1;
            this.filterExpression = filterExpression;
            if (!this.isAjax())
                location.href = $t.formatString(unescape(this.urlFormat), 
                    this.currentPage, this.orderBy || '~', escape(this.filterExpression) || '');
                            
            this.ajaxRequest();
        }
    };

    $.extend($t.gridFilter, {
        initialize: function(grid) {
            $.extend(grid, $t.gridFilter.implementation);
            
            $('.t-grid-content', grid.element).bind('scroll', function() {
                grid.hideFilter();
            });
            
            $(document).click($t.delegate(grid, grid.filterDocumentClick));
            $('.t-grid-filter', grid.element)
				.click($t.delegate(grid, grid.showFilter))
				.live('mouseenter', $t.hover)
				.live('mouseleave', $t.leave);
				
			$('.t-filter-options .t-button', grid.element)
				.live('mouseenter', $t.buttonHover)
				.live('mouseleave', $t.buttonLeave);
        }
    });
})(jQuery);