Source: longo.js

/*
 * @project Longo.js
 * @module longo
 * @desc Asynchronous local database with a flavor of FRP.
 * @requires {@linkplain http://underscorejs.org/|underscore}
 * @example
 * <caption>Longo needs some files. Root path(`Longo.LONGOROOT`) to longo components is defined as `/Longo.js`.<br>
 * If you use other path on your http(s) server, you need adjust change root path of Longo.</caption>
 * <script type="text/javascript" src="path/to/Longo.js/lib/underscore-min.js"></script>
 * <script type="text/javascript" src="path/to/Longo.js/longo.js"></script>
 * <script>
 *   // Adjust root directory.
 *   Longo.setRoot("path/to/Longo.js");
 * <script>
 *
 * @see https://github.com/georgeOsdDev/Longo
 *
 * @license   The MIT License (MIT)
 * @copyright Copyright (c) 2014 Takeharu Oshida <georgeosddev@gmail.com>
 */

// For command line test
if (typeof module !== "undefined" && module.exports) {
  /*jshint -W079 */
  var _ = require("underscore");
  var Promise = require("promise");
  var EventEmitter = require("events").EventEmitter;
  /*jshint +W079 */
}

(function(global, _, EventEmitter, undefined) {
  "use strict";

  var wnd = global;

  /**
   * @namespace Longo
   */
  var Longo = global.Longo = {};

  /**
   * VERSION of module
   * @memberof Longo
   * @constant
   * @type {String}
   */
  Longo.VERSION = "0.1.0";

  /**
   * The root path for Longo components
   * This value is automatically updated on window onload event.<br>
   * If you want to access DB object before window onload, <br>
   * be sure that set correct path with `setRoot`.
   * @see Longo.setRoot
   * @memberof Longo
   * @type {String}
   * @default "/Longo.js"
   *
   */
  Longo.LONGOROOT = "/Longo.js";

  /**
   * If you are using longo.min.js,<br>
   * this value will be automatically replaced with `longoWorker.min.js`
   * @see Longo.useMinified
   *
   * @memberof Longo
   * @type {String}
   * @default "longoWorker.js"
   */
  Longo.WORKERJS = "longoWorker.js";

  /**
   * You can change this value with `debug`,`info`,`warn`,`error`.
   * @see Longo.setLogLevel
   * @memberof Longo
   * @type {String}
   * @default "warn"
   */
  Longo.LOGLEVEL = "warn";

  /**
   * @name Longo.Status
   * @namespace
   */
  var Status = Longo.Status = {
    /**
     * CREATED
     * @memberof Longo.Status
     * @type {Number}
     * @constant
     */
    "CREATED": 0,
    /**
     * STARTED
     * @memberof Longo.Status
     * @type {Number}
     * @constant
     */
    "STARTED": 1,
    /**
     * STOPPED
     * @memberof Longo.Status
     * @type {Number}
     * @constant
     */
    "STOPPED": 2,
    /**
     * DELETED
     * @memberof Longo.Status
     * @type {Number}
     * @constant
     */
    "DELETED": 3
  };

  /**
   * @name Longo.Error
   * @class Longo.Error
   * @param {Number} code    error code
   * @param {String} message error message
   * @param {Object} stack   error stack trace
   * @private
   * @constructs
   * @extends Error
   */
  Longo.Error = function(code, message, stack) {
    this.code = code;
    this.message = message;
    this.stack = stack || (new Error().stack);
  };

  function parseError(obj) {
    return new Longo.Error(obj.code, obj.message, obj.stack);
  }

  /**
   * UNEXPECTED_ERROR
   * @memberof Longo.Error
   * @constant
   */
  Longo.Error.UNEXPECTED_ERROR = 1;
  /**
   * EXECUTION_ERROR
   * @memberof Longo.Error
   * @constant
   */
  Longo.Error.EXECUTION_ERROR = 2;
  /**
   * WEBWORKER_ERROR
   * @memberof Longo.Error
   * @constant
   */
  Longo.Error.WEBWORKER_ERROR = 3;
  /**
   * INVALID_QUERY
   * @memberof Longo.Error
   * @constant
   */
  Longo.Error.INVALID_QUERY = 4;
  /**
   * COLLECTION_NOT_FOUND
   * @memberof Longo.Error
   * @constant
   */
  Longo.Error.COLLECTION_NOT_FOUND = 5;
  /**
   * COLLECTION_ALREADY_EXISTS
   * @memberof Longo.Error
   * @constant
   */
  Longo.Error.COLLECTION_ALREADY_EXISTS = 6;
  /**
   * COLLECTION_NOT_STARTED
   * @memberof Longo.Error
   * @constant
   */
  Longo.Error.COLLECTION_NOT_STARTED = 7;
  /**
   * DUPLICATE_KEY_ERROR
   * @memberof Longo.Error
   * @constant
   */
  Longo.Error.DUPLICATE_KEY_ERROR = 8;
  /**
   * DOCUMENT_NOT_FOUND
   * @memberof Longo.Error
   * @constant
   */
  Longo.Error.DOCUMENT_NOT_FOUND = 9;
  /**
   * MOD_ID_NOT_ALLOWED
   * @memberof Longo.Error
   * @constant
   */
  Longo.Error.MOD_ID_NOT_ALLOWED = 10;
  /**
   * NOT_SUPPOETRD
   * @memberof Longo.Error
   * @constant
   */
  Longo.Error.NOT_SUPPOETRD = 11;
  /**
   * EVAL_ERROR
   * @memberof Longo.Error
   * @constant
   */
  Longo.Error.EVAL_ERROR = 12;
  /**
   * INVALID_MODIFIER_SPECIFIED
   * @memberof Longo.Error
   * @constant
   */
  Longo.Error.INVALID_MODIFIER_SPECIFIED = 13;
  /**
   * PARSE_ERROR
   * @memberof Longo.Error
   * @constant
   */
  Longo.Error.PARSE_ERROR = 14;

  Longo.ErrorCds = _.invert(Longo.Error);


  /**
   * Utility methods of Longo.<br>
   * These methods are also available from application.
   * @namespace
   * @name Longo.Utils
   * @memberof Longo
   * @static
   */
  var Utils = Longo.Utils = {

    /**
     * Decode Uint16Array to String
     * @see Longo.Utils.str2ab
     * @see http://www.html5rocks.com/en/tutorials/webgl/typed_arrays/#toc-transferables
     * @see http://updates.html5rocks.com/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
     * @function
     * @memberof Longo.Utils
     * @static
     * @param {Uint16Array} buf Uint16Array
     * @return {String} str decoded string
     */
    ab2str: function(buf) {
      return String.fromCharCode.apply(null, new Uint16Array(buf));
    },

    /**
     * Encode String to Uint16Array for zero-copy messageing
     * @see Longo.Utils.ab2str
     * @see http://www.html5rocks.com/en/tutorials/webgl/typed_arrays/#toc-transferables
     * @see http://updates.html5rocks.com/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
     * @function
     * @memberof Longo.Utils
     * @static
     * @param {String} str target string
     * @return {Uint16Array} buf encoded Uint16Array
     */
    str2ab: function(str) {
      var buf = new ArrayBuffer(str.length * 2); // 2 bytes for each char
      var bufView = new Uint16Array(buf);
      for (var i = 0, strLen = str.length; i < strLen; i++) {
        bufView[i] = str.charCodeAt(i);
      }
      return bufView.buffer;
    },

    /**
     * Browser-friendly inheritance fully compatible with standard node.js inherits<br>
     * This method have Side-effect
     * @see https://github.com/isaacs/inherits
     * @function
     * @memberof Longo.Utils
     * @static
     * @param {Function} ctor constructor
     * @return {Function} superCtor constructor of superClass
     */
    inherits: function(ctor, superCtor) {
      ctor._super = superCtor;
      ctor.prototype = Object.create(superCtor.prototype, {
        constructor: {
          value: ctor,
          enumerable: false,
          writable: true,
          configurable: true
        }
      });
    },

    /**
     * return prefixed logger.
     * @function
     * @memberof Longo.Utils
     * @static
     * @param {prefix}
     * @return {Object} logger
     */
    createLogger: function(prefix, loglevel) {
      var lookup = {
        "log": 4,
        "debug": 3,
        "info": 2,
        "warn": 1,
        "error": 0,
      };
      var _logger = {
        log: function() {},
        debug: function() {},
        info: function() {},
        warn: function() {},
        error: function() {}
      };
      var level = lookup[loglevel + "".toLowerCase()] || 5;
      if (!global.console) return _logger;

      if (level > 3 && global.console.log && global.console.log.bind) {
        _logger.log = (function() {
          return global.console.log.bind(global.console, prefix);
        })();
      }

      if (level > 2 && global.console.debug && global.console.debug.bind) {
        _logger.debug = (function() {
          return global.console.debug.bind(global.console, prefix);
        })();
      }

      if (level > 1 && global.console.info && global.console.info.bind) {
        _logger.info = (function() {
          return global.console.info.bind(global.console, prefix);
        })();
      }

      if (level > 0 && global.console.warn && global.console.warn.bind) {
        _logger.warn = (function() {
          return global.console.warn.bind(global.console, prefix);
        })();
      }

      if (global.console.error && global.console.error.bind) {
        _logger.error = (function() {
          return global.console.error.bind(global.console, prefix);
        })();
      }
      return _logger;
    },

    /**
     * Do nothing
     * @function
     * @memberof Longo.Utils
     * @static
     * @return undefined
     */
    noop: function() {
      return void 0;
    },


    /**
     * return noop as function.
     * @function
     * @memberof Longo.Utils
     * @static
     * @return {Function} Longo.Utils.noop
     */
    asNoop: function() {
      return Utils.noop;
    },

    /**
     * Alias for `Array.prototype.slice.apply`.
     * @function
     * @memberof Longo.Utils
     * @static
     * @param {Object} arguments argument object
     * @return {Array} result
     */
    aSlice: function(obj) {
      return Array.prototype.slice.apply(obj);
    },

    /**
     * return input as Array.
     * @function
     * @memberof Longo.Utils
     * @static
     * @param {Object} val
     * @return {Array} result
     */
    toArray: function(obj) {
      return _.isArray(obj) ? obj : [obj];
    },

    /**
     * return true if input is not `null` or `undefined`.
     * @function
     * @memberof Longo.Utils
     * @static
     * @param {Object} val
     * @return {Boolean} result
     */
    existy: function(val) {
      return val !== null && val !== undefined;
    },

    /**
     * return true if input is not `null` or `undefined` or `false`<br>
     * `0`, `-1`, `""` is detected as truthy
     * @function
     * @memberof Longo.Utils
     * @static
     * @param {Object} val
     * @return {Boolean} result
     */
    truthy: function(val) {
      return (val !== false) && Utils.existy(val);
    },

    /**
     * return true if input is the `true`
     * @function
     * @memberof Longo.Utils
     * @static
     * @param {Object} val
     * @return {Boolean} result
     */
    isTrue: function(val) {
      return val === true;
    },

    /**
     * return true if input is the `false`
     * @function
     * @memberof Longo.Utils
     * @static
     * @param {Object} val
     * @return {Boolean} result
     */
    isFalse: function(val) {
      return val === false;
    },

    /**
     * return true if input is less than 0
     * @function
     * @memberof Longo.Utils
     * @static
     * @param {Object} val
     * @return {Boolean} result
     */
    isNegativeNum: function(val) {
      return _.isNumber(val) && val < 0;
    },

    /**
     * return true if input is the 0
     * @function
     * @memberof Longo.Utils
     * @static
     * @param {Object} val
     * @return {Boolean} result
     */
    isZero: function(val) {
      return val === 0;
    },

    /**
     * return true if input is the 1
     * @function
     * @memberof Longo.Utils
     * @static
     * @param {Object} val
     * @return {Boolean} result
     */
    isOne: function(val) {
      return val === 1;
    },

    /**
     * return true if input is greater than 0
     * @function
     * @memberof Longo.Utils
     * @static
     * @param {Object} val
     * @return {Boolean} result
     */
    isPositiveNum: function(val) {
      return _.isNumber(val) && val > 0;
    },

    /**
     * execute action with values when condition is truthy
     * @function
     * @memberof Longo.Utils
     * @static
     * @param {Boolean} cond
     * @param {Function} action
     * @param {Array} values
     * @param {Object} context
     * @return {Any} result
     */
    doWhen: function(cond, action, values, context) {
      var arr = Utils.toArray(values);
      if (Utils.truthy(cond))
        return action.apply(context, arr);
      else
        return Utils.noop();
    },

    /**
     * execute action with values when condition is truthy else execute alternative
     * @function
     * @memberof Longo.Utils
     * @static
     * @param {Boolean} cond
     * @param {Function} action
     * @param {Function} alternative
     * @param {Array} values
     * @param {Object} context
     * @return {Any} result
     */
    doWhenOrElse: function(cond, action, alternative, values, context) {
      var arr = Utils.toArray(values);
      if (Utils.truthy(cond))
        return action.apply(context, arr);
      else
        return alternative.apply(context, arr);
    },

    /**
     * return input if input is existy else return els
     * @function
     * @memberof Longo.Utils
     * @static
     * @param {Object} val
     * @param {Object} els
     * @return {Any} result
     */
    getOrElse: function(val, els) {
      return Utils.existy(val) ? val : els;
    },

    /**
     * return val if result of input has been evaluated by predictor is truthy else return els
     * @function
     * @memberof Longo.Utils
     * @static
     * @param {Object} val
     * @param {Object} els
     * @param {Function} pred predictor
     * @return {Any} result
     */
    checkOrElse: function(val, els, pred) {
      return Utils.truthy(pred(val)) ? val : els;
    },

    /**
     * Try parse string to JSON object
     * @function
     * @memberof Longo.Utils
     * @static
     * @param {String} str JSON formated string
     * @return {Array} result A Tuple `[error, parsed]`
     */
    tryParseJSON: function(str) {
      var result = [null, null];
      try {
        result[1] = JSON.parse(str);
      } catch (e) {
        result[0] = new Longo.Error(Longo.Error.PARSE_ERROR, "Failed to parse: " + str, e.stack);
      }
      return result;
    },


    /**
     * Generate objectId
     * @function
     * @memberof Longo.Utils
     * @static
     * @param {String} [id=null]
     * @return {String} objectId if id is specified return that id
     */
    objectId: (function(){
      var lastNow = Date.now();
      var seq = 0;
      var CHARS = "abcdefghijklmnopqrstuvwxyz0123456789".split("");
      return function(id) {
        if(id) return id;
        var n = Date.now();
        if (n === lastNow) {
          seq++;
          n = n + "" + Number(seq+"", 16);
        } else {
          lastNow = n;
          seq = 0;
          n = n + "" + seq;
        }
        return (n + _.shuffle(CHARS).join("")).substr(0,24);
      };
    })(),
    /**
     * Parse id to `Date` object
     * @function
     * @memberof Longo.Utils
     * @static
     * @param {String} id start with timestamp
     * @return {Date} result
     */
    dataFromId: function(id) {
      return new Date(Number(id.substr(0, 13)));
    },

    /**
     * Return uuid like random value
     * @function
     * @memberof Longo.Utils
     * @static
     * @return {String} uuid uuid like random value
     */
    uuid: (function() {
      var s4 = function() {
        return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
      };
      return function() {
        return s4() + s4() + s4() + s4();
      };
    })(),

    /**
     * Return promise object
     * @function
     * @memberof Longo.Utils
     * @static
     * @param {function} f This function should call `done` and `reject`.
     * @return {Promise} promise thenable object
     */
    defer: function(f) {
      return new Promise(function(done, reject) {
        return f(done, reject);
      });
    },

    clone: function(obj) {
      if (null === obj || "object" !== typeof obj) return obj;
      var copy = obj.constructor();
      for (var attr in obj) {
        if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
      }
      return copy;
    },

    deepClone: function(obj) {
      if (null === obj || "object" !== typeof obj) return obj;
      var copy = obj.constructor();
      for (var attr in obj) {
        if (obj.hasOwnProperty(attr)) {
          if (typeof obj[attr] === "object") {
            copy[attr] = Utils.deepClone(obj[attr]);
          } else {
            copy[attr] = obj[attr];
          }
        }
      }
      return copy;
    },

    nextTick: function(func){
      setTimeout(func,0);
    }

  };

  Utils.inherits(Longo.Error, Error);

  /**
   * Longo use dom based EventEmitter by default.<br>
   * For better performance, please use Wolfy87's EventEmitter implementation.<br>
   * @see https://github.com/Wolfy87/EventEmitter
   * @class EventEmitter
   */
  if (!EventEmitter) {
    // Inner Classes
    EventEmitter = function() {
      this.dom = wnd.document.createDocumentFragment();
      this.listners = {};
    };

    /**
     * @method
     * @memberof EventEmitter
     * @param {String} type A string representing the event type to listen for.
     * @param {Function} listner The object that receives a notification when an event of the specified type occurs.
     * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget.addEventListener
     */
    EventEmitter.prototype.addEventListener = function() {
      this.dom.addEventListener.apply(this.dom, Longo.Utils.aSlice(arguments));
    };

    /**
     * @method
     * @memberof EventEmitter
     * @param {String} type A string representing the event type being removed.
     * @param {Function} listner The listener parameter indicates the EventListener function to be removed.
     * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget.removeEventListener
     */
    EventEmitter.prototype.removeEventListener = function() {
      this.dom.removeEventListener.apply(this.dom, Longo.Utils.aSlice(arguments));
    };

    /**
     * @method
     * @memberof EventEmitter
     * @param {String} type dusoatch event type
     * @param {Object} data dispatch parameter
     * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget.dispatchEvent
     * @see https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent
     */
    EventEmitter.prototype.dispatchEvent = function() {
      var args = Longo.Utils.aSlice(arguments);
      if (!args[0]) return;
      if (args[0].constructor.name !== "Event" || args[0].constructor.name !== "CustomEvent") {
        var detail = (args[1] && args[1].detail) ? args[1].detail : args[1];
        var cev = new CustomEvent(args[0].toString(), {
          detail: detail
        });
        args[0] = cev;
      }
      this.dom.dispatchEvent.apply(this.dom, args);
    };
    // alias

    /**
     * Alias for EventEmitter.addEventListener
     * @method
     * @memberof EventEmitter
     */
    EventEmitter.prototype.on = EventEmitter.prototype.addEventListener;
    /**
     * Alias for EventEmitter.addEventListener
     * @method
     * @memberof EventEmitter
     */
    EventEmitter.prototype.bind = EventEmitter.prototype.addEventListener;
    /**
     * Alias for EventEmitter.removeEventListener
     * @method
     * @memberof EventEmitter
     */
    EventEmitter.prototype.off = EventEmitter.prototype.removeEventListener;
    /**
     * Alias for EventEmitter.removeEventListener
     * @method
     * @memberof EventEmitter
     */
    EventEmitter.prototype.unbind = EventEmitter.prototype.removeEventListener;
    /**
     * Alias for EventEmitter.dispatchEvent
     * @method
     * @memberof EventEmitter
     */
    EventEmitter.prototype.emit = EventEmitter.prototype.dispatchEvent;
    /**
     * Alias for EventEmitter.dispatchEvent
     * @method
     * @memberof EventEmitter
     */
    EventEmitter.prototype.trigger = EventEmitter.prototype.dispatchEvent;

    if (global.console && global.console.warn) {
      global.console.warn(["[WARN]:EventEmitter is not imported.",
        "Longo use dom based EventEmitter by default.",
        "For better performance, please use Wolfy87's EventEmitter implementation.",
        "https://github.com/Wolfy87/EventEmitter"
      ].join(" "));
    }
  }
  Longo.EventEmitter = EventEmitter;

  /**
   * Return Database instance.<br>
   * It is better to create database instance with {@link Longo.createDB} instead of `new` keyword.
   * @name Longo.DB
   * @class Database object
   * @param {String} name name of this database
   * @constructs
   * @private
   * @extends EventEmitter
   * @example
   * var db = Longo.createDB("test");
   */
  var DB = Longo.DB = function(name) {
    EventEmitter.call(this);
    this.name = name;
    this.lastError = null;
    this.currentOpId = null;
    this.collections = {};
    this.logger = Longo.Utils.createLogger("Longo." + name, Longo.LOGLEVEL);
  };
  Longo.Utils.inherits(DB, EventEmitter);

  /**
   * return array of collection names
   * @method
   * @memberof Longo.DB
   * @return {Array} names An array containing all collections in the existing database
   */
  Longo.DB.prototype.getCollectionNames = function() {
    return _.keys(this.collections);
  };

  /**
   * Returns a {@link Collection}
   * @method
   * @memberof Longo.DB
   * @param {String} name The name of the collection
   * @return {Collection}
   */
  Longo.DB.prototype.getCollection = function(name) {
    return this.collections[name] || this.collection(name);
  };

  /**
   * Creates a new {@link Collection} explicitly<br>
   * Because Longo creates a collection implicitly when the collection is first referenced in a command<br>
   * this method is used primarily for creating new capped collections.<br>
   * @method
   * @memberof Longo.DB
   * @param {String}  [name='temp'] The name of the collection to create.
   * @param {Object}  [option] Configuration options for creating a capped collection.
   * @param {Boolean} [option.capped = false] Enables a capped collection. To create a capped collection, specify true.
   *                                          If you specify true, you must also set a maximum size in the size field.
   * @param {Number}  [option.size = 1024*1024] Specifies a maximum size in bytes for a capped collection.
   *                                            The size field is required for capped collections.
   *                                            If capped is false, you can use this field will be ignored.
   * @param {Number}  [option.max = 1000] The maximum number of documents allowed in the capped collection.
   *                                      The size limit takes precedence over this limit.
   *                                      If a capped collection reaches its maximum size before it reaches the maximum number of documents,
   *                                      Longo removes old documents.
   *                                      If you prefer to use this limit, ensure that the size limit, which is required,
   *                                      is sufficient to contain the documents limit.
   *                                      If capped is false, you can use this field will be ignored.
   * @return {Collection} collection
   */
  Longo.DB.prototype.createCollection = function(name, option) {
    return this._createCollectionWithData(name, option);
  };

  /**
   * @private
   */
  Longo.DB.prototype._createCollectionWithData = function(name, option, dataset) {
    var cname = Utils.getOrElse(name, "temp") + "";
    var opt = Utils.getOrElse(option, {});
    var coll = new Collection(cname, opt, this);
    coll._initialize(dataset);
    this.collections[cname] = coll;
    return coll;
  };

  /**
   * Return {@link Collection} instance<br>
   * You can use this method as a start of cursor function chain.<br>
   * If specified name of collection is not exist, Longo create new {@link Collection}.<br>
   * And when new collection is created,<br>
   * Longo search parsistant data from LocalStorage and initialize with that data.<br>
   *
   * @method
   * @memberof Longo.DB
   * @param {String} [name='temp'] The name of the collection to create.
   * @example
   *   var db = Longo.use("School");
   *   db.collection("students").save([{"name":"longo"},{"name":"mongo"},{"name":"underscore"}).done();
   *   db.collection("students").find({}).sort({"name":1}).limit(1).done();
   *
   */
  Longo.DB.prototype.collection = function(name) {
    var cname, lastData, initData;

    cname = Utils.getOrElse(name, "temp") + "";
    if (this.collections[cname]) return this.collections[cname];
    lastData = Utils.tryParseJSON((localStorage.getItem("Longo:" + this.name + ":" + cname)));
    initData = Utils.toArray(Utils.getOrElse(lastData[1], []));
    return this._createCollectionWithData(name, {}, initData);
  };

  /**
   * Removes the current database.
   * @method
   * @memberof Longo.DB
   */
  Longo.DB.prototype.dropDatabase = function() {
    _.each(_.values(this.collections), function(coll) {
      coll.drop();
    });
    Longo._dropDB(this.name);
  };


  /**
   * Callback structure for {@link Longo.DB#cloneCollection}
   * @callback Longo.DB.cloneCollectionCallback
   * @param {Longo.Error} [error=null]      null when success
   * @param {Collection}  [collection=null] null when fail
   */

  /**
   * Create clone from existing {@link Collection}
   * @method
   * @memberof Longo.DB
   * @param  {String}   from          Collection name of Collection instance that holds dataset to copy.
   * @param  {String}   [name='temp'] Collection name that you want to clone.
   * @param  {Object}   [query={}]    A standard query document that limits the documents copied as part of the db.cloneCollection() operation.
   *                             All query selectors available to the find() are available here.
   * @param  {cloneCollectionCallback} [done=null] callback for result. See {@link Longo.DB.cloneCollectionCallback}
   * @return {String}   opId     id of this action
   */
  Longo.DB.prototype.cloneCollection = function(from, name, query, done) {
    var self = this;
    var cname = Utils.getOrElse(name, "temp") + "";
    if (!_.isFunction(done)) done = Utils.noop;

    if (this.collections[cname]) {
      var error = new Longo.Error(Longo.Error.COLLECTION_ALREADY_EXISTS, "Collection is already exists! name: " + cname, null);
      self.lastError = error;
      return done(error, null);
    }
    if (!this.collections[from]) return done(null, this.createCollection(cname));

    var cloneCb = function(error, data) {
      if (Utils.existy(error)) return done(error, null);
      return done(null, self._createCollectionWithData(cname, self.collections[from].option, data));
    };

    this.collection(from).find(query, {}).done(cloneCb);
  };

  /**
   * The db.currentOp() method can take no arguments, and it return just opId unlike MongoDB's currentOp
   * @method
   * @memberof Longo.DB
   * @return {String} currentOpId A last opId of this database instance.
   */
  Longo.DB.prototype.currentOp = function() {
    return this.currentOpId;
  };

  /**
   * Return last error message of this database instance
   * @method
   * @memberof Longo.DB
   * @return {String} lastError The last error message string
   */
  Longo.DB.prototype.getLastError = function() {
    if (!this.lastError) return null;
    return this.lastError.message;
  };

  /**
   * Return last error object of this database instance
   * @method
   * @memberof Longo.DB
   * @return {Error} lastError A full document with status information
   */
  Longo.DB.prototype.getLastErrorObj = function() {
    return this.lastError;
  };

  /**
   * Terminates an operation as specified by the operation ID.<br>
   * To find operations and their corresponding IDs, See {@link Longo.DB#currentOp}
   * @method
   * @memberof Longo.DB
   * @param {String} opId current opperation ID
   */
  Longo.DB.prototype.killOp = function(opId) {
    var tokens = opId.split[":"];
    if (this.collections[tokens[0]]) {
      delete this.collections[tokens[0]].cbs[tokens[1]];
      delete this.collections[tokens[0]].observers[tokens[1]];
    }
  };

  /**
   * Return the current database name.
   * @method
   * @memberof Longo.DB
   * @return {Error} name The current database name
   */
  Longo.DB.prototype.getName = function() {
    return this.name;
  };


  /**
   * Callback structure for {@link Longo.Collection}
   * @callback Longo.Collection.collectionCallback
   * @param {Longo.Error} [error=null]  null when success
   * @param {Array}       [result=null] null when fail
   */

  /**
   * Do not call this class with `new` from application.<br>
   * use {@link Longo.DB#createCollection} or {@link Longo.DB#collection} method.
   * @name Longo.Collection
   * @class Collection object. Most collection methods return {@link Longo.Cursor} object.<br>
   *        In longo inspite of MongoDB, Cursor object does not have and reference to data.<br>
   *        You need to call `{@link Longo.Cursor#done}` or `{@link Longo.Cursor#onValue}` or `{@link Longo.Cursor#assign}`
   *        or `{@link Longo.Cursor#promise}` at the end of method chain.
   * @param {String} [name='temp'] name of this collection
   * @param {Object} [option] config for capped collection
   * @param {Boolean} [option.capped = false] See {@link Longo.DB#createCollection}
   * @param {Number} [option.size = 1024*1024] See {@link Longo.DB#createCollection}
   * @param {Number} [option.max = 1000] See {@link Longo.DB#createCollection}
   * @param {DB} parent database instance
   * @constructs
   * @private
   * @extends EventEmitter
   * @example
   *   var db = Longo.createDB("test");
   *   db.createCollection("score");
   */
  var Collection = Longo.Collection = function(name, opt, db) {
    EventEmitter.call(this);
    var self = this;
    this.name = name;
    this.option = {
      "capped": opt.capped || false,
      "size": opt.size || 1024 * 1024,
      "max": opt.max || 1000,
      "storeFunction": false
    };
    this.db = db;
    this.logger = Utils.createLogger("Longo." + this.db.name + "." + this.name, Longo.LOGLEVEL);
    this.cbs = {
      "-1": function(error) {
        self.logger.error([
            "Error:Failed to detect specified callback.",
            "If you want to handle this error with your own listener,",
            "Use `db.collection('name').setDefaultErrorHandler(listner);`"
          ].join(""),
          error);
      }
    };
    this.observers = {};

    var worker = this.worker = new Worker(Longo.getRoot() + "/" + Longo.WORKERJS);
    this.worker.postMessage = worker.webkitPostMessage || worker.postMessage;

    worker.addEventListener("message", this._onMessage(), false);
    worker.addEventListener("error", this._onError(), false);
    this.status = Status.STARTED;
  };
  Utils.inherits(Collection, EventEmitter);

  /**
   * Return collection is capped.<br>
   * @method
   * @memberof Longo.Collection
   * @return {Boolean} isCapped
   */
  Longo.Collection.prototype.isCapped = function() {
    return this.option.capped;
  };

  /**
   * Set your own error handler.<br>
   * By default, Longo just log when some error happen.<br>
   * @method
   * @memberof Longo.Collection
   * @param {Function} func your error handler
   */
  Longo.Collection.prototype.setDefaultErrorHandler = function(func) {
    this.cbs["-1"] = func;
  };

  /**
   * Save collection snapshot to LocalStorage.<br>
   * If dataset in this collection changed, persisted data will be updated.<br>
   * @see {@link Longo.Collection#parsistOnce}
   * @method
   * @memberof Longo.Collection
   * @param {Function} func your error handler
   */
  Longo.Collection.prototype.parsist = function() {
    var self = this;
    this.find({}).onValue(function(e, result) {
      localStorage.setItem("Longo:" + self.db.name + ":" + self.name, JSON.stringify(result));
    });
  };

  /**
   * Save collection snapshot to LocalStorage.<br>
   * @see {@link Longo.Collection#parsist}
   * @method
   * @memberof Longo.Collection
   * @param {Function} func your error handler
   */
  Longo.Collection.prototype.parsistOnce = function() {
    var self = this;
    this.find({}).done(function(e, result) {
      localStorage.setItem("Longo:" + self.db.name + ":" + self.name, JSON.stringify(result));
    });
  };
  /**
   * This method handle message from WebWorker
   * @private
   */
  Longo.Collection.prototype._onMessage = function() {
    var self = this;
    return function(e) {
      var response, data, seq, error, result;
      response = Utils.tryParseJSON(Utils.ab2str(e.data));
      self.logger.info("Response Received", response);

      if (Utils.existy(response[0])) {
        error = parseError(response[0]);
        self.db.lastError = error;
        self.db.emit("error", error);
        self.emit("error", error);
        return Utils.nextTick(function(){
          self.logger.error("ERROR:Failed to parse WebWorker message", Longo.ErrorCds[error.code]);
        });
      }
      data = response[1] || {};
      seq = data.seq || "-1";
      result = data.result;

      if (Utils.existy(data.error)) {
        error = parseError(data.error);
        self.db.lastError = error;
        self.db.emit("error", error);
        self.emit("error", error);
        self.logger.error("ERROR:Failed at worker", error);
        return Utils.nextTick(function(){
          Utils.doWhen(_.isFunction(self.cbs[seq]), self.cbs[seq], [error, null]);
        });
      }

      if (data.isUpdated) {
        _.each(_.values(self.observers), function(ob) {
          if(_.isFunction(ob.func)) self._send(ob.message, ob.func, false);
        });
        self.emit("updated");
      }
      self.logger.info("Trigger callback: ", self.name + ":" + seq);
      Utils.nextTick(function(){
        Utils.doWhen(_.isFunction(self.cbs[seq]), self.cbs[seq], [null, result]);
      });
    };
  };

  /**
   * This method handle error from WebWorker
   * @private
   */
  Longo.Collection.prototype._onError = function() {
    var self = this;
    return function(e) {
      if (!Utils.existy(e.code)) e.code = Longo.Error.WEBWORKER_ERROR;
      self.db.lastError = e;
      self.db.emit("error", e);
      self.emit("error", e);
      self.logger.error(["Error:WebWorker Error!  Line ", e.lineno, " in ", e.filename, ": ", e.message].join(""));
      e.preventDefault();
      return self.cbs["-1"](e);
    };
  };

  /**
   * This method initialize collection with initial dataset
   * @private
   * @param [Array] dataset
   */
  Longo.Collection.prototype._initialize = function(dataset) {
    var _dataset = Utils.checkOrElse(dataset, [], _.isArray);
    var msg = {
      "cmds": [{
        "cmd": "start",
        "name": this.name,
        "option": this.option,
        "dataset": _dataset
      }]
    };
    this._send(msg);
  };

  /**
   * This method return seaquence
   * @private
   */
  Longo.Collection.prototype._getNextSeq = function() {
    var seq = 0;
    var nextSeq = function() {
      seq++;
      return seq + "";
    };
    this._getNextSeq = nextSeq;
    return seq + "";
  };

  /**
   * This method send message to WebWorker
   * @private
   * @param [Object] message
   * @param [Function] cb
   * @param [Boolean] observe
   */
  Longo.Collection.prototype._send = function(message, cb, observe) {
    var seq, callbackFunc, json, bytes, opId;
    callbackFunc = Utils.checkOrElse(cb, Utils.noop, _.isFunction);

    if (this.status !== Status.STARTED) return callbackFunc(Longo.Error.COLLECTION_NOT_STARTED, null);

    seq = this._getNextSeq();
    this.cbs[seq] = callbackFunc;
    message.seq = seq;

    if (this.option.storeFunction) {
      //http://stackoverflow.com/questions/5264916/convert-javascript-object-incl-functions-to-string
    }

    json = JSON.stringify(message);

    // Zero-Copy transfer
    // http://updates.html5rocks.com/2011/12/Transferable-Objects-Lightning-Fast
    bytes = Utils.str2ab(json);
    this.worker.postMessage(bytes, [bytes]);
    this.logger.info("New operation called: " + this.name + ":" + seq);

    if (observe) {
      this.observers[seq] = {
        "message": message,
        "func": callbackFunc,
      };
      this.logger.info("New observer registerd: " + this.name + ":" + seq);
    }

    opId = this.name + ":" + seq;
    this.db.currentOpId = opId;
    return opId;
  };

  /**
   * Selects documents in a collection and returns Cursor object.
   * @method
   * @memberof Longo.Collection
   * @param {Object} [query={}] Specifies selection query using query operators.
   *                               To return all documents in a collection, omit this parameter or pass an empty document ({}).
   *                               Specifies the fields to return using projection operators.
   * @param {Object} [projection]  To return all fields in the matching document, omit this parameter.
   * @return {Cursor} cursor Cursor object
   */
  Longo.Collection.prototype.find = function(query, projection) {
    var cmds = [{
      "cmd": "find",
      "query": query
    }, {
      "cmd": "project",
      "projection": projection
    }];
    return new Cursor(this, cmds);
  };

  /**
   * Returns one document that satisfies the specified query query.
   * If multiple documents satisfy the query, this method returns the first document according to the natural order
   * which reflects the order of documents on the memory.
   * In capped collections, natural order is the same as insertion order.
   *
   * @method
   * @memberof Longo.Collection
   * @param {Object} [query={}] Specifies selection query using query operators.
   *                               To return all documents in a collection, omit this parameter or pass an empty document ({}).
   *                               Specifies the fields to return using projection operators.
   * @param {Object} [projection]  To return all fields in the matching document, omit this parameter.
   * @return {Cursor} cursor Cursor object
   */
  Longo.Collection.prototype.findOne = function(query, projection) {
    var cmds = [{
      "cmd": "find",
      "query": query
    }, {
      "cmd": "project",
      "projection": projection
    }, {
      "cmd": "limit",
      "value": 1
    }];
    return new Cursor(this, cmds);
  };

  Longo.Collection.prototype.aggregate = function(pipeline) {
    var key,
      cursor = new Cursor(this, []);
    _.each(pipeline, function(op) {
      key = _.keys(op)[0];
      switch (key) {
      case "$project":
        cursor = cursor.project(op[key]);
        break;
      case "$match":
        cursor = cursor.match(op[key]);
        break;
      case "$skip":
        cursor = cursor.skip(op[key]);
        break;
      case "$unwind":
        cursor = cursor.unwind(op[key]);
        break;
      case "$group":
        cursor = cursor.group(op[key]);
        break;
      case "$sort":
        cursor = cursor.sort(op[key]);
        break;
      default:
        //noop
      }
    });
    return cursor;
  };

  /**
   * Updates an existing document or inserts a new document, depending on its document parameter.
   *
   * Only JSON formattable elements are supported. So `function` and `Regex` are not stored in the collection.
   *
   * <b>Insert</b><br>
   * If the document does not contain an _id field, then the save() method performs an insert().<br>
   * During the operation, the longo will create an ObjectId and assign it to the _id field.<br>
   *
   * <b>Upsert</b><br>
   * If the document contains an _id field, then the save() method performs an update with upsert, querying by the _id field.<br>
   * If a document does not exist with the specified _id value, the save() method performs an insert.<br>
   * If a document exists with the specified _id value,<br>
   * the save() method performs an update that replaces all fields in the existing document with the fields from the document.<br>
   *
   * @method
   * @memberof Longo.Collection
   * @param {Object} doc A document to save to the collection.
   * @return {Cursor} cursor Cursor object
   */
  Longo.Collection.prototype.save = function(doc) {
    var cmd = {
      "cmd": "save",
      "doc": doc,
    };
    return new Cursor(this, cmd);
  };

  /**
   * Inserts a document or documents into a collection.
   * Different from MongoDB, this method does not accept second parameter for option.
   *
   * Only JSON formattable elements are supported. So `function` and `Regex` are not stored in the collection.
   *
   * @method
   * @memberof Longo.Collection
   * @param {Object | Array} document A document or array of documents to insert into the collection.
   * @return {Cursor} cursor Cursor object
   */
  Longo.Collection.prototype.insert = function(document) {
    var cmd = {
      "cmd": "insert",
      "doc": Utils.toArray(document)
    };
    return new Cursor(this, cmd);
  };

  /**
   * Removes documents from a collection.
   * @method
   * @memberof Longo.Collection
   * @param {Object} query Specifies deletion query using query operators.
   *                       To delete all documents in a collection, pass an empty document ({}).
   * @param {Boolean} [justOne] To limit the deletion to just one document, set to true.
   *                            Omit to use the default value of false and delete all documents matching the deletion query.
   * @return {Cursor} cursor Cursor object
   */
  Longo.Collection.prototype.remove = function(query, justOne) {
    var cmd = {
      "cmd": "remove",
      "query": query,
      "justOne": justOne
    };
    return new Cursor(this, cmd);
  };

  /**
   * Modifies an existing document or documents in a collection.<br>
   * The method can modify specific fields of existing document or documents or replace an existing document entirely,<br>
   * depending on the update parameter.<br>
   * By default, the update() method updates a single document.<br>
   * Set the Multi Parameter to update all documents that match the query criteria.<br>
   * @method
   * @memberof Longo.Collection
   * @param {Object} query The selection criteria for the update.
   *                       Use the same query selectors as used in the find() method.
   * @param {Object} update The modifications to apply.
   * @param {Object} [option] option.
   * @param {Boolean} [option.upsert = false] If set to true, creates a new document when no document matches the query criteria.
   *                                          which does not insert a new document when no match is found.
   * @param {Boolean} [option.multi = false] If set to true, updates multiple documents that meet the query criteria.
   *                                         If set to false, updates one document.
   * @return {Cursor} cursor Cursor object
   */
  Longo.Collection.prototype.update = function(query, update, option) {
    var cmd = {
      "cmd": "update",
      "query": query,
      "update": update,
      "option": option
    };
    return new Cursor(this, cmd);
  };

  /**
   * Drop collection from database.<br>
   * @method
   * @memberof Longo.Collection
   * @param {Function} [cb] callback
   * @return promise if cb was null
   */
  Longo.Collection.prototype.drop = function(cb) {
    this.emit("drop");
    this.worker.terminate();
    this.cbs = {};
    this.observers = {};
    delete this.db.collections[this.name];
    localStorage.setItem("Longo:" + this.db.name + ":" + this.name, []);
    if (_.isFunction(cb)) return cb();
    return Utils.defer(function(done){
      done();
    });
  };

  /**
   * Returns the count of documents that would match a {@link Longo.Collection#find} query.
   * @method
   * @memberof Longo.Collection
   * @param {Object} query The query selection query.
   * @return {Cursor} cursor Cursor object
   */
  Longo.Collection.prototype.count = function(query) {
    var cmds = [{
      "cmd": "find",
      "query": query
    }, {
      "cmd": "count",
    }];
    return new Cursor(this, cmds);
  };

  /**
   * Return the size of the collection.
   * @method
   * @memberof Longo.Collection
   * @return {Cursor} cursor Cursor object
   */
  Longo.Collection.prototype.dataSize = function() {
    var cmds = [{
      "cmd": "size",
    }];
    return new Cursor(this, cmds);
  };

  /**
   * Return the total size of the data in the collection.
   * As same as {@link Longo.Collection#dataSize}
   * @method
   * @memberof Longo.Collection
   * @return {Cursor} cursor Cursor object
   */
  Longo.Collection.prototype.totalSize = Longo.Collection.prototype.dataSize;

  /**
   * Copies all documents from collection into newCollection.<br>
   * If newCollection does not exist, MongoDB creates it.
   * {@link Longo.Collection.collectionCallback} will be called with the number of documents copied.<br>
   * If the copy fails, it will be called with the error.
   * @method
   * @memberof Longo.Collection
   * @param {String} newCollection Name of destination Collection.
   * @param {Object} query The query selection query.
   * @param  {collectionCallback} [done=null] callback for result. See {@link Longo.Collection.collectionCallback}
   * @return {String} opId
   */
  Longo.Collection.prototype.copyTo = function(newCollection, done) {
    var self = this;
    return this.db.cloneCollection(self.name, newCollection + "", {}, function(error, newColl) {
      if (error) {
        return self.find({}).done(function(error2) {
          if (error2) return done(error2);
          return self.db.collection(newCollection + "").count().dene(done);
        });
      } else {
        return newColl.count().done(done);
      }
    });
  };

  /**
   * Changes the name of an existing collection
   * This method have side effect
   * @method
   * @memberof Longo.Collection
   * @param {String} The new name of the collection.
   */
  Longo.Collection.prototype.renameCollection = function(name) {
    this.db.collections[name] = this;
    delete this.db.collections[this.name];
    this.name = name;
    this.logger = Utils.createLogger("Longo." + this.db.name + "." + this.name, Longo.LOGLEVEL);
  };

  // @TODO
  Longo.Collection.prototype.distinct = function() {};
  Longo.Collection.prototype.findAndModify = function() {};
  Longo.Collection.prototype.group = function() {};
  Longo.Collection.prototype.mapReduce = function() {};



  /**
   * Callback structure for {@link Longo.Cursor}
   * @callback Longo.Cursor.cursorCallback
   * @param {Longo.Error} [error=null]  null when success
   * @param {Array}       [result=null] null when fail
   */

  /**
   * Do not call this class with `new` from application.<br>
   * @name Longo.Cursor
   * @class Cursor object with command stack.<br>
   * Cursor itself does not have, reference to result dataset.<br>
   * All stacked command will never triggerd until receiver method will be called.<br>
   * Asynchronously ,result dataset will be passed to receiver.<br>
   * @see {@link Longo.Cursor.done}
   * @see {@link Longo.Cursor.onValue}
   * @see {@link Longo.Cursor.assign}
   * @see {@link Longo.Cursor.promise}
   * @constructs
   * @private
   */
  var Cursor = Longo.Cursor = function() {
    var args = Utils.aSlice(arguments);
    if (args[0] instanceof Cursor) {
      this.collection = args[0].collection;
      this.cmds = args[0].cmds;
      this.wrapCb = args[0].wrapCb;

      this.cmds.push(Utils.getOrElse(args[1], {}));
      if (_.isFunction(args[2])) this.wrapCb.push(args[2]);
    } else {
      this.collection = args[0];
      this.cmds = Utils.toArray(args[1]);
      this.wrapCb = Utils.toArray(Utils.getOrElse(args[2], Utils.noop));
    }
  };

  /**
   * Handle result set of query with Node.js callback style receiver
   * @method
   * @memberof Longo.Cursor
   * @param  {cursorCallback} cb callback for result. See {@link Longo.Cursor.cursorCallback}
   * @return {String} opId operation id
   */
  Longo.Cursor.prototype.done = function(cb) {
    var self,
      message,
      callback,
      userCallback = Utils.checkOrElse(cb, Utils.noop, _.isFunction);
    self = this;
    message = {
      "cmds": this.cmds
    };
    callback = function() {
      var args = Utils.aSlice(arguments);
      _.invoke(self.wrapCb, "call");
      userCallback.apply(null, args);
    };

    return this.collection._send(message, callback);
  };

  /**
   * Handle result set of query with Node.js callback style receiver<br>
   * And observe collection with same query, callback will be executed when collection changed.
   * @method
   * @memberof Longo.Cursor
   * @param  {cursorCallback} cb callback for result. See {@link Longo.Cursor.cursorCallback}
   * @param  {Boolean} [skipDuplicates=false] skipDuplicate Set true if you do not want to receive duplicate resultset.
   * @return {String} opId operation id
   */
  Longo.Cursor.prototype.onValue = function(cb, skipDuplicates) {

    if (_.some([
      _.contains(_.keys(this.cmds), "save"),
      _.contains(_.keys(this.cmds), "insert"),
      _.contains(_.keys(this.cmds), "remove"),
      _.contains(_.keys(this.cmds), "update"),
      _.contains(_.keys(this.cmds), "drop")
    ])) {
      this.collection.logger.warn("WARN:`onValue` is not supported for 'save','insert','remove','update','drop'. call `done` instead.");
      return this.done(cb);
    }

    var self,
      message,
      callback,
      userCallback = Utils.checkOrElse(cb, Utils.noop, _.isFunction);
    self = this;
    message = {
      "cmds": this.cmds
    };

    callback = (function() {
      var cache = null;
      return function() {
        var args = Utils.aSlice(arguments);
        _.invoke(self.wrapCb, "call");
        var hash = JSON.stringify(args[1]);
        if (!skipDuplicates || !_.isEqual(cache, hash)) {
          cache = hash;
          userCallback.apply(null, args);
        }
      };
    })();

    return this.collection._send(message, callback, true);
  };

  /**
   * Assign result set to dom element with template.<br>
   * And observe collection change.<br>
   * @method
   * @memberof Longo.Cursor
   * @param  {String} elementSelector ElementSelector string or jQuery object Example: "p.myClass" , $("#myId")
   * @param  {Function} template underscore.js template
   * @return {String} opId operation id
   * @example
   * <caption>See live <a href="http://georgeosddev.github.io/longo/example/assign/" target="_blank">example</a> for more detail.<caption>
   * var db = Longo.use("example");
   * var tpl = _.template($("#resultTpl").html());
   * db.collection("output").find({}).sort({"value":-1}).assign($("#out"), tpl);
   */
  Longo.Cursor.prototype.assign = function(elementSelector, template) {
    var target, cb;
    if (!Utils.existy(elementSelector) || !Utils.existy(template)) return this.collection.logger.error("Error:Assign Result Error", new Longo.Error(Longo.Error.EXECUTION_ERROR, "invalid parameter"));

    if (_.isFunction(elementSelector.html)) {
      cb = function(error, result) {
        if (error) return this.collection.logger.error("Error:Assign Result Error", error);
        elementSelector.html(template({
          "result": result
        }));
      };
    } else {
      target = document.querySelector(elementSelector);
      cb = function(error, result) {
        if (error) return this.collection.logger.error("Error:Assign Result Error", error);
        target.innerHTML = template({
          "result": result
        });
      };
    }
    return this.onValue(cb, true);
  };


  /**
   * Return `{@linkplain https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise | Promise}` object for query.<br>
   * You can access result with `then` or error with `catch` method.<br>
   * This method does not return opId.
   * @method
   * @memberof Longo.Cursor
   * @return {Promise} promise object
   */
  Longo.Cursor.prototype.promise = function() {
    var self = this;
    return new Promise(function(resolve, reject) {
      var callback = function() {
        var args = Utils.aSlice(arguments);
        _.invoke(self.wrapCb, "call");
        if (args[0]) {
          reject(args[0]);
        } else {
          resolve(args[1]);
        }
      };
      self.collection._send({
        cmds: self.cmds
      }, callback);
    });
  };


  /**
   * Counts the number of documents referenced by a cursor.<br>
   * Append the count() method to a {@link Longo.Collection#find} query to return the number of matching documents.<br>
   * The operation does not perform the query but instead counts the results that would be returned by the query.<br>
   * Diffeer from MongoShell, `count` does not take `applySkipLimit` parameter.<br>
   * So this method is as same as {@link Longo.Cursor#size}
   * @see {@link Longo.Cursor#size}
   * @method
   * @memberof Longo.Cursor
   * @return {Cursor} cursor Cursor object
   */
  Longo.Cursor.prototype.count = function() {
    var cmd = {
      "cmd": "count"
    };
    return new Cursor(this, cmd);
  };

  /**
   * Iterates the cursor to apply a JavaScript function to each document from the cursor.<br>
   * The `func` will be evaluate at Worker Thread. You should care for limitation of WebWorker.<br>
   * If you want to work at Main-Thread, You can use simply use `_.forEach` to result set in `done` function.<br>
   * @method
   * @memberof Longo.Cursor
   * @func {Function} func javascript function
   * @return {Cursor} cursor Cursor object
   */
  Longo.Cursor.prototype.forEach = function(func) {
    var cmd = {
      "cmd": "forEach",
      "func": "return " + func.toString() + ";"
    };
    return new Cursor(this, cmd);
  };

  /**
   * Use the limit() method on a cursor to specify the maximum number of documents the cursor will return.
   * @method
   * @memberof Longo.Cursor
   * @param {Number} num limit
   * @return {Cursor} cursor Cursor object
   */
  Longo.Cursor.prototype.limit = function(num) {
    var cmd = {
      "cmd": "limit",
      "value": num
    };
    return new Cursor(this, cmd);
  };

  /**
   * Applies function to each document visited by the cursor<br>
   * and collects the return values from successive application into an array.<br>
   * The `func` will be evaluate at Worker Thread. You should care for limitation of WebWorker.<br>
   * If you want to work at Main-Thread, You can use simply use `_.map` to result set in `done` function.<br>
   * @method
   * @memberof Longo.Cursor
   * @func {Function} func javascript function
   * @return {Cursor} cursor Cursor object
   */
  Longo.Cursor.prototype.map = function(func) {
    var cmd = {
      "cmd": "map",
      "func": "return " + func.toString() + ";"
    };
    return new Cursor(this, cmd);
  };

  /**
   * Specifies the exclusive upper bound for a specific field in order to constrain the results of {@link Longo.Collection#find}().<br>
   * max() provides a way to specify an upper bound on compound field.
   * @see {@link Longo.Cursor#min}
   * @method
   * @memberof Longo.Cursor
   * @param {Object} indexBounds The exclusive upper bound for the field
   * @return {Cursor} cursor Cursor object
   */
  Longo.Cursor.prototype.max = function(indexBounds) {
    var cmd = {
      "cmd": "max",
      "indexBounds": indexBounds || {}
    };
    return new Cursor(this, cmd).limit(1);
  };

  /**
   * Specifies the inclusive lower bound for a specific field in order to constrain the results of {@link Longo.Collection#find}().<br>
   * min() provides a way to specify lower bounds on compound field.
   * @see {@link Longo.Cursor#max}
   * @method
   * @memberof Longo.Cursor
   * @param {Object} indexBounds The exclusive lower bound for the field
   * @return {Cursor} cursor Cursor object
   */
  Longo.Cursor.prototype.min = function(indexBounds) {
    var cmd = {
      "cmd": "min",
      "indexBounds": indexBounds || {}
    };
    return new Cursor(this, cmd).limit(1);
  };

  /**
   * A count of the number of documents that match the {@link Longo.Collection#find}() query<br>
   * after applying any cursor.skip() and cursor.limit() methods.
   * @method
   * @memberof Longo.Cursor
   * @see {@link Longo.Cursor#count}
   * @return {Cursor} cursor Cursor object
   */
  Longo.Cursor.prototype.size = function() {
    var cmd = {
      "cmd": "count"
    };
    return new Cursor(this, cmd);
  };

  /**
   * Call the cursor.skip() method on a cursor to control where MongoDB begins returning results.<br>
   * This approach may be useful in implementing `paged` results.
   * @method
   * @memberof Longo.Cursor
   * @param {Number} num the number to skip
   * @return {Cursor} cursor Cursor object
   */
  Longo.Cursor.prototype.skip = function(num) {
    var cmd = {
      "cmd": "skip",
      "value": num
    };
    return new Cursor(this, cmd);
  };

  /**
   * Specifies the order in which the query returns matching documents. You must apply sort().
   * You can use sorter in two style.<br>
   * 1.field and value pairs: in this style, value must be `1` or `-1`
   * 2.function: the function which will be applyed to result set as `_.sort(dataset, sorter)`
   * @method
   * @memberof Longo.Cursor
   * @param {Object | Function} sorter see avobe
   * @return {Cursor} cursor Cursor object
   */
  Longo.Cursor.prototype.sort = function(sorter) {
    var cmd = {
      "cmd": "sort",
      "sorter": sorter
    };
    if (_.isFunction(sorter)) cmd.sorter = "return " + sorter.toString() + ";";
    return new Cursor(this, cmd);
  };

  /**
   * @private
   */
  Longo.Cursor.prototype.match = function(query) {
    var cmd = {
      "cmd": "find",
      "query": query
    };
    return new Cursor(this, cmd);
  };

  /**
   * @private
   */
  Longo.Cursor.prototype.project = function(projection) {
    var cmd = {
      "cmd": "project",
      "projection": projection
    };
    return new Cursor(this, cmd);
  };

  /**
   * @private
   */
  Longo.Cursor.prototype.unwind = function(projection) {
    var cmd = {
      "cmd": "unwind",
      "projection": projection
    };
    return new Cursor(this, cmd);
  };

  /**
   * @private
   */
  Longo.Cursor.prototype.group = function(grouping) {
    var cmd = {
      "cmd": "group",
      "grouping": grouping
    };
    return new Cursor(this, cmd);
  };


  /**
   * Return version of Longo module
   * @name Longo.getVersion
   * @function
   * @memberof Longo
   * @public
   */
  Longo.getVersion = function() {
    return Longo.VERSION;
  };

  /**
   * Set rootPath for longo modules<br>
   * This method will be called when window `load` event fired.
   *
   * @function
   * @memberof Longo
   * @public
   * @param {String} root Abstruct path of longo root
   * @example
   * Longo.setRoot("/javascript/vender/longo");
   */
  Longo.setRoot = function(root) {
    if (typeof root !== "string") {
      root = "/Longo.js";
      var scripts = wnd.document.getElementsByTagName("script");
      var i = scripts.length;
      while (i--) {
        var match = scripts[i].src.match(/(^|.*)\/longo(\.min){0,}\.js(\?.*)?$/);
        if (match) {
          root = match[1];
          if (match[2]) Longo.WORKERJS = "longoWorker.min.js"; // use min
          if (match[3]) Longo.WORKERJS = Longo.WORKERJS + match[3]; // no cache
          break;
        }
      }
    }
    if (Longo.LONGOROOT !== root) console.info("Set LONGOROOT :" + root);
    Longo.LONGOROOT = root;
  };
  if (wnd.addEventListener) wnd.addEventListener("load", Longo.setRoot, false);

  /**
   * Return rootPath for longo modules
   * @name Longo.getRoot
   * @function
   * @memberof Longo
   * @public
   */
  Longo.getRoot = function() {
    return Longo.LONGOROOT;
  };

  /**
   * Force to use minified longoWorker module.
   * @function
   * @memberof Longo
   * @public
   */
  Longo.useMinified = function() {
    Longo.WORKERJS = "longoWorker.min.js";
  };

  /**
   * Set logging level.
   * @function
   * @memberof Longo
   * @public
   * @param {string} loglevel `log`,`debug`,`info`,`warn`,`error`
   */
  Longo.setLogLevel = function(level) {
    Longo.LOGLEVEL = level;
  };


  (function() {
    var dbs = {};

    /**
     * Return new database instance.<br>
     * If sepcifyed name database is already exists, return that database.
     * @name Longo.createDB
     * @function
     * @memberof Longo
     * @public
     * @param {String} [name='temp'] database name
     * @return {DB} db
     */
    Longo.createDB = function(name) {
      var dname = Utils.getOrElse(name, "temp") + "";
      var db;
      if (_.has(dbs, dname)) return dbs[dname];
      db = new DB(dname);
      dbs[dname] = db;
      return db;
    };

    /**
     * @private
     */
    Longo._dropDB = function(name) {
      if (dbs[name]) delete dbs[name];
    };

  })();

  /**
   * Alias for Longo.createDB().
   * @name Longo.use
   * @function
   * @memberof Longo
   * @param {String} name database name
   * @return {DB} db
   * @example
   * var db = Longo.use("School");
   * db.collection("students").insert({name:"tome",score:100}).done();
   */
  Longo.use = Longo.createDB;

  // export for test
  if (typeof exports !== "undefined") {
    if (typeof module !== "undefined" && module.exports) {
      module.exports = Longo;
    }
    exports.Longo = Longo;
  } else {
    global.Longo = Longo;
  }

})(this, _, (typeof EventEmitter !== undefined) ? EventEmitter : undefined);