var command = function(spec, my) {
    var that = {};
    spec = spec || {};
    my = my || {};
    // Attributes //
    var priv = {};
    priv.commandlist = {
        'post':postCommand,
        'put':putCommand,
        'get':getCommand,
        'remove':removeCommand,
        'allDocs':allDocsCommand,
        'putAttachment':putAttachmentCommand
    };
    // creates the good command thanks to his label
    if (spec.label && priv.commandlist[spec.label]) {
        priv.label = spec.label;
        delete spec.label;
        return priv.commandlist[priv.label](spec, my);
    }

    priv.tried     = 0;
    priv.doc       = spec.doc || {};
    priv.doc._id   = priv.doc._id || generateUuid();
    priv.docid     = spec.docid || '';

    // xxx fixed spec.content to spec.doc.content for PUTATTACHMENT
    // xxx need extra check for GET, otherwise spec.doc is undefined
    priv.content   = spec.doc === undefined ? undefined :
        typeof spec.doc.content === 'string'?
                spec.doc.content:
                undefined;
    priv.option    = spec.options || {};
    priv.callbacks = spec.callbacks || {};
    priv.success   = priv.callbacks.success || function (){};
    priv.error     = priv.callbacks.error || function (){};
    priv.retry     = function() {
        that.error({
            status:13,statusText:'Fail Retry',error:'fail_retry',
            message:'Impossible to retry.',reason:'Impossible to retry.'
        });
    };
    priv.end       = function() {};
    priv.on_going  = false;

    // Methods //
    /**
     * Returns a serialized version of this command.
     * Override this function.
     * @method serialized
     * @return {object} The serialized command.
     */
    that.serialized = function() {
        return {label:that.getLabel(),
                tried:priv.tried,
                doc:that.cloneDoc(),
                option:that.cloneOption()};
    };

    /**
     * Returns the label of the command.
     * @method getLabel
     * @return {string} The label.
     */
    that.getLabel = function() {
        return 'command';
    };

    that.getDocId = function () {
        return (priv.docid || priv.doc._id).split('/')[0];
    };
    that.getAttachmentId = function () {
        return (priv.docid || priv.doc._id).split('/')[1];
    };
    that.getContent = function () {
        return priv.content;
    };

    /**
     * Returns an information about the document.
     * @method getDocInfo
     * @param  {string} infoname The info name.
     * @return The info value.
     */
    that.getDocInfo = function (infoname) {
        return priv.doc[infoname];
    };

    /**
     * Returns the value of an option.
     * @method getOption
     * @param  {string} optionname The option name.
     * @return The option value.
     */
    that.getOption = function (optionname) {
        return priv.option[optionname];
    };

    /**
     * Validates the storage.
     * @param  {object} storage The storage.
     */
    that.validate = function (storage) {
        if (!(priv.docid || priv.doc._id).match(/^[^\/]+([\/][^\/]+)?$/)) {
            that.error({
                status:21,statusText:'Invalid Document Id',
                error:'invalid_document_id',
                message:'The document id must be like "abc" or "abc/def".',
                reason:'The document id is no like "abc" or "abc/def"'
            });
            return false;
        }
        if (!that.validateState()) {
            return false;
        }
        return storage.validate();
    };

    /*
     * Extend this function
     */
    that.validateState = function() {
        return true;
    };

    that.canBeRetried = function () {
        return (typeof priv.option.max_retry === 'undefined' ||
                priv.option.max_retry === 0 ||
                priv.tried < priv.option.max_retry);
    };
    that.getTried = function() {
        return priv.tried;
    };

    /**
     * Delegate actual excecution the storage.
     * @param {object} storage The storage.
     */
    that.execute = function(storage) {
        if (!priv.on_going) {
            if (that.validate (storage)) {
                priv.tried ++;
                priv.on_going = true;
                storage.execute (that);
            }
        }
    };

    /**
     * Execute the good method from the storage.
     * Override this function.
     * @method executeOn
     * @param  {object} storage The storage.
     */
    that.executeOn = function(storage) {};

    that.success = function(return_value) {
        priv.on_going = false;
        priv.success (return_value);
        priv.end(doneStatus());
    };

    that.retry = function (return_error) {
        priv.on_going = false;
        if (that.canBeRetried()) {
            priv.retry();
        } else {
            that.error (return_error);
        }
    };

    that.error = function(return_error) {
        priv.on_going = false;
        priv.error(return_error);
        priv.end(failStatus());
    };

    that.end = function () {
        priv.end(doneStatus());
    };

    that.onSuccessDo = function (fun) {
        if (fun) {
            priv.success = fun;
        } else {
            return priv.success;
        }
    };

    that.onErrorDo = function (fun) {
        if (fun) {
            priv.error = fun;
        } else {
            return priv.error;
        }
    };

    that.onEndDo = function (fun) {
        priv.end = fun;
    };

    that.onRetryDo = function (fun) {
        priv.retry = fun;
    };

    /**
     * Is the command can be restored by another JIO : yes.
     * @method canBeRestored
     * @return {boolean} true
     */
    that.canBeRestored = function() {
        return true;
    };

    /**
     * Clones the command and returns it.
     * @method clone
     * @return {object} The cloned command.
     */
    that.clone = function () {
        return command(that.serialized(), my);
    };

    /**
     * Clones the command options and returns the clone version.
     * @method cloneOption
     * @return {object} The clone of the command options.
     */
    that.cloneOption = function () {
        var k, o = {};
        for (k in priv.option) {
            o[k] = priv.option[k];
        }
        return o;
    };

    /**
     * Clones the document and returns the clone version.
     * @method cloneDoc
     * @return {object} The clone of the document.
     */
    that.cloneDoc = function () {
        if (priv.docid) {
            return priv.docid;
        }
        var k, o = {};
        for (k in priv.doc) {
            o[k] = priv.doc[k];
        }
        return o;
    };

    return that;
};