/**
 * Ex DOM Storage 0.01
 *
 * LICENCE : MIT-style
 * AUTHOR  : Toru Yamaguchi <zigorou@cpan.org>
 */

/* -------------------------------------------------- */

/**
 * Cookie utility class
 */
var Cookie = function() {
  this.params = this.parse();
};

Cookie.prototype = {
  set: function(key, value) {
    window.document.cookie = key + '=' + value + ";";
    this.params[key] = value;
  },
  get: function(key) {
    return this.params[key];
  },
  parse: function() {
    var cookie = window.document.cookie;
    if (!cookie)
      return {};

    var params = {};
    var pairs = cookie.split("; ");
    for (var idx in pairs) with ( { pair: pairs[idx].split("=") } ) {
      params[pair[0]] = pair[1];
    }

    return params;
  }
};

/* -------------------------------------------------- */

var storageId = "";
var attrId = "storage";
var storage = null;
var isInitiation = false;
var contentDocument = window.document;

var localStorageSpace = 65494;
var sessionStorageSpace = 65474;

/**
 * attachEvent hacks
 */
if (typeof contentDocument.attachEvent == "object") {
  (function(contentDocument) {
     var listeners = {
       onstorage: [],
       onstoragecommit: []
     };

     contentDocument.__nativeAttachEvent = contentDocument.attachEvent;
     // contentDocument.__nativeDetachEvent = contentDocument.detachEvent;
     contentDocument.__nativeFireEvent = contentDocument.fireEvent;

     contentDocument.attachEvent = function(type, fn) {
       if (listeners[type]) {
         listeners[type].push(fn);
         return true;
       }
       else {
         return contentDocument.__nativeAttachEvent(type, fn);
       }
     };

     contentDocument.fireEvent = function(type, evt) {
       if (listeners[type]) {
         try {
           if (typeof contentDocument[type] == "function")
             contentDocument[type](evt);

           for (var i in listeners[type]) if (typeof listeners[type][i] == "function") {
             listeners[type][i](evt);
           }
           return true;
         }
         catch (e) {
           return false;
         }
       }
       else {
         return contentDocument.__nativeFireEvent(type, evt);
       }
     };
   })(contentDocument);
}

/**
 * initialize storage
 */
function initialize() {
  isInitiation = true; // Block onpropertychange event

  storageId = document.URL.substr(document.URL.indexOf("#") + 1);
  element.addBehavior("#default#userData");
  element.load(storageId);

  var initSessionStorage = false;

  if (storageId == "sessionStorage") {
    var cookie = new Cookie();

    if (cookie.get("__sessionStorage__")) {
      initSessionStorage = true;
      cookie.set("__sessionStorage__", 1);
    }
  }

  with ({ serialized: element.getAttribute(attrId) }) {
    if (!serialized || initSessionStorage) {
      storage = {};
      element.setAttribute(attrId, JSON.stringify(storage));
      element.save(storageId);
    }
    else {
      storage = JSON.parse(serialized);
      for (var key in storage) {
        element[key] = storage[key];
      }
    }
  }

  isInitiation = false;
}

/**
 * syncronize between userData and element properties
 */
function syncronize(evt) {
  if (isInitiation) // Block onpropertychange event for loading storage data
    return;

  var key = evt.propertyName;
  if (key == "length" || key == "remainingSpace")
    return;

  setItem(key, element[key]);
}

/**
 * update userData
 */
function update() {
  if (storage != null) {
    var serialized = JSON.stringify(storage);
    element.setAttribute(attrId, serialized);
  }
  else {
    element.removeAttribute(attrId);
  }

  element.save(storageId);
}

/**
 * create storage event object
 */
function createEvent(opts) {
  var evt = createStorageEvent();
  evt.initStorageEvent("storage", false, false, opts.key, opts.oldValue, opts.newValue, (storageId == "sessionStorage") ? "#session" : window.location.href, window);
  return evt;
}

function stringify(obj) {
  if (typeof obj == "string") {
    return obj;
  }
  else if (typeof obj != "undefined" && obj != null && typeof obj["toString"] == "function") {
    return obj.toString();
  }
  else {
    return String(obj);
  }
}

/* -------------------------------------------------- */

/**
 * Implementation StorageEvent Interface
 * http://www.whatwg.org/specs/web-apps/current-work/#storageevent
 */
var createStorageEvent = function() {
  var evt = contentDocument.createEventObject();

  evt.initStorageEvent = function(type, canBubble, cancelable, key, oldValue, newValue, url, source) {
    this.type = type;
    this.canBubble = canBubble;
    this.cancelable = cancelable;
    this.key = key;
    this.oldValue = oldValue;
    this.newValue = newValue;
    this.url = url;
    this.uri = url;
    this.source = source;
  };

  evt.initStorageEventNS = function(namespaceURI, type, canBubble, cancelable, key, oldValue, newValue, url, source) { };

  return evt;
};

/* -------------------------------------------------- */

/*
 * Implementation Storage Interface
 * http://www.whatwg.org/specs/web-apps/current-work/#storage
 */
function getLength() {
  var length = 0;
  for (var p in storage)
    length++;
  return length;
}

function getRemainingSpace(serialized) {
  if (!serialized)
    serialized = JSON.stringify(storage);

  if (storageId == "localStorage") {
    return localStorageSpace - serialized.length;
  }
  else {
    return sessionStorageSpace - serialized.length;
  }
}

function clear() {
  storage = {};
  var opts = { oldValue: null, newValue: null, key: null };
  update();
  var evt = createEvent(opts);
  //  storageEvent.fire(evt);
  contentDocument.fireEvent("onstorage", evt);
}

function getItem(key) {
  if (storage)
    return storage[key];
  else
    return null;
}

function key(idx) {
  if (idx < 0)
    throw new Error("INDEX_SIZE_ERR");

  var cnt = 0;
  for (var p in storage) {
    if (idx == cnt++)
      return p;
  }

  throw new Error("INDEX_SIZE_ERR");
}

function removeItem(key) {
  var opts = { oldValue: storage[key], newValue: null, key: key };
  delete storage[key];
  update();
  var evt = createEvent(opts);
  //  storageEvent.fire(evt);
  contentDocument.fireEvent("onstorage", evt);
}

function setItem(key, value) {
  var serialized = stringify(value);
  var opts = { oldValue: storage[key], newValue: serialized, key: key };

  if (storage == null)
    storage = {};

  if (storage[key] == serialized) {
    throw new Error("INVALID_ACCESS_ERR");
  }

  storage[key] = serialized;
  update();
  var evt = createEvent(opts);
  //  storageEvent.fire(evt);
  contentDocument.fireEvent("onstorage", evt);
}

