/*
* Copyright 2013, Nexedi SA
* Released under the LGPL license.
* http://www.gnu.org/licenses/lgpl.html
*/
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global jIO: true, localStorage: true, setTimeout: true */
/**
 * JIO Local Storage. Type = 'local'.
 * Local browser "database" storage.
 */
jIO.addStorageType('local', function (spec, my) {

  spec = spec || {};
  var that, priv, localstorage;
  that = my.basicStorage(spec, my);
  priv = {};

  /*
   * Wrapper for the localStorage used to simplify instion of any kind of
   * values
   */
  localstorage = {
    getItem: function (item) {
      var value = localStorage.getItem(item);
      return value === null ? null : JSON.parse(value);
    },
    setItem: function (item, value) {
      return localStorage.setItem(item, JSON.stringify(value));
    },
    removeItem: function (item) {
      return localStorage.removeItem(item);
    }
  };

  // attributes
  priv.username = spec.username || '';
  priv.application_name = spec.application_name || 'untitled';

  priv.localpath = 'jio/localstorage/' + priv.username + '/' +
    priv.application_name;

  // ==================== Tools ====================
  /**
   * Update [doc] the document object and remove [doc] keys
   * which are not in [new_doc]. It only changes [doc] keys not starting
   * with an underscore.
   * ex: doc:     {key:value1,_key:value2} with
   *     new_doc: {key:value3,_key:value4} updates
   *     doc:     {key:value3,_key:value2}.
   * @param  {object} doc The original document object.
   * @param  {object} new_doc The new document object
   */
  priv.documentObjectUpdate = function (doc, new_doc) {
    var k;
    for (k in doc) {
      if (doc.hasOwnProperty(k)) {
        if (k[0] !== '_') {
          delete doc[k];
        }
      }
    }
    for (k in new_doc) {
      if (new_doc.hasOwnProperty(k)) {
        if (k[0] !== '_') {
          doc[k] = new_doc[k];
        }
      }
    }
  };

  /**
   * Checks if an object has no enumerable keys
   * @method objectIsEmpty
   * @param  {object} obj The object
   * @return {boolean} true if empty, else false
   */
  priv.objectIsEmpty = function (obj) {
    var k;
    for (k in obj) {
      if (obj.hasOwnProperty(k)) {
        return false;
      }
    }
    return true;
  };

  // ===================== overrides ======================
  that.specToStore = function () {
    return {
      "application_name": priv.application_name,
      "username": priv.username
    };
  };

  that.validateState = function () {
    if (typeof priv.username === "string" && priv.username !== '') {
      return '';
    }
    return 'Need at least one parameter: "username".';
  };

  // ==================== commands ====================
  /**
   * Create a document in local storage.
   * @method post
   * @param  {object} command The JIO command
   */
  that.post = function (command) {
    setTimeout(function () {
      var doc = command.getDocId();
      if (!(typeof doc === "string" && doc !== "")) {
        that.error({
          "status": 405,
          "statusText": "Method Not Allowed",
          "error": "method_not_allowed",
          "message": "Cannot create document which id is undefined",
          "reason": "Document id is undefined"
        });
        return;
      }
      doc = localstorage.getItem(priv.localpath + "/" + doc);
      if (doc === null) {
        // the document does not exist
        localstorage.setItem(priv.localpath + "/" + command.getDocId(),
          command.cloneDoc());
        that.success({
          "ok": true,
          "id": command.getDocId()
        });
      } else {
        // the document already exists
        that.error({
          "status": 409,
          "statusText": "Conflicts",
          "error": "conflicts",
          "message": "Cannot create a new document",
          "reason": "Document already exists"
        });
      }
    });
  };

  /**
   * Create or update a document in local storage.
   * @method put
   * @param  {object} command The JIO command
   */
  that.put = function (command) {
    setTimeout(function () {
      var doc;
      doc = localstorage.getItem(priv.localpath + "/" + command.getDocId());
      if (doc === null) {
        //  the document does not exist
        doc = command.cloneDoc();
      } else {
        // the document already exists
        priv.documentObjectUpdate(doc, command.cloneDoc());
      }
      // write
      localstorage.setItem(priv.localpath + "/" + command.getDocId(), doc);
      that.success({
        "ok": true,
        "id": command.getDocId()
      });
    });
  };

  /**
   * Add an attachment to a document
   * @method  putAttachment
   * @param  {object} command The JIO command
   */
  that.putAttachment = function (command) {
    setTimeout(function () {
      var doc;
      doc = localstorage.getItem(priv.localpath + "/" + command.getDocId());
      if (doc === null) {
        //  the document does not exist
        that.error({
          "status": 404,
          "statusText": "Not Found",
          "error": "not_found",
          "message": "Impossible to add attachment",
          "reason": "Document not found"
        });
        return;
      }

      // the document already exists
      doc._attachments = doc._attachments || {};
      doc._attachments[command.getAttachmentId()] = {
        "content_type": command.getAttachmentMimeType(),
        "digest": "md5-" + command.md5SumAttachmentData(),
        "length": command.getAttachmentLength()
      };

      // upload data
      localstorage.setItem(priv.localpath + "/" + command.getDocId() + "/" +
        command.getAttachmentId(),
        command.getAttachmentData());
      // write document
      localstorage.setItem(priv.localpath + "/" + command.getDocId(), doc);
      that.success({
        "ok": true,
        "id": command.getDocId() + "/" + command.getAttachmentId()
      });
    });
  };

  /**
   * Get a document or attachment
   * @method get
   * @param  {object} command The JIO command
   */
  that.get = function (command) {
    setTimeout(function () {
      var doc;
      if (typeof command.getAttachmentId() === "string") {
        // seeking for an attachment
        doc = localstorage.getItem(priv.localpath + "/" + command.getDocId() +
          "/" + command.getAttachmentId());
        if (doc !== null) {
          that.success(doc);
        } else {
          that.error({
            "status": 404,
            "statusText": "Not Found",
            "error": "not_found",
            "message": "Cannot find the attachment",
            "reason": "Attachment does not exist"
          });
        }
      } else {
        // seeking for a document
        doc = localstorage.getItem(priv.localpath + "/" + command.getDocId());
        if (doc !== null) {
          that.success(doc);
        } else {
          that.error({
            "status": 404,
            "statusText": "Not Found",
            "error": "not_found",
            "message": "Cannot find the document",
            "reason": "Document does not exist"
          });
        }
      }
    });
  };

  /**
   * Remove a document or attachment
   * @method remove
   * @param  {object} command The JIO command
   */
  that.remove = function (command) {
    setTimeout(function () {
      var doc, error, i, attachment_list;
      error = function (word) {
        that.error({
          "status": 404,
          "statusText": "Not Found",
          "error": "not_found",
          "message": word + " not found",
          "reason": "missing"
        });
      };
      doc = localstorage.getItem(priv.localpath + "/" + command.getDocId());
      if (typeof command.getAttachmentId() === "string") {
        // remove attachment from document
        if (doc !== null && typeof doc === "object" &&
            typeof doc._attachments === "object") {
          if (typeof doc._attachments[command.getAttachmentId()] ===
              "object") {
            delete doc._attachments[command.getAttachmentId()];
            if (priv.objectIsEmpty(doc._attachments)) {
              delete doc._attachments;
            }
            localstorage.setItem(priv.localpath + "/" + command.getDocId(),
              doc);
            localstorage.removeItem(priv.localpath + "/" + command.getDocId() +
              "/" + command.getAttachmentId());
            that.success({
              "ok": true,
              "id": command.getDocId() + "/" + command.getAttachmentId()
            });
          } else {
            error("Attachment");
          }
        } else {
          error("Document");
        }
      } else {
        // seeking for a document
        attachment_list = [];
        if (doc !== null && typeof doc === "object") {
          if (typeof doc._attachments === "object") {
            // prepare list of attachments
            for (i in doc._attachments) {
              if (doc._attachments.hasOwnProperty(i)) {
                attachment_list.push(i);
              }
            }
          }
        } else {
          return error("Document");
        }
        localstorage.removeItem(priv.localpath + "/" + command.getDocId());
        // delete all attachments
        for (i = 0; i < attachment_list.length; i += 1) {
          localstorage.removeItem(priv.localpath + "/" + command.getDocId() +
            "/" + attachment_list[i]);
        }
        that.success({
          "ok": true,
          "id": command.getDocId()
        });
      }
    });
  };

  /**
   * Get all filenames belonging to a user from the document index
   * @method allDocs
   * @param  {object} command The JIO command
   */
  that.allDocs = function (command) {
    var i, j, file, items = 0,
      s = new RegExp(priv.localpath + '\\/.*$'),
      all_doc_response = {},
      query_object = [], query_syntax, query_response = [];

    query_syntax = command.getOption('query');
    if (query_syntax === undefined) {
      all_doc_response.rows = [];

      for (i in localStorage) {
        if (localStorage.hasOwnProperty(i)) {
          // filter non-documents
          if (s.test(i)) {
            items += 1;
            j = i.split('/').slice(-1)[0];

            file = { value: {} };
            file.id = j;
            file.key = j;
            if (command.getOption('include_docs')) {
              file.doc = JSON.parse(localStorage.getItem(i));
            }
            all_doc_response.rows.push(file);
          }
        }
      }
      all_doc_response.total_rows = items;
      that.success(all_doc_response);
    } else {
      // create complex query object from returned results
      for (i in localStorage) {
        if (localStorage.hasOwnProperty(i)) {
          if (s.test(i)) {
            items += 1;
            j = i.split('/').slice(-1)[0];
            query_object.push(localstorage.getItem(i));
          }
        }
      }
      query_response = jIO.ComplexQueries.query(query_syntax, query_object);
      that.success(query_response);
    }
  };

  return that;
});