/*
 * Filter.js
 * version: 1.3.2 (4/11/2012)
 *
 * Licensed under the MIT:
 *   http://www.opensource.org/licenses/mit-license.php
 *
 * Copyright 2011 Jiren Patel[ joshsoftware.com ]
 * 
 * Dependency:
 *  jQuery(v1.6 >=)
 */

 (function(window) {

    var _filterJS = function(dataModel, parentNode, view, settings) {
        this.dataModel = dataModel;
        this.view = view;
        this.parentNode = parentNode;
        this.settings = settings || {};
        //this.last_result = null;

        if (this.dataModel.constructor == Object){
          this.dataModel = [this.dataModel];
        }

        for (name in this.dataModel[0]) {
            this.settings.root = name;
        }

        this.render();
        this.buildCategoryMap();
        this.bindEvents();

        if(this.settings.exec_callbacks_on_init &&  this.settings.callbacks){
          this.execCallBacks(this.settings.all_objects);
        }

        if(!this.settings.filter_types){
          this.settings.filter_types = {};
        }

        if(!this.settings.filter_types['range']){
          this.settings.filter_types['range'] = this.rangeFilter;
        }
            
    };

    var FilterJS = function(dataModel, parentNode, view, settings) {
        return new _filterJS(dataModel, parentNode, view, settings);
    };

    FilterJS.VERSION = '1.3.2';

    //Register new html tag
    FilterJS.registerHtmlElement = function(tag_name){
        _filterJS.prototype[tag_name] = function(attrs, content){
           return _filterJS.prototype.content_tag(tag_name, attrs, content);
        };
    };

    _filterJS.prototype = {
       /**
	      * Tag to make html elements.
	      * i.e this.content_tag('span', {class: 'logo_text'}, "Logo Text")
	      * First argument is tag name
	      * Second argument is attributes class,title,id etc.	
	      * Last argument is array of html elements or text.
        **/
        content_tag: function(tag, attrs, content) {
            var el = document.createElement(tag);

            if (attrs) $(el).attr(attrs);
            if (content) {
                if (content.constructor == Array) {
                    content.forEach(function(c) {
                        if (c) $(el).append(c.constructor == String ? c : $(c));
                    });
                }
                else {
                    $(el).html(content);
                }
            }
            return el;
        },

        /**
         * Link Tag:
  	     * i.e. this.link('/test/1' ,{'title': 'title'}, 'link text')	
         **/
        link: function(url, attrs, content) {
            attrs = attrs || {};
            attrs['href'] = url;
            return this.content_tag('a', attrs, content)
        },

        /** 
         * Image Tag:
	       * i.e. this.image('/test.png', {class: 'image'})
	       **/
        image: function(url, attrs) {
            attrs = attrs || {};
            attrs['src'] = url;
            return this.content_tag('img', attrs)
        },

        //Render Html using JSON data
        render: function(parentNode) {
            var base = this;
            var node = $(this.parentNode);

            if (!this.parentNode || !this.view) return;

            this.dataModel.forEach(function(dm) {
                dm = dm[base.settings.root];
                var el = $(base.view(dm));
                el.attr({'id': base.settings.root + '_' + dm.id, 'data-fjs': true});
                node.append(el);
            });
        },

        //Bind Events to filter html elements
        bindEvents: function() {
            var base = this;
            base.settings.selector.forEach(function(selector, index) {
                $(selector.element).live(selector.events, function(e) {
                    base.filter();
                });
            })

           if(base.settings.search){
              $(base.settings.search.input).live('keyup', function(e){
                  //var search_result = base.search(this, base.last_result || base.settings.all_objects);
                  //base.hideShow(search_result);
                  base.filter();
              });
           }
        },

        //Unbind fileter events
        unbindEvents: function() {
            var base = this;
            base.settings.selector.forEach(function(selector, index) {
                $(selector.element).die(selector.events);
            })
        },
                    

        //Find elements accroding to selection criteria.
        filter: function() {
            var base = this;
            var filter_out = base.settings.all_objects.slice();
            var selected_count = 0;
            var select_none = false; //Zero Selection for any category

            base.settings.selector.forEach(function(s) {
                var out = $(s.element).filter(s.select).map(function() {
                    return $(this).val();
                });
                selected_count += out.length;

                if (out.length) {
                    filter_out = base.grep(filter_out, base.findObjects(s.name, out, s.type));
                }
                else{
                  select_none = true;
                }

            });

            if(select_none && base.settings.and_filter_on) filter_out = [];

            //Search
            if(base.settings.search){
              filter_out = base.search(base.settings.search, filter_out);
            }
            base.hideShow(filter_out);

            //Callbacks
            base.execCallBacks(filter_out);

            //base.last_result = filter_out;
        },

        //Compare and collect objects
        findObjects: function(filter_name, categories, filter_type) {
            var base = this;
            var out = [];
            var category_map = base.settings.object_map[filter_name]; 
            var filter_type_function = base.settings.filter_types[filter_type];

            $.each(categories, function(index, category_value) {
                var cat;

                if(filter_type){
                  cat = $.map(category_map, function(n,v){
                          if(filter_type_function(category_value, v)){
                            return category_map[v];
                          }
                  });
                } else {
                   cat = category_map[category_value];
                }

                if(cat) {
                    out = out.concat(cat);
                }
            });

            return out;
        },

        rangeFilter: function(category_value, v){
          var range = category_value.split('-');
          if(range.length == 2){
             if (range[0] == 'below') range[0] = -Infinity;
             if (range[1] == 'above') range[1] = Infinity;
             if (Number(v) >= range[0] && Number(v) <= range[1]){
               return true;
             }
          }
        },

        //Find objects in array
        grep: function(filter_out, value) {
            return jQuery.grep(filter_out,
              function(p, i) {
                return (jQuery.inArray(p, value) != -1);
              });
        },

        //Make eval expresssion  to collect object from the json data.
        makeEvalString: function(field_map, root) {
            var fields = field_map.split('.ARRAY.');
            var eval_out_str = root + '.' + fields[0];

            for (i = 1; i < fields.length; i++) {
                eval_out_str += ".filter_collect('" + fields[i] + "')";
            }

            return eval_out_str;
        },

        //Create map accroding to selection criteria.
        buildObjectMap: function() {
            var base = this;
            var filter_criteria = this.settings.filter_criteria;
            var object_map = {};
            base.settings.selector = [];

            for (name in filter_criteria) {
                var selector = {};

                selector.element = filter_criteria[name][0].split(/.EVENT.|.SELECT.|.TYPE./)[0];
                selector.events = (filter_criteria[name][0].match(/.EVENT.(\S*)/) || [])[1];
                selector.select = (filter_criteria[name][0].match(/.SELECT.(\S*)/) || [])[1]
                selector.type = (filter_criteria[name][0].match(/.TYPE.(\S*)/) || [])[1];
                selector.name = name;

                base.settings.selector.push(selector);

                filter_criteria[name].push(base.makeEvalString(filter_criteria[name][1], base.settings.root));
                object_map[name] = {};
            }

            return object_map;
        },

        buildCategoryMap: function() {
            var filter_criteria = this.settings.filter_criteria;
            var object_map = this.buildObjectMap();
            var settings = this.settings;
            settings.all_objects = [];

            this.dataModel.forEach(function(dm) {
                settings.all_objects.push(dm[settings.root].id);
                for (name in filter_criteria) {
                    var eval_out = eval('dm.' + filter_criteria[name][2]);
                    var obj = object_map[name];

                    if (eval_out && eval_out.constructor == Array) {
                        eval_out.forEach(function(x) {
                            if (obj[x]) {
                                obj[x].push(dm[settings.root].id);
                            } else {
                                obj[x] = [dm[settings.root].id];
                            }
                        });
                    }
                    else {
                        if (obj[eval_out]) {
                            obj[eval_out].push(dm[settings.root].id);
                        } else {
                            obj[eval_out] = [dm[settings.root].id];
                        }
                    }
                }
            });

            settings.object_map = object_map;

            return object_map;
        },

        hideShow: function(id_arr) {
            var id_str = "#" + this.settings.root + '_';
            $(this.parentNode + " > *[data-fjs]").hide();

            id_arr.forEach(function(id) {
                $(id_str + id).show();
            });
        },

        search: function(search_config, filter_result){
          var val = $.trim($(search_config.input).val());
          
          if(!val.length) return filter_result;

          var base = this;
          var id_str = "#" + base.settings.root + '_';

          return $.map(filter_result, function(id){
              var $item = $(id_str + id);
              if(search_config.field_selector){
                $item = $item.find(search_config.field_selector) 
              }

              if($item.text().toUpperCase().indexOf(val.toUpperCase()) >= 0){
                return id;
              }
          });

        },

        execCallBacks: function(result){
            var base = this;
            if(!base.settings.callbacks) return;

            filtered_objects = [];
            $.each(base.dataModel, function(i, v){
              if(v && result.indexOf(v[base.settings.root].id) != -1){
                filtered_objects.push(v[base.settings.root]);
              }
            });

            $.each(base.settings.callbacks, function(name, callback){
                callback.call(base, filtered_objects);
              }
            );
        }

    };

    window.FilterJS = FilterJS;

    //Register html tags
    FilterJS.registerHtmlElement('div');
    FilterJS.registerHtmlElement('span');
    FilterJS.registerHtmlElement('li');
    FilterJS.registerHtmlElement('ul');
    FilterJS.registerHtmlElement('p');

})(this);

/**
 * Recursive method to collect object from json object.
 * i.e. test =  [ {"deal": {"id": 1 }}, {"deal": {"id": 2}}]
 *  - to collect id from the json data
 *    test.filter_collect('deal').filter_collect('id')
 *    this will return [1,2]
 */
Array.prototype.filter_collect = function(field, out_arr) {
    var out_arr = out_arr || [];
    this.forEach(function(obj) {
        if (obj.constructor == Array) {
            obj.filter_collect(field, out_arr);
        }
        else {
            out_arr.push(obj[field]);
        }
    });
    return out_arr;
};

//In IE forEach mathod not define so added manually if not define.
if(!Array.prototype.forEach) {
    Array.prototype.forEach = function(action, that) {
        for (var i = 0, n = this.length; i < n; i++)
        if (i in this) action.call(that, this[i], i, this);
    };
}

//In IE indexOf method not define.
if (!Array.prototype.indexOf) {
  Array.prototype.indexOf = function(obj, start) {
    for (var i = (start || 0), j = this.length; i < j; i++) {
      if (this[i] === obj) { return i; }
  }
  return -1;
}

}


