/*
 * Copyright (c) 2007 Collactive. All rights reserved.
 */

// Both Firefox & IE seem to choke at ~4000, so split around there.

// Storage stuff
if (typeof(Collactive) == 'undefined') {
	Collactive = {};
}

Collactive.Storage = function () {
	this._storage = {};
	this._sessionStorage = {};

	this.clear = function() {
		this._storage = {};
        this._writeStorage(this._serializeData({}));
	};

    // Saves a value persistently
    this.save = function(key, value) {
        this._storage = this._deserializeData(this._readStorage());
        if (value == null) {
            delete this._storage[key];
        } else {
            this._storage[key] = value;
        }
        this._writeStorage(this._serializeData(this._storage));
    };

    // Loads a persistent value
    this.load = function(key) {
        this._storage = this._deserializeData(this._readStorage());
        return this._storage[key];
    };

	// Returns all the keys in the storage, optionally only those that
	// match a regex.
	this.keys = function(exp) {
        this._storage = this._deserializeData(this._readStorage());

		var keys = [];
		for (var k in this._storage) {
			if (exp == null || k.match(exp)) {
				keys[keys.length] = k;
			}
		}
		return keys;
	};
	
    // Saves a value only for this session. The value must be read with
    // loadSession(), not load().
    this.saveSession = function(key, value) {
        if (value == null) {
            delete this._sessionStorage[key];
        } else {
            this._sessionStorage[key] = value;
        }
    };

    // Loads a session-only value
    this.loadSession = function(key) {
        return this._sessionStorage[key];
    };

	this.clearSession = function() {
        this._sessionStorage = {};
	};

	this._serializeData = function(data) {
        return Collactive.Base64.encode(Collactive.JSON.objectToJSONString(data));
	};

	this._deserializeData = function(data) {
        var storage = Collactive.JSON.parseJSON(Collactive.Base64.decode(data));
        if (storage == null) {
            return {};
        }
        return storage;
	};

	return this;
};

Collactive.CookieStorage = function() {
    this.MAX_COOKIE_LENGTH = 2000;

    // Deserializes the storage
    this._readStorage = function() {
        return this._read("storage");
    };

    // Serializes the storage
    this._writeStorage = function(data) {
        this._write("storage", data);
    };

    // Deserializes a storage
    this._read = function(prefix) {
        var encodedData = "";
        var cookie;
        var i = 0;
        // The boolean check of a string checks for both null and an empty string
        while ((cookie = this._loadCookie(prefix + i))) {
            encodedData += cookie;
            i++;
        }
        return encodedData;
    };

    // Serializes a storage into cookie(s)
    this._write = function(prefix, encodedData, expireSec) {
        var chunkNumber = 0;
        for (i = 0; i < encodedData.length; i+= this.MAX_COOKIE_LENGTH) {
            var length = encodedData.length - i;
            if (length > this.MAX_COOKIE_LENGTH) {
                length = this.MAX_COOKIE_LENGTH;
            }

            var chunk = encodedData.substr(i, length);
            this._saveCookie(prefix + chunkNumber, chunk, expireSec);

            chunkNumber++;
        }

        // Empty any remaining strings
        while (this._loadCookie(prefix + chunkNumber) != null) {
            this._saveCookie(prefix + chunkNumber, "", -60);
            chunkNumber++;
        }
    };

    // Implementation for saves. Saves values in the collactive.com domain
    // expireSecs:
    //  - null: never expire
    //  - 0: expire at end of session
    //  - >0: expire in this number of seconds from now
    //  - <0: expire in the past (deletes the cookie)
    this._saveCookie = function(key, value, expireSecs) {
        if (value == null) {
            value = "";
        }

        var domain = "collactive.com";
        var expireDate
        if (expireSecs == null) {
            expireDate = "Sun, 10 Jan 2038 12:34:56";
        } else if (expireSecs == 0) {
            expireDate = null;
        } else {
            var d = new Date();
            d.setTime(d.getTime() + 1000 * expireSecs);
            expireDate = d.toGMTString();
        }

        var expireString = (expireDate == null) ? "" : ("; expires=" + expireDate);
        document.cookie = key + "=" + value + "; domain=." + domain + "; path=/" + expireString;
    };

    // Loads a value (from a cookie)
    this._loadCookie = function(key) {
        var nameEQ = key + "=";
        var ca = document.cookie.split(';');
        for(var i=0;i < ca.length;i++) {
            var c = ca[i];
            while (c.charAt(0)==' ') c = c.substring(1,c.length);
            if (c.indexOf(nameEQ) == 0) {
                var value = c.substring(nameEQ.length,c.length);
                if (value == "") {
                    value = null;
                }
                return value;
            }
        }
        return null;
    };

	return this;
};

Collactive.CookieStorage.prototype = new Collactive.Storage();

Collactive.CSAStorage = function() {
	this._readStorage = function() {
		return GM_getValue("storage", null);
	}

	this._writeStorage = function(data) {
	    GM_setValue("storage", data, true);
	}
	return this;
};

Collactive.CSAStorage.prototype = new Collactive.Storage();

Collactive.JSON = {
    m: {
        '\b': '\\b',
        '\t': '\\t',
        '\n': '\\n',
        '\f': '\\f',
        '\r': '\\r',
        '"' : '\\"',
        '\\': '\\\\'
    },
    s: {
        array: function (x) {
            var a = ['['], b, f, i, l = x.length, v;
            for (i = 0; i < l; i += 1) {
                v = x[i];
                f = Collactive.JSON.s[typeof v];
                if (f) {
                    v = f(v);
                    if (typeof v == 'string') {
                        if (b) {
                            a[a.length] = ',';
                        }
                        a[a.length] = v;
                        b = true;
                    }
                }
            }
            a[a.length] = ']';
            return a.join('');
        },
        'boolean': function (x) {
            return String(x);
        },
        'null': function (x) {
            return "null";
        },
        number: function (x) {
            return isFinite(x) ? String(x) : 'null';
        },
        object: function (x) {
            if (x) {
                if (x instanceof Array) {
                    return Collactive.JSON.s.array(x);
                }
                var a = ['{'], b, f, i, v;
                for (i in x) {
                    v = x[i];
                    f = Collactive.JSON.s[typeof v];
                    if (f) {
                        v = f(v);
                        if (typeof v == 'string') {
                            if (b) {
                                a[a.length] = ',';
                            }
                            a.push(Collactive.JSON.s.string(i), ':', v);
                            b = true;
                        }
                    }
                }
                a[a.length] = '}';
                return a.join('');
            }
            return 'null';
        },
        string: function (x) {
            if (/[\"\\\x00-\x1f]/.test(x)) {
                x = x.replace(/([\x00-\x1f\\\"])/g,
                              function(a, b) {
                                  var c = Collactive.JSON.m[b];
                                  if (c) {
                                      return c;
                                  }
                                  c = b.charCodeAt();
                                  return '\\u00' +
                                      Math.floor(c / 16).toString(16) +
                                      (c % 16).toString(16);
                              });
            }
            return '"' + x + '"';
        }
    },

    objectToJSONString: function (obj) {
        return Collactive.JSON.s.object(obj);
    },

    arrayToJSONString: function (arr) {
        return Collactive.JSON.s.array(arr);
    },

    parseJSON: function (str) {
        try {
            if ((str == null) || (str == '') || str.match(/^\s*$/)) {
                return null;
            }
            return !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
                                                               str.replace(/\"(\\.|[^\"\\])*\"/g, ''))) &&
            eval('(' + str + ')');
        } catch (e) {
            return null;
        }
    }
};

Collactive.Base64 = {
    // This code was written by Tyler Akins and has been placed in the
    // public domain.  It would be nice if you left this header intact.
    // Base64 code from Tyler Akins -- http://rumkin.com

    KEYSTR: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",

    encode: function(input) {
	   if (input == null) {
	     input = "";
	   }
       var keyStr = Collactive.Base64.KEYSTR;

       var output = "";
       var chr1, chr2, chr3;
       var enc1, enc2, enc3, enc4;
       var i = 0;

       do {
          chr1 = input.charCodeAt(i++);
          chr2 = input.charCodeAt(i++);
          chr3 = input.charCodeAt(i++);

          enc1 = chr1 >> 2;
          enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
          enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
          enc4 = chr3 & 63;

          if (isNaN(chr2)) {
             enc3 = enc4 = 64;
          } else if (isNaN(chr3)) {
             enc4 = 64;
          }

          output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) +
             keyStr.charAt(enc3) + keyStr.charAt(enc4);
       } while (i < input.length);

       return output;
    },

    decode: function(input) {
	   if (input == null) {
	     input = "";
	   }
	   if (input == "") {
	       return "";
	   }

       var keyStr = Collactive.Base64.KEYSTR;
       var output = "";
       var chr1, chr2, chr3;
       var enc1, enc2, enc3, enc4;
       var i = 0;

       // remove all characters that are not A-Z, a-z, 0-9, +, /, or =
       input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

       do {
          enc1 = keyStr.indexOf(input.charAt(i++));
          enc2 = keyStr.indexOf(input.charAt(i++));
          enc3 = keyStr.indexOf(input.charAt(i++));
          enc4 = keyStr.indexOf(input.charAt(i++));

          chr1 = (enc1 << 2) | (enc2 >> 4);
          chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
          chr3 = ((enc3 & 3) << 6) | enc4;

          output = output + String.fromCharCode(chr1);

          if (enc3 != 64) {
             output = output + String.fromCharCode(chr2);
          }
          if (enc4 != 64) {
             output = output + String.fromCharCode(chr3);
          }
       } while (i < input.length);

       return output;
    }
};
