/*jslint indent: 2, maxlen: 80, nomen: true */
/*global define, jIO, jio_tests, hex_sha256, window, test, ok, deepEqual, sinon,
  expect */

// define([module_name], [dependencies], module);
(function (dependencies, module) {
  "use strict";
  if (typeof define === 'function' && define.amd) {
    return define(dependencies, module);
  }
  module(jIO, jio_tests, hex_sha256);
}(['jio', 'jio_tests', 'sha256', 'revisionstorage'], function (
  jIO,
  util,
  hex_sha256
) {
  "use strict";

  //////////////////////////////////////////////////////////////////////////////
  // Tools

  /**
   * Clones all native object in deep. Managed types: Object, Array, String,
   * Number, Boolean, Function, null.
   *
   * @param  {A} object The object to clone
   * @return {A} The cloned object
   */
  function deepClone(object) {
    var i, cloned;
    if (Array.isArray(object)) {
      cloned = [];
      for (i = 0; i < object.length; i += 1) {
        cloned[i] = deepClone(object[i]);
      }
      return cloned;
    }
    if (typeof object === "object") {
      cloned = {};
      for (i in object) {
        if (object.hasOwnProperty(i)) {
          cloned[i] = deepClone(object[i]);
        }
      }
      return cloned;
    }
    return object;
  }

  function generateTools() {
    return {
      clock: sinon.useFakeTimers(),
      spy: util.ospy,
      tick: util.otick
    };
  }

  function generateRevisionHash(doc, revisions, deleted_flag) {
    var string;
    doc = deepClone(doc);
    delete doc._rev;
    delete doc._revs;
    delete doc._revs_info;
    string = JSON.stringify(doc) + JSON.stringify(revisions) +
      JSON.stringify(deleted_flag ? true : false);
    return hex_sha256(string);
  }

  //////////////////////////////////////////////////////////////////////////////
  // Tests

  module("Revision Storage + Local Storage");

  test("Post", function () {

    var o = generateTools();

    o.jio = jIO.newJio({
      "type": "revision",
      "sub_storage": {
        "type": "local",
        "username": "urevpost",
        "application_name": "arevpost"
      }
    });
    o.localpath = "jio/localstorage/urevpost/arevpost";

    // post without id
    o.revisions = {"start": 0, "ids": []};
    o.spy(o, "status", undefined, "Post without id");
    o.jio.post({}, function (err, response) {
      o.f.apply(arguments);
      o.uuid = (err || response).id;
      ok(util.isUuid(o.uuid), "Uuid should look like " +
         "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx : " + o.uuid);
    });
    o.tick(o);
    o.rev = "1-" + generateRevisionHash({"_id": o.uuid}, o.revisions);

    // check document
    deepEqual(
      util.jsonlocalstorage.getItem(o.localpath + "/" + o.uuid + "." + o.rev),
      {"_id": o.uuid + "." + o.rev},
      "Check document"
    );

    // check document tree
    o.doc_tree = {
      "_id": o.uuid + ".revision_tree.json",
      "children": [{
        "rev": o.rev,
        "status": "available",
        "children": []
      }]
    };
    deepEqual(
      util.jsonlocalstorage.getItem(
        o.localpath + "/" + o.uuid + ".revision_tree.json"
      ),
      o.doc_tree,
      "Check document tree"
    );

    // post non empty document
    o.doc = {"_id": "post1", "title": "myPost1"};
    o.rev = "1-" + generateRevisionHash(o.doc, o.revisions);
    o.spy(o, "value", {"ok": true, "id": "post1", "rev": o.rev}, "Post");
    o.jio.post(o.doc, o.f);
    o.tick(o);

    // check document
    o.doc._id = "post1." + o.rev;
    deepEqual(
      util.jsonlocalstorage.getItem(o.localpath + "/post1." + o.rev),
      o.doc,
      "Check document"
    );

    // check document tree
    o.doc_tree._id = "post1.revision_tree.json";
    o.doc_tree.children[0] = {
      "rev": o.rev,
      "status": "available",
      "children": []
    };
    deepEqual(
      util.jsonlocalstorage.getItem(
        o.localpath + "/post1.revision_tree.json"
      ),
      o.doc_tree,
      "Check document tree"
    );

    // post and document already exists
    o.doc = {"_id": "post1", "title": "myPost2"};
    o.rev = "1-" + generateRevisionHash(o.doc, o.revisions);
    o.spy(o, "value", {
      "ok": true,
      "id": "post1",
      "rev": o.rev
    }, "Post and document already exists");
    o.jio.post(o.doc, o.f);
    o.tick(o);

    // check document
    o.doc._id = "post1." + o.rev;
    deepEqual(
      util.jsonlocalstorage.getItem(o.localpath + "/post1." + o.rev),
      o.doc,
      "Check document"
    );

    // check document tree
    o.doc_tree._id = "post1.revision_tree.json";
    o.doc_tree.children.unshift({
      "rev": o.rev,
      "status": "available",
      "children": []
    });
    deepEqual(
      util.jsonlocalstorage.getItem(
        o.localpath + "/post1.revision_tree.json"
      ),
      o.doc_tree,
      "Check document tree"
    );

    // post + revision
    o.doc = {"_id": "post1", "_rev": o.rev, "title": "myPost2"};
    o.revisions = {"start": 1, "ids": [o.rev.split('-')[1]]};
    o.rev = "2-" + generateRevisionHash(o.doc, o.revisions);
    o.spy(o, "value", {"ok": true, "id": "post1", "rev": o.rev},
           "Post + revision");
    o.jio.post(o.doc, o.f);
    o.tick(o);

    // // keep_revision_history
    // ok (false, "keep_revision_history Option Not Implemented");

    // check document
    o.doc._id = "post1." + o.rev;
    delete o.doc._rev;
    deepEqual(
      util.jsonlocalstorage.getItem(o.localpath + "/post1." + o.rev),
      o.doc,
      "Check document"
    );

    // check document tree
    o.doc_tree._id = "post1.revision_tree.json";
    o.doc_tree.children[0].children.unshift({
      "rev": o.rev,
      "status": "available",
      "children": []
    });
    deepEqual(
      util.jsonlocalstorage.getItem(
        o.localpath + "/post1.revision_tree.json"
      ),
      o.doc_tree,
      "Check document tree"
    );

    // add attachment
    o.doc._attachments = {
      "attachment_test": {
        "length": 35,
        "digest": "A",
        "content_type": "oh/yeah"
      }
    };
    util.jsonlocalstorage.setItem(o.localpath + "/post1." + o.rev, o.doc);
    util.jsonlocalstorage.setItem(
      o.localpath + "/post1." + o.rev + "/attachment_test",
      "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
    );

    // post + attachment copy
    o.doc = {"_id": "post1", "_rev": o.rev, "title": "myPost2"};
    o.revisions = {
      "start": 2,
      "ids": [o.rev.split('-')[1], o.revisions.ids[0]]
    };
    o.rev = "3-" + generateRevisionHash(o.doc, o.revisions);
    o.spy(o, "value", {"ok": true, "id": "post1", "rev": o.rev},
           "Post + attachment copy");
    o.jio.post(o.doc, o.f);
    o.tick(o);

    // check attachment
    deepEqual(
      util.jsonlocalstorage.getItem(o.localpath + "/post1." + o.rev +
                                    "/attachment_test"),
      "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "Check Attachment"
    );

    // check document tree
    o.doc_tree._id = "post1.revision_tree.json";
    o.doc_tree.children[0].children[0].children.unshift({
      "rev": o.rev,
      "status": "available",
      "children": []
    });
    deepEqual(
      util.jsonlocalstorage.getItem(
        o.localpath + "/post1.revision_tree.json"
      ),
      o.doc_tree,
      "Check document tree"
    );

    // post + wrong revision
    o.doc = {"_id": "post1", "_rev": "3-wr3", "title": "myPost3"};
    o.revisions = {"start": 3, "ids": ["wr3"]};
    o.rev = "4-" + generateRevisionHash(o.doc, o.revisions);
    o.spy(o, "value", {"id": "post1", "ok": true, "rev": o.rev},
          "Post + wrong revision");
    o.jio.post(o.doc, o.f);
    o.tick(o);

    // check document
    deepEqual(
      util.jsonlocalstorage.getItem(o.localpath + "/post1.3-wr3"),
      null,
      "Check document"
    );

    // check document
    o.doc._id = "post1." + o.rev;
    delete o.doc._rev;
    deepEqual(
      util.jsonlocalstorage.getItem(o.localpath + "/post1." + o.rev),
      o.doc,
      "Check document"
    );

    // check document tree
    o.doc_tree._id = "post1.revision_tree.json";
    o.doc_tree.children.unshift({
      "rev": "3-wr3",
      "status": "missing",
      "children": [{
        "rev": o.rev,
        "status": "available",
        "children": []
      }]
    });
    deepEqual(
      util.jsonlocalstorage.getItem(
        o.localpath + "/post1.revision_tree.json"
      ),
      o.doc_tree,
      "Check document tree"
    );

    util.closeAndcleanUpJio(o.jio);
  });

  test("Put", function () {

    var o = generateTools();

    o.jio = jIO.newJio({
      "type": "revision",
      "sub_storage": {
        "type": "local",
        "username": "urevput",
        "application_name": "arevput"
      }
    });
    o.localpath = "jio/localstorage/urevput/arevput";

    // put without id
    // error 20 -> document id required
    o.spy(o, "status", 20, "Put without id");
    o.jio.put({}, o.f);
    o.tick(o);

    // put non empty document
    o.doc = {"_id": "put1", "title": "myPut1"};
    o.revisions = {"start": 0, "ids": []};
    o.rev = "1-" + generateRevisionHash(o.doc, o.revisions);
    o.spy(o, "value", {"ok": true, "id": "put1", "rev": o.rev},
           "Creates a document");
    o.jio.put(o.doc, o.f);
    o.tick(o);

    // check document
    o.doc._id = "put1." + o.rev;
    deepEqual(
      util.jsonlocalstorage.getItem(o.localpath + "/put1." + o.rev),
      o.doc,
      "Check document"
    );

    // check document tree
    o.doc_tree = {
      "_id": "put1.revision_tree.json",
      "children": [{
        "rev": o.rev,
        "status": "available",
        "children": []
      }]
    };
    deepEqual(
      util.jsonlocalstorage.getItem(
        o.localpath + "/put1.revision_tree.json"
      ),
      o.doc_tree,
      "Check document tree"
    );

    // put without rev and document already exists
    o.doc = {"_id": "put1", "title": "myPut2"};
    o.rev = "1-" + generateRevisionHash(o.doc, o.revisions);
    o.spy(o, "value", {"ok": true, "id": "put1", "rev": o.rev},
           "Put same document without revision");
    o.jio.put(o.doc, o.f);
    o.tick(o);

    o.doc_tree.children.unshift({
      "rev": o.rev,
      "status": "available",
      "children": []
    });

    // put + revision
    o.doc = {"_id": "put1", "_rev": o.rev, "title": "myPut2"};
    o.revisions = {"start": 1, "ids": [o.rev.split('-')[1]]};
    o.rev = "2-" + generateRevisionHash(o.doc, o.revisions);
    o.spy(o, "value", {"id": "put1", "ok": true, "rev": o.rev},
           "Put + revision");
    o.jio.put(o.doc, o.f);
    o.tick(o);

    // check document
    o.doc._id = "put1." + o.rev;
    delete o.doc._rev;
    deepEqual(
      util.jsonlocalstorage.getItem(o.localpath + "/put1." + o.rev),
      o.doc,
      "Check document"
    );

    // check document tree
    o.doc_tree.children[0].children.unshift({
      "rev": o.rev,
      "status": "available",
      "children": []
    });
    deepEqual(
      util.jsonlocalstorage.getItem(
        o.localpath + "/put1.revision_tree.json"
      ),
      o.doc_tree,
      "Check document tree"
    );

    // put + wrong revision
    o.doc = {"_id": "put1", "_rev": "3-wr3", "title": "myPut3"};
    o.revisions = {"start": 3, "ids": ["wr3"]};
    o.rev = "4-" + generateRevisionHash(o.doc, o.revisions);
    o.spy(o, "value", {"id": "put1", "ok": true, "rev": o.rev},
           "Put + wrong revision");
    o.jio.put(o.doc, o.f);
    o.tick(o);

    // check document
    o.doc._id = "put1." + o.rev;
    delete o.doc._rev;
    deepEqual(
      util.jsonlocalstorage.getItem(o.localpath + "/put1." + o.rev),
      o.doc,
      "Check document"
    );

    // check document tree
    o.doc_tree.children.unshift({
      "rev": "3-wr3",
      "status": "missing",
      "children": [{
        "rev": o.rev,
        "status": "available",
        "children": []
      }]
    });
    deepEqual(
      util.jsonlocalstorage.getItem(
        o.localpath + "/put1.revision_tree.json"
      ),
      o.doc_tree,
      "Check document tree"
    );

    // put + revision history
    o.doc = {
      "_id": "put1",
      //"_revs": ["3-rh3", "2-rh2", "1-rh1"], // same as below
      "_revs": {"start": 3, "ids": ["rh3", "rh2", "rh1"]},
      "title": "myPut3"
    };
    o.spy(o, "value", {"id": "put1", "ok": true, "rev": "3-rh3"},
           "Put + revision history");
    o.jio.put(o.doc, o.f);
    o.tick(o);

    // check document
    o.doc._id = "put1.3-rh3";
    delete o.doc._revs;
    deepEqual(
      util.jsonlocalstorage.getItem(o.localpath + "/put1.3-rh3"),
      o.doc,
      "Check document"
    );

    // check document tree
    o.doc_tree.children.unshift({
      "rev": "1-rh1",
      "status": "missing",
      "children": [{
        "rev": "2-rh2",
        "status": "missing",
        "children": [{
          "rev": "3-rh3",
          "status": "available",
          "children": []
        }]
      }]
    });
    deepEqual(
      util.jsonlocalstorage.getItem(
        o.localpath + "/put1.revision_tree.json"
      ),
      o.doc_tree,
      "Check document tree"
    );

    // add attachment
    o.doc._attachments = {
      "att1": {
        "length": 1,
        "content_type": "text/plain",
        "digest": "md5-0cc175b9c0f1b6a831c399e269772661"
      },
      "att2": {
        "length": 2,
        "content_type": "dont/care",
        "digest": "md5-5360af35bde9ebd8f01f492dc059593c"
      }
    };
    util.jsonlocalstorage.setItem(o.localpath + "/put1.3-rh3", o.doc);
    util.jsonlocalstorage.setItem(o.localpath + "/put1.3-rh3/att1", "a");
    util.jsonlocalstorage.setItem(o.localpath + "/put1.3-rh3/att2", "bc");

    // put + revision with attachment
    o.attachments = o.doc._attachments;
    o.doc = {"_id": "put1", "_rev": "3-rh3", "title": "myPut4"};
    o.revisions = {"start": 3, "ids": ["rh3", "rh2", "rh1"]};
    o.rev = "4-" + generateRevisionHash(o.doc, o.revisions);
    o.spy(o, "value", {"id": "put1", "ok": true, "rev": o.rev},
           "Put + revision (document contains attachments)");
    o.jio.put(o.doc, o.f);
    o.tick(o);

    // check document
    o.doc._id = "put1." + o.rev;
    o.doc._attachments = o.attachments;
    delete o.doc._rev;
    deepEqual(
      util.jsonlocalstorage.getItem(o.localpath + "/put1." + o.rev),
      o.doc,
      "Check document"
    );

    // check attachments
    deepEqual(
      util.jsonlocalstorage.getItem(o.localpath + "/put1." + o.rev + "/att1"),
      "a",
      "Check Attachment"
    );
    deepEqual(
      util.jsonlocalstorage.getItem(o.localpath + "/put1." + o.rev + "/att2"),
      "bc",
      "Check Attachment"
    );

    // check document tree
    o.doc_tree.children[0].children[0].children[0].children.unshift({
      "rev": o.rev,
      "status": "available",
      "children": []
    });
    deepEqual(
      util.jsonlocalstorage.getItem(
        o.localpath + "/put1.revision_tree.json"
      ),
      o.doc_tree,
      "Check document tree"
    );

    util.closeAndcleanUpJio(o.jio);

  });

  test("Put Attachment", function () {

    var o = generateTools();

    o.jio = jIO.newJio({
      "type": "revision",
      "sub_storage": {
        "type": "local",
        "username": "urevputattmt",
        "application_name": "arevputattmt"
      }
    });

    // putAttachment without doc id
    // error 20 -> document id required
    o.spy(o, "status", 20, "PutAttachment without doc id" +
          " -> 20 document id required");
    o.jio.putAttachment({}, o.f);
    o.tick(o);

    // putAttachment without attachment id
    // erorr 22 -> attachment id required
    o.spy(o, "status", 22, "PutAttachment without attachment id" +
          " -> 22 attachment id required");
    o.jio.putAttachment({"_id": "putattmt1"}, o.f);
    o.tick(o);

    // putAttachment without document
    o.revisions = {"start": 0, "ids": []};
    o.rev_hash = generateRevisionHash({"_id": "doc1", "_attachment": "attmt1"},
                                      o.revisions);
    o.rev = "1-" + o.rev_hash;
    o.spy(o, "value",
          {"ok": true, "id": "doc1", "attachment": "attmt1", "rev": o.rev},
          "PutAttachment without document, without data");
    o.jio.putAttachment({"_id": "doc1", "_attachment": "attmt1"}, o.f);
    o.tick(o);

    // check document
    deepEqual(
      util.jsonlocalstorage.getItem(
        "jio/localstorage/urevputattmt/arevputattmt/doc1." + o.rev
      ),
      {
        "_id": "doc1." + o.rev,
        "_attachments": {
          "attmt1": {
            "length": 0,
            // md5("")
            "digest": "md5-d41d8cd98f00b204e9800998ecf8427e"
          }
        }
      },
      "Check document"
    );

    // check attachment
    deepEqual(util.jsonlocalstorage.getItem(
      "jio/localstorage/urevputattmt/arevputattmt/doc1." + o.rev
        + "/attmt1"
    ), "", "Check attachment");
    // adding a metadata to the document
    o.doc = util.jsonlocalstorage.getItem(
      "jio/localstorage/urevputattmt/arevputattmt/doc1." + o.rev
    );
    o.doc.title = "My Title";
    util.jsonlocalstorage.setItem(
      "jio/localstorage/urevputattmt/arevputattmt/doc1." + o.rev,
      o.doc
    );

    // update attachment
    o.prev_rev = o.rev;
    o.revisions = {"start": 1, "ids": [o.rev_hash]};
    o.rev_hash = generateRevisionHash({
      "_id": "doc1",
      "_data": "abc",
      "_attachment": "attmt1",
    }, o.revisions);
    o.rev = "2-" + o.rev_hash;
    o.spy(o, "value",
          {"ok": true, "id": "doc1", "attachment": "attmt1", "rev": o.rev},
          "Update Attachment, with data");
    o.jio.putAttachment({
      "_id": "doc1",
      "_data": "abc",
      "_attachment": "attmt1",
      "_rev": o.prev_rev
    }, o.f);
    o.tick(o);

    // check document
    deepEqual(
      util.jsonlocalstorage.getItem(
        "jio/localstorage/urevputattmt/arevputattmt/doc1." + o.rev
      ),
      {
        "_id": "doc1." + o.rev,
        "title": "My Title",
        "_attachments": {
          "attmt1": {
            "length": 3,
            // md5("abc")
            "digest": "md5-900150983cd24fb0d6963f7d28e17f72"
          }
        }
      },
      "Check document"
    );

    // check attachment
    deepEqual(util.jsonlocalstorage.getItem(
      "jio/localstorage/urevputattmt/arevputattmt/doc1." + o.rev +
        "/attmt1"
    ), "abc", "Check attachment");

    // putAttachment new attachment
    o.prev_rev = o.rev;
    o.revisions = {"start": 2, "ids": [o.rev_hash, o.revisions.ids[0]]};
    o.rev_hash = generateRevisionHash({
      "_id": "doc1",
      "_data": "def",
      "_attachment": "attmt2",
    }, o.revisions);
    o.rev = "3-" + o.rev_hash;
    o.spy(o, "value",
          {"ok": true, "id": "doc1", "attachment": "attmt2", "rev": o.rev},
          "PutAttachment without document, without data");
    o.jio.putAttachment({
      "_id": "doc1",
      "_data": "def",
      "_attachment": "attmt2",
      "_rev": o.prev_rev
    }, o.f);
    o.tick(o);

    // check document
    deepEqual(
      util.jsonlocalstorage.getItem(
        "jio/localstorage/urevputattmt/arevputattmt/doc1." + o.rev
      ),
      {
        "_id": "doc1." + o.rev,
        "title": "My Title",
        "_attachments": {
          "attmt1": {
            "length": 3,
            "digest": "md5-900150983cd24fb0d6963f7d28e17f72"
          },
          "attmt2": {
            "length": 3,
            // md5("def")
            "digest": "md5-4ed9407630eb1000c0f6b63842defa7d"
          }
        }
      },
      "Check document"
    );

    // check attachment
    deepEqual(util.jsonlocalstorage.getItem(
      "jio/localstorage/urevputattmt/arevputattmt/doc1." + o.rev +
        "/attmt2"
    ), "def", "Check attachment");

    util.closeAndcleanUpJio(o.jio);

  });

  test("Get", function () {

    var o = generateTools();

    o.jio = jIO.newJio({
      "type": "revision",
      "sub_storage": {
        "type": "local",
        "username": "urevget",
        "application_name": "arevget"
      }
    });
    o.localpath = "jio/localstorage/urevget/arevget";

    // get inexistent document
    o.spy(o, "status", 404, "Get inexistent document (winner)" +
          " -> 404 Not Found");
    o.jio.get({"_id": "get1"}, o.f);
    o.tick(o);

    // get inexistent attachment
    o.spy(o, "status", 404, "Get inexistent attachment (winner)" +
          " -> 404 Not Found");
    o.jio.getAttachment({"_id": "get1", "_attachment": "get2"}, o.f);
    o.tick(o);

    // adding a document
    o.doctree = {"children": [{
      "rev": "1-rev1",
      "status": "available",
      "children": []
    }]};
    o.doc_myget1 = {"_id": "get1.1-rev1", "title": "myGet1"};
    util.jsonlocalstorage.setItem(
      o.localpath + "/get1.revision_tree.json",
      o.doctree
    );
    util.jsonlocalstorage.setItem(o.localpath + "/get1.1-rev1", o.doc_myget1);

    // get document
    o.doc_myget1_cloned = deepClone(o.doc_myget1);
    o.doc_myget1_cloned._id = "get1";
    o.doc_myget1_cloned._rev = "1-rev1";
    o.doc_myget1_cloned._revisions = {"start": 1, "ids": ["rev1"]};
    o.doc_myget1_cloned._revs_info = [{
      "rev": "1-rev1",
      "status": "available"
    }];
    o.spy(o, "value", o.doc_myget1_cloned, "Get document (winner)");
    o.jio.get({"_id": "get1"}, {
      "revs_info": true,
      "revs": true,
      "conflicts": true
    }, o.f);
    o.tick(o);

    // adding two documents
    o.doctree = {"children": [{
      "rev": "1-rev1",
      "status": "available",
      "children": []
    }, {
      "rev": "1-rev2",
      "status": "available",
      "children": [{
        "rev": "2-rev3",
        "status": "available",
        "children": []
      }]
    }]};
    o.doc_myget2 = {"_id": "get1.1-rev2", "title": "myGet2"};
    o.doc_myget3 = {"_id": "get1.2-rev3", "title": "myGet3"};
    util.jsonlocalstorage.setItem(
      o.localpath + "/get1.revision_tree.json",
      o.doctree
    );
    util.jsonlocalstorage.setItem(o.localpath + "/get1.1-rev2", o.doc_myget2);
    util.jsonlocalstorage.setItem(o.localpath + "/get1.2-rev3", o.doc_myget3);

    // get document
    o.doc_myget3_cloned = deepClone(o.doc_myget3);
    o.doc_myget3_cloned._id = "get1";
    o.doc_myget3_cloned._rev = "2-rev3";
    o.doc_myget3_cloned._revisions = {"start": 2, "ids": ["rev3", "rev2"]};
    o.doc_myget3_cloned._revs_info = [{
      "rev": "2-rev3",
      "status": "available"
    }, {
      "rev": "1-rev2",
      "status": "available"
    }];
    o.doc_myget3_cloned._conflicts = ["1-rev1"];
    o.spy(o, "value", o.doc_myget3_cloned,
          "Get document (winner, after posting another one)");
    o.jio.get({"_id": "get1"},
              {"revs_info": true, "revs": true, "conflicts": true},
              o.f);
    o.tick(o);

    // get inexistent specific document
    o.spy(o, "status", 404, "Get document (inexistent specific revision)" +
          " -> 404 Not Found");
    o.jio.get({"_id": "get1", "_rev": "1-rev0"}, {
      "revs_info": true,
      "revs": true,
      "conflicts": true,
    }, o.f);
    o.tick(o);

    // get specific document
    o.doc_myget2_cloned = deepClone(o.doc_myget2);
    o.doc_myget2_cloned._id = "get1";
    o.doc_myget2_cloned._rev = "1-rev2";
    o.doc_myget2_cloned._revisions = {"start": 1, "ids": ["rev2"]};
    o.doc_myget2_cloned._revs_info = [{
      "rev": "1-rev2",
      "status": "available"
    }];
    o.doc_myget2_cloned._conflicts = ["1-rev1"];
    o.spy(o, "value", o.doc_myget2_cloned, "Get document (specific revision)");
    o.jio.get({"_id": "get1", "_rev": "1-rev2"}, {
      "revs_info": true,
      "revs": true,
      "conflicts": true,
    }, o.f);
    o.tick(o);

    // adding an attachment
    o.attmt_myget3 = {
      "get2": {
        "length": 3,
        "digest": "md5-dontcare",
        "content_type": "oh/yeah"
      }
    };
    o.doc_myget3._attachments = o.attmt_myget3;
    util.jsonlocalstorage.setItem(o.localpath + "/get1.2-rev3", o.doc_myget3);
    util.jsonlocalstorage.setItem(o.localpath + "/get1.2-rev3/get2", "abc");

    // get attachment winner
    o.spy(o, "value", "abc", "Get attachment (winner)");
    o.jio.getAttachment({"_id": "get1", "_attachment": "get2"}, o.f);
    o.tick(o);

    // get inexistent attachment specific rev
    o.spy(o, "status", 404, "Get inexistent attachment (specific revision)" +
          " -> 404 Not Found");
    o.jio.getAttachment({
      "_id": "get1",
      "_attachment": "get2",
      "_rev": "1-rev1"
    }, {
      "revs_info": true,
      "revs": true,
      "conflicts": true,
    }, o.f);
    o.tick(o);

    // get attachment specific rev
    o.spy(o, "value", "abc", "Get attachment (specific revision)");
    o.jio.getAttachment({
      "_id": "get1",
      "_attachment": "get2",
      "_rev": "2-rev3"
    }, {
      "revs_info": true,
      "revs": true,
      "conflicts": true,
    }, o.f);
    o.tick(o);

    // get document with attachment (specific revision)
    delete o.doc_myget2_cloned._attachments;
    o.spy(o, "value", o.doc_myget2_cloned,
          "Get document which have an attachment (specific revision)");
    o.jio.get({"_id": "get1", "_rev": "1-rev2"}, {
      "revs_info": true,
      "revs": true,
      "conflicts": true
    }, o.f);
    o.tick(o);

    // get document with attachment (winner)
    o.doc_myget3_cloned._attachments = o.attmt_myget3;
    o.spy(o, "value", o.doc_myget3_cloned,
          "Get document which have an attachment (winner)");
    o.jio.get({"_id": "get1"},
              {"revs_info": true, "revs": true, "conflicts": true},
              o.f);
    o.tick(o);

    util.closeAndcleanUpJio(o.jio);

  });

  test("Remove", function () {

    var o = generateTools();

    o.jio = jIO.newJio({
      "type": "revision",
      "sub_storage": {
        "type": "local",
        "username": "urevrem",
        "application_name": "arevrem"
      }
    });
    o.localpath = "jio/localstorage/urevrem/arevrem";

    // 1. remove document without revision
    o.spy(o, "status", 409, "Remove document without revision " +
          "-> 409 Conflict");
    o.jio.remove({"_id": "remove1"}, o.f);
    o.tick(o);

    // 2. remove attachment without revision
    o.spy(o, "status", 409, "Remove attachment without revision " +
          "-> 409 Conflict");
    o.jio.removeAttachment({"_id": "remove1", "_attachment": "remove2"}, o.f);
    o.tick(o);

    // adding a document with attachments
    o.doc_myremove1 = {
      "_id": "remove1.1-veryoldrev",
      "title": "myRemove1"
    };

    util.jsonlocalstorage.setItem(o.localpath + "/remove1.1-veryoldrev",
                         o.doc_myremove1);

    o.doc_myremove1._id = "remove1.2-oldrev";
    o.attachment_remove2 = {
      "length": 3,
      "digest": "md5-dontcare",
      "content_type": "oh/yeah"
    };
    o.attachment_remove3 = {
      "length": 5,
      "digest": "md5-865f5cc7fbd7854902eae9d8211f178a",
      "content_type": "he/ho"
    };
    o.doc_myremove1._attachments = {
      "remove2": o.attachment_remove2,
      "remove3": o.attachment_remove3
    };

    util.jsonlocalstorage.setItem(o.localpath + "/remove1.2-oldrev",
                         o.doc_myremove1);
    util.jsonlocalstorage.setItem(
      o.localpath + "/remove1.2-oldrev/remove2",
      "abc"
    );
    util.jsonlocalstorage.setItem(
      o.localpath + "/remove1.2-oldrev/remove3",
      "defgh"
    );

    // add document tree
    o.doctree = {
      "children": [{
        "rev": "1-veryoldrev",
        "status": "available",
        "children": [{
          "rev": "2-oldrev",
          "status": "available",
          "children": []
        }]
      }]
    };
    util.jsonlocalstorage.setItem(o.localpath + "/remove1.revision_tree.json",
                         o.doctree);

    // 3. remove inexistent attachment
    o.spy(o, "status", 404, "Remove inexistent attachment -> 404 Not Found");
    o.jio.removeAttachment({
      "_id": "remove1",
      "_attachment": "remove0",
      "_rev": "2-oldrev"
    }, o.f);
    o.tick(o);

    // 4. remove existing attachment
    o.rev_hash = generateRevisionHash({
      "_id": "remove1",
      "_attachment": "remove2",
    }, {"start": 2, "ids": ["oldrev", "veryoldrev"]});
    o.spy(o, "value", {
      "ok": true,
      "id": "remove1",
      "attachment": "remove2",
      "rev": "3-" + o.rev_hash
    }, "Remove existing attachment");
    o.jio.removeAttachment({
      "_id": "remove1",
      "_attachment": "remove2",
      "_rev": "2-oldrev"
    }, o.f);
    o.tick(o);

    o.doctree = {
      "_id": "remove1.revision_tree.json",
      "children": [{
        "rev": "1-veryoldrev",
        "status": "available",
        "children": [{
          "rev": "2-oldrev",
          "status": "available",
          "children": [{
            "rev": "3-" + o.rev_hash,
            "status": "available",
            "children": []
          }]
        }]
      }]
    };

    // 5. check if document tree has been updated correctly
    deepEqual(util.jsonlocalstorage.getItem(
      o.localpath + "/remove1.revision_tree.json"
    ), o.doctree, "Check document tree");

    // 6. check if the attachment still exists
    deepEqual(util.jsonlocalstorage.getItem(
      o.localpath + "/remove1.2-oldrev/remove2"
    ), "abc", "Check attachment -> still exists");

    // 7. check if document is updated
    deepEqual(util.jsonlocalstorage.getItem(
      o.localpath + "/remove1.3-" + o.rev_hash
    ), {
      "_id": "remove1.3-" + o.rev_hash,
      "title": "myRemove1",
      "_attachments": {"remove3": o.attachment_remove3}
    }, "Check document");

    // 8. remove document with wrong revision
    o.spy(o, "status", 409, "Remove document with wrong revision " +
          "-> 409 Conflict");
    o.jio.remove({"_id": "remove1", "_rev": "1-a"}, o.f);
    o.tick(o);

    // 9. remove attachment wrong revision
    o.spy(o, "status", 409, "Remove attachment with wrong revision " +
          "-> 409 Conflict");
    o.jio.removeAttachment({
      "_id": "remove1",
      "_attachment": "remove2",
      "_rev": "1-a"
    }, o.f);
    o.tick(o);

    // 10. remove document
    o.last_rev = "3-" + o.rev_hash;
    o.rev_hash = generateRevisionHash(
      {"_id": "remove1"},
      {"start": 3, "ids": [o.rev_hash, "oldrev", "veryoldrev"]},
      true
    );
    o.spy(o, "value", {"ok": true, "id": "remove1", "rev": "4-" + o.rev_hash},
          "Remove document");
    o.jio.remove({"_id": "remove1", "_rev": o.last_rev}, o.f);
    o.tick(o);

    // 11. check document tree
    o.doctree.children[0].children[0].children[0].children.unshift({
      "rev": "4-" + o.rev_hash,
      "status": "deleted",
      "children": []
    });
    deepEqual(util.jsonlocalstorage.getItem(
      o.localpath + "/remove1.revision_tree.json"
    ), o.doctree, "Check document tree");

    util.closeAndcleanUpJio(o.jio);
  });

  test("allDocs", function () {

    var o = generateTools();

    o.jio = jIO.newJio({
      "type": "revision",
      "sub_storage": {
        "type": "local",
        "username": "urevad1",
        "application_name": "arevad1"
      }
    });
    o.localpath = "jio/localstorage/urevad1/arevad1";

    // adding 3 documents
    o.jio.put({"_id": "yes"}, function (err, response) {
      o.rev1 = (response || {}).rev;
    });
    o.jio.put({"_id": "no"}, function (err, response) {
      o.rev2 = (response || {}).rev;
    });
    o.jio.put({"_id": "maybe"}, function (err, response) {
      o.rev3 = (response || {}).rev;
    });
    o.clock.tick(1000);

    // adding conflicts
    o.jio.put({"_id": "maybe"});

    // adding 2 attachments
    o.jio.putAttachment({
      "_id": "yes",
      "_attachment": "blue",
      "_mimetype": "text/plain",
      "_rev": o.rev1,
      "_data": "sky"
    }, function (err, response) {
      o.rev1 = (response || {}).rev;
    });
    o.jio.putAttachment({
      "_id": "no",
      "_attachment": "Heeeee!",
      "_mimetype": "text/plain",
      "_rev": o.rev2,
      "_data": "Hooooo!"
    }, function (err, response) {
      o.rev2 = (response || {}).rev;
    });
    o.clock.tick(1000);

    o.rows = {
      "total_rows": 3,
      "rows": [{
        "id": "maybe",
        "key": "maybe",
        "value": {
          "rev": o.rev3
        }
      }, {
        "id": "no",
        "key": "no",
        "value": {
          "rev": o.rev2
        }
      }, {
        "id": "yes",
        "key": "yes",
        "value": {
          "rev": o.rev1
        }
      }]
    };
    o.spy(o, "value", o.rows, "allDocs");
    o.jio.allDocs(function (err, response) {
      if (response && response.rows) {
        response.rows.sort(function (a, b) {
          return a.id > b.id ? 1 : a.id < b.id ? -1 : 0;
        });
      }
      o.f(err, response);
    });
    o.tick(o);

    o.rows.rows[0].doc = {
      "_id": "maybe",
      "_rev": o.rev3
    };
    o.rows.rows[1].doc = {
      "_id": "no",
      "_rev": o.rev2,
      "_attachments": {
        "Heeeee!": {
          "content_type": "text/plain",
          "digest": "md5-2686969b0bc0fd9bc186146a1ecb09a7",
          "length": 7
        }
      },
    };
    o.rows.rows[2].doc = {
      "_id": "yes",
      "_rev": o.rev1,
      "_attachments": {
        "blue": {
          "content_type": "text/plain",
          "digest": "md5-900bc885d7553375aec470198a9514f3",
          "length":  3
        }
      },
    };
    o.spy(o, "value", o.rows, "allDocs + include docs");
    o.jio.allDocs({"include_docs": true}, function (err, response) {
      if (response && response.rows) {
        response.rows.sort(function (a, b) {
          return a.id > b.id ? 1 : a.id < b.id ? -1 : 0;
        });
      }
      o.f(err, response);
    });
    o.tick(o);

    util.closeAndcleanUpJio(o.jio);
  });

  test("Scenario", function () {

    var o = generateTools();

    o.jio = jIO.newJio({
      "type": "revision",
      "sub_storage": {
        "type": "local",
        "username": "usam1",
        "application_name": "asam1"
      }
    });
    o.localpath = "jio/localstorage/usam1/asam1";

    // new application
    ok(o.jio, "I open my application with revision and localstorage");

    // put non empty document A-1
    o.doc = {"_id": "sample1", "title": "mySample1"};
    o.revisions = {"start": 0, "ids": []};
    o.hex = generateRevisionHash(o.doc, o.revisions);
    o.rev = "1-" + o.hex;

    o.spy(o, "value", {"ok": true, "id": "sample1", "rev": o.rev},
          "Then, I create a new document (no attachment), my application " +
          "keep the revision in memory");
    o.jio.put(o.doc, o.f);
    o.tick(o);

    // open new tab (JIO)
    o.jio2 = jIO.newJio({
      "type": "revision",
      "sub_storage": {
        "type": "local",
        "username": "usam1",
        "application_name": "asam1"
      }
    });
    o.localpath = "jio/localstorage/usam1/asam1";

    // Create a new JIO in a new tab
    ok(o.jio2, "Now, I am opening a new tab, with the same application" +
       " and the same storage tree");

    // Get the document from the first storage
    o.doc._rev = o.rev;
    o.doc._revisions = {"ids": [o.hex], "start": 1};
    o.doc._revs_info = [{"rev": o.rev, "status": "available"}];
    o.spy(o, "value", o.doc, "And, on this new tab, I load the document," +
          "and my application keep the revision in memory");
    o.jio2.get({"_id": "sample1", "_rev": o.rev}, {
      "revs_info": true,
      "revs": true,
      "conflicts": true,
    }, o.f);
    o.tick(o);

    // MODIFY the 2nd version
    o.doc_2 = {"_id": "sample1", "_rev": o.rev,
               "title": "mySample2_modified"};
    o.revisions_2 = {"start": 1, "ids": [o.hex]};
    o.hex_2 = generateRevisionHash(o.doc_2, o.revisions_2);
    o.rev_2 = "2-" + o.hex_2;
    o.spy(o, "value", {"id": "sample1", "ok": true, "rev": o.rev_2},
          "So, I can modify and update it");
    o.jio2.put(o.doc_2, o.f);
    o.tick(o);

    // MODIFY first version
    o.doc_1 = {
      "_id": "sample1",
      "_rev": o.rev,
      "title": "mySample1_modified"
    };
    o.revisions_1 = {"start": 1, "ids": [o.rev.split('-')[1]]};
    o.hex_1 = generateRevisionHash(o.doc_1, o.revisions_1);
    o.rev_1 = "2-" + o.hex_1;
    o.spy(o, "value", {"id": "sample1", "ok": true, "rev": o.rev_1},
          "Back to the first tab, I update the document.");
    o.jio.put(o.doc_1, o.f);
    o.tick(o);

    // Close 1st tab
    o.jio.close();

    // Close 2nd tab
    o.jio2.close();
    ok(o.jio2, "I close tab both tabs");

    // Reopen JIO
    o.jio = jIO.newJio({
      "type": "revision",
      "sub_storage": {
        "type": "local",
        "username": "usam1",
        "application_name": "asam1"
      }
    });
    o.localpath = "jio/localstorage/usam1/asam1";
    ok(o.jio, "Later, I open my application again");

    // GET document without revision = winner & conflict!
    o.mydocSample3 = {"_id": "sample1", "title": "mySample1_modified",
                      "_rev": o.rev_1};
    o.mydocSample3._conflicts = [o.rev_2];
    o.mydocSample3._revs_info = [{"rev": o.rev_1, "status": "available"}, {
      "rev": o.rev,
      "status": "available"
    }];
    o.mydocSample3._revisions = {"ids": [o.hex_1, o.hex], "start": 2};
    o.spy(o, "value", o.mydocSample3, "I load the same document as before" +
          ", and a popup shows that there is a conflict");
    o.jio.get({"_id": "sample1"}, {
      "revs_info": true,
      "revs": true,
      "conflicts": true
    }, o.f);
    o.tick(o);

    // REMOVE one of the two conflicting versions
    o.revisions = {"start": 2, "ids": [
      o.rev_1.split('-')[1],
      o.rev.split('-')[1]
    ]};
    o.doc_myremove3 = {"_id": "sample1", "_rev": o.rev_1};
    o.rev_3 = "3-" + generateRevisionHash(o.doc_myremove3, o.revisions, true);

    o.spy(o, "value", {"ok": true, "id": "sample1", "rev": o.rev_3},
           "I choose one of the document and close the application.");
    o.jio.remove({"_id": "sample1", "_rev": o.rev_1}, o.f);
    o.tick(o);

    // check to see if conflict still exists
    o.mydocSample4 = {
      "_id": "sample1",
      "title": "mySample2_modified",
      "_rev": o.rev_2
    };
    o.mydocSample4._revs_info = [{"rev": o.rev_2, "status": "available"}, {
      "rev": o.rev,
      "status": "available"
    }];
    o.mydocSample4._revisions = {"ids": [o.hex_2, o.hex], "start": 2};

    o.spy(o, "value", o.mydocSample4, "Test if conflict still exists");
    o.jio.get({"_id": "sample1"}, {
      "revs_info": true,
      "revs": true,
      "conflicts": true
    }, o.f);
    o.tick(o);

    // END
    util.closeAndcleanUpJio(o.jio);
  });

}));