(function(){
  
  var Spine;
  if (typeof exports !== "undefined") {
    Spine = exports;
  } else {
    Spine = this.Spine = {};
  }
  
  Spine.version = "0.0.4";
  
  var $ = Spine.$ = this.jQuery || this.Zepto || function(){ return arguments[0]; };
  
  var makeArray = Spine.makeArray = function(args){
    return Array.prototype.slice.call(args, 0);
  };
  
  var isArray = Spine.isArray = function(value){
    return Object.prototype.toString.call(value) == "[object Array]";
  };
  
  // Shim Array, as these functions aren't in IE
  if (typeof Array.prototype.indexOf === "undefined")
    Array.prototype.indexOf = function(value){
      for ( var i = 0; i < this.length; i++ )
    		if ( this[ i ] === value )
    			return i;
    	return -1;
    };
  
  var Events = Spine.Events = {
    bind: function(ev, callback) {
      var evs   = ev.split(" ");
      var calls = this._callbacks || (this._callbacks = {});
      
      for (var i=0; i < evs.length; i++)
        (this._callbacks[evs[i]] || (this._callbacks[evs[i]] = [])).push(callback);

      return this;
    },

    trigger: function() {
      var args = makeArray(arguments);
      var ev   = args.shift();
            
      var list, calls, i, l;
      if (!(calls = this._callbacks)) return false;
      if (!(list  = this._callbacks[ev])) return false;
      
      for (i = 0, l = list.length; i < l; i++)
        if (list[i].apply(this, args) === false)
          break;

      return true;
    },
    
    unbind: function(ev, callback){
      if ( !ev ) {
        this._callbacks = {};
        return this;
      }
      
      var list, calls, i, l;
      if (!(calls = this._callbacks)) return this;
      if (!(list  = this._callbacks[ev])) return this;
      
      if ( !callback ) {
        delete this._callbacks[ev];
        return this;
      }
      
      for (i = 0, l = list.length; i < l; i++)
        if (callback === list[i]) {
          list.splice(i, 1);
          break;
        }
        
      return this;
    }
  };
  
  var Log = Spine.Log = {
    trace: true,
    
    logPrefix: "(App)",

    log: function(){
      if ( !this.trace ) return;
      if (typeof console == "undefined") return;
      var args = makeArray(arguments);
      if (this.logPrefix) args.unshift(this.logPrefix);
      console.log.apply(console, args);
      return this;
    }
  };
  
  // Classes (or prototypial inheritors)
  
  if (typeof Object.create !== "function")
      Object.create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
      };
      
  var moduleKeywords = ["included", "extended"];

  var Class = Spine.Class = {
    inherited: function(){},
    created: function(){},
    
    prototype: {
      initialize: function(){},
      init: function(){}
    },

    create: function(include, extend){
      var object = Object.create(this);
      object.parent    = this;
      object.prototype = object.fn = Object.create(this.prototype);

      if (include) object.include(include);
      if (extend)  object.extend(extend);

      object.created();
      this.inherited(object);
      return object;
    },

    init: function(){
      var instance = Object.create(this.prototype);
      instance.parent = this;

      instance.initialize.apply(instance, arguments);
      instance.init.apply(instance, arguments);
      return instance;
    },

    proxy: function(func){
      var thisObject = this;
      return(function(){ 
        return func.apply(thisObject, arguments); 
      });
    },
    
    proxyAll: function(){
      var functions = makeArray(arguments);
      for (var i=0; i < functions.length; i++)
        this[functions[i]] = this.proxy(this[functions[i]]);
    },

    include: function(obj){
      for(var key in obj)
        if (moduleKeywords.indexOf(key) == -1)
          this.fn[key] = obj[key];
      
      var included = obj.included;
      if (included) included.apply(this);
      return this;
    },

    extend: function(obj){
      for(var key in obj)
        if (moduleKeywords.indexOf(key) == -1)
          this[key] = obj[key];
      
      var extended = obj.extended;
      if (extended) extended.apply(this);
      return this;
    }
  };
  
  Class.prototype.proxy    = Class.proxy;
  Class.prototype.proxyAll = Class.proxyAll;
  Class.instancet               = Class.init;
  Class.sub                = Class.create;

  // Models
  
  Spine.guid = function(){
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
      var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
      return v.toString(16);
    }).toUpperCase();      
  };

  var Model = Spine.Model = Class.create();
  
  Model.extend(Events);

  Model.extend({
    setup: function(name, atts){
      var model = Model.sub();
      if (name) model.name = name;
      if (atts) model.attributes = atts;
      return model;
    },
    
    created: function(sub){
      this.records = {};
      this.attributes = this.attributes ? 
        makeArray(this.attributes) : [];
    },

    find: function(id){
      var record = this.records[id];
      if ( !record ) throw("Unknown record");
      return record.clone();
    },

    exists: function(id){
      try {
        return this.find(id);
      } catch (e) {
        return false;
      }
    },

    refresh: function(values){
      values = this.fromJSON(values);
      this.records = {};

      for (var i=0, il = values.length; i < il; i++) {    
        var record = values[i];
        record.newRecord = false;
		record.id = record.id || Spine.guid();
        this.records[record.id] = record;
      }

      this.trigger("refresh");
      return this;
    },

	// adeed by ayush
    createAll: function(values){
      values = this.fromJSON(values);

      for (var i=0, il = values.length; i < il; i++) {    
        var record = values[i];
        record.newRecord = false;
		record.id = record.id || Spine.guid();
        this.records[record.id] = record;
      }

      return this;
    },

    select: function(callback){
      var result = [];

      for (var key in this.records)
        if (callback(this.records[key]))
          result.push(this.records[key]);

      return this.cloneArray(result);
    },

    findByAttribute: function(name, value){
      for (var key in this.records)
        if (this.records[key][name] == value)
          return this.records[key].clone();
    },

    findAllByAttribute: function(name, value){
      return(this.select(function(item){
        return(item[name] == value);
      }));
    },

    each: function(callback){
      for (var key in this.records)
        callback(this.records[key]);
    },

    all: function(){
      return this.cloneArray(this.recordsValues());
    },

    first: function(){
      var record = this.recordsValues()[0];
      return(record && record.clone());
    },

    last: function(){
      var values = this.recordsValues()
      var record = values[values.length - 1];
      return(record && record.clone());
    },

    count: function(){
      return this.recordsValues().length;
    },

    deleteAll: function(){
      for (var key in this.records)
        delete this.records[key];
    },

    destroyAll: function(){
      for (var key in this.records)
        this.records[key].destroy();
    },

    update: function(id, atts){
      this.find(id).updateAttributes(atts);
    },

    create: function(atts){
      var record = this.init(atts);
      return record.save();
    },

    destroy: function(id){
      this.find(id).destroy();
    },

    sync: function(callback){
      this.bind("change", callback);
    },

    fetch: function(callbackOrParams){
      typeof(callbackOrParams) == "function" ? 
        this.bind("fetch", callbackOrParams) : 
          this.trigger("fetch", callbackOrParams);
    },

    toJSON: function(){
      return this.recordsValues();
    },
    
    fromJSON: function(objects){
      if ( !objects ) return;
      if ( typeof objects == "string" )
        objects = JSON.parse(objects)
      if ( isArray(objects) ) {
        var results = [];
        for (var i=0; i < objects.length; i++)
          results.push(this.init(objects[i]));
        return results;
      } else {
        return this.init(objects);
      }
    },

    // Private

    recordsValues: function(){
      var result = [];
      for (var key in this.records)
        result.push(this.records[key]);
      return result;
    },

    cloneArray: function(array){
      var result = [];
      for (var i=0; i < array.length; i++)
        result.push(array[i].clone());
      return result;
    },

	// added by ayush
	logAll: function() {
		for(var i = 0; i < this.all().length; i++) {
			var e = this.all()[i];
			if(window.console) console.log(e.toString());
		}
	}

  });

  Model.include({
    model: true,
    newRecord: true,

    init: function(atts){
      if (atts) this.load(atts);
      this.trigger("init", this);
    },

    isNew: function(){
      return this.newRecord;
    },
    
    isValid: function(){
      return(!this.validate());
    },

    validate: function(){ },

    load: function(atts){
      for(var name in atts)
        this[name] = atts[name];
    },

    attributes: function(){
      var result = {};
      for (var i=0; i < this.parent.attributes.length; i++) {
        var attr = this.parent.attributes[i];
        result[attr] = this[attr];
      }
      result.id = this.id;
      return result;
    },

    eql: function(rec){
      return(rec && rec.id === this.id && 
             rec.parent === this.parent);
    },

    save: function(){
      var error = this.validate();
      if ( error ) {
        this.trigger("error", this, error);
        return false;
      }
      
      this.trigger("beforeSave", this);
      this.newRecord ? this.create() : this.update();
      this.trigger("save", this);
      return this;
    },

    updateAttribute: function(name, value){
      this[name] = value;
      return this.save();
    },

    updateAttributes: function(atts){
      this.load(atts);
      return this.save();
    },
    
    destroy: function(){
      this.trigger("beforeDestroy", this);
      delete this.parent.records[this.id];
      this.destroyed = true;
      this.trigger("destroy", this);
      this.trigger("change", this, "destroy");
    },

    dup: function(){
      var result = this.parent.init(this.attributes());
      result.newRecord = this.newRecord;
      return result;
    },
    
    clone: function(){
      return Object.create(this);
    },

    reload: function(){
      if ( this.newRecord ) return this;
      var original = this.parent.find(this.id);
      this.load(original.attributes());
      return original;
    },

    toJSON: function(){
      return(this.attributes());
    },
    
    exists: function(){
      return(this.id && this.id in this.parent.records);
    },

    // Private

    update: function(){
      this.trigger("beforeUpdate", this);
      var records = this.parent.records;
      records[this.id].load(this.attributes());
      var clone = records[this.id].clone();
      this.trigger("update", clone);
      this.trigger("change", clone, "update");
    },

    create: function(){
      this.trigger("beforeCreate", this);
      if ( !this.id ) this.id = Spine.guid();
      this.newRecord   = false;
      var records      = this.parent.records;
      records[this.id] = this.dup();
      var clone        = records[this.id].clone();
      this.trigger("create", clone);
      this.trigger("change", clone, "create");
    },
    
    bind: function(events, callback){
      return this.parent.bind(events, this.proxy(function(record){
        if ( record && this.eql(record) )
          callback.apply(this, arguments);
      }));
    },
    
    trigger: function(){
      return this.parent.trigger.apply(this.parent, arguments);
    }
  });
  
  // Controllers
  
  var eventSplitter = /^(\w+)\s*(.*)$/;
  
  var Controller = Spine.Controller = Class.create({
    tag: "div",
    
    initialize: function(options){
      this.options = options;

      for (var key in this.options)
        this[key] = this.options[key];

      if (!this.el) this.el = document.createElement(this.tag);
      this.el = $(this.el);

      if ( !this.events ) this.events = this.parent.events;
      if ( !this.elements ) this.elements = this.parent.elements;

      if (this.events) this.delegateEvents();
      if (this.elements) this.refreshElements();
      if (this.proxied) this.proxyAll.apply(this, this.proxied);
    },
        
    $: function(selector){
      return $(selector, this.el);
    },
        
    delegateEvents: function(){
      for (var key in this.events) {
        var methodName = this.events[key];
        var method     = this.proxy(this[methodName]);

        var match      = key.match(eventSplitter);
        var eventName  = match[1], selector = match[2];

        if (selector === '') {
          this.el.bind(eventName, method);
        } else {
          this.el.delegate(selector, eventName, method);
        }
      }
    },
    
    refreshElements: function(){
      for (var key in this.elements) {
        this[this.elements[key]] = this.$(key);
      }
    },
    
    delay: function(func, timeout){
      setTimeout(this.proxy(func), timeout || 0);
    }
  });
  
  Controller.include(Events);
  Controller.include(Log);
  
  Spine.App = Class.create();
  Spine.App.extend(Events);
  Controller.fn.App = Spine.App;
})();