text: /**
   * A Twitter library in JavaScript
   * https://github.com/mynetx/codebird-js
   * 
   * @package codebird
   * @version 2.4.0-dev
   * @author J.M. <me@mynetx.net>
   * @copyright 2010-2013 J.M. <me@mynetx.net>
   *
   * This program is free software: you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation, either version 3 of the License, or
   * (at your option) any later version.
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
   */
  
  /**
   * Array.indexOf polyfill
   */
  if (! Array.indexOf) {
      Array.prototype.indexOf = function (obj, start) {
          for (var i = (start || 0); i < this.length; i++) {
              if (this[i] == obj) {
                  return i;
              }
          }
        return -1;
      };
  }
  
  
  
  /*
   * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
   * in FIPS PUB 180-1
   * Version 2.1 Copyright Paul Johnston 2000 - 2002.
   * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
   * Distributed under the BSD License
   * See http://pajhome.org.uk/crypt/md5 for details.
   */
  
  var hexcase = 0;
  var b64pad = "";
  var chrsz = 8;
  
  function hex_sha1(s) {
  	return binb2hex(core_sha1(str2binb(s), s.length * chrsz));
  }
  
  function b64_sha1(s) {
  	return binb2b64(core_sha1(str2binb(s), s.length * chrsz));
  }
  
  function str_sha1(s) {
  	return binb2str(core_sha1(str2binb(s), s.length * chrsz));
  }
  
  function hex_hmac_sha1(key, data) {
  	return binb2hex(core_hmac_sha1(key, data));
  }
  
  function b64_hmac_sha1(key, data) {
  	return binb2b64(core_hmac_sha1(key, data));
  }
  
  function str_hmac_sha1(key, data) {
  	return binb2str(core_hmac_sha1(key, data));
  }
  
  function sha1_vm_test() {
  	return hex_sha1("abc") == "a9993e364706816aba3e25717850c26c9cd0d89d";
  }
  
  function core_sha1(x, len) {
  	x[len >> 5] |= 0x80 << (24 - len % 32);
  	x[((len + 64 >> 9) << 4) + 15] = len;
  
  	var w = Array(80);
  	var a = 1732584193;
  	var b = -271733879;
  	var c = -1732584194;
  	var d = 271733878;
  	var e = -1009589776;
  
  	for (var i = 0; i < x.length; i += 16) {
  		var olda = a;
  		var oldb = b;
  		var oldc = c;
  		var oldd = d;
  		var olde = e;
  
  		for (var j = 0; j < 80; j++) {
  			if (j < 16)
  				w[j] = x[i + j];
  			else
  				w[j] = rol(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1);
  			var t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)), safe_add(safe_add(e, w[j]), sha1_kt(j)));
  			e = d;
  			d = c;
  			c = rol(b, 30);
  			b = a;
  			a = t;
  		}
  
  		a = safe_add(a, olda);
  		b = safe_add(b, oldb);
  		c = safe_add(c, oldc);
  		d = safe_add(d, oldd);
  		e = safe_add(e, olde);
  	}
  	return Array(a, b, c, d, e);
  
  }
  
  function sha1_ft(t, b, c, d) {
  	if (t < 20)
  		return (b & c) | ((~b) & d);
  	if (t < 40)
  		return b ^ c ^ d;
  	if (t < 60)
  		return (b & c) | (b & d) | (c & d);
  	return b ^ c ^ d;
  }
  
  function sha1_kt(t) {
  	return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 : (t < 60) ? -1894007588 : -899497514;
  }
  
  function core_hmac_sha1(key, data) {
  	var bkey = str2binb(key);
  	if (bkey.length > 16)
  		bkey = core_sha1(bkey, key.length * chrsz);
  
  	var ipad = Array(16), opad = Array(16);
  	for (var i = 0; i < 16; i++) {
  		ipad[i] = bkey[i] ^ 0x36363636;
  		opad[i] = bkey[i] ^ 0x5C5C5C5C;
  	}
  
  	var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * chrsz);
  	return core_sha1(opad.concat(hash), 512 + 160);
  }
  
  function safe_add(x, y) {
  	var lsw = (x & 0xFFFF) + (y & 0xFFFF);
  	var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
  	return (msw << 16) | (lsw & 0xFFFF);
  }
  
  function rol(num, cnt) {
  	return (num << cnt) | (num >>> (32 - cnt));
  }
  
  function str2binb(str) {
  	var bin = Array();
  	var mask = (1 << chrsz) - 1;
  	for (var i = 0; i < str.length * chrsz; i += chrsz)
  		bin[i >> 5] |= (str.charCodeAt(i / chrsz) & mask) << (24 - i % 32);
  	return bin;
  }
  
  function binb2str(bin) {
  	var str = "";
  	var mask = (1 << chrsz) - 1;
  	for (var i = 0; i < bin.length * 32; i += chrsz)
  		str += String.fromCharCode((bin[i >> 5] >>> (24 - i % 32)) & mask);
  	return str;
  }
  
  function binb2hex(binarray) {
  	var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
  	var str = "";
  	for (var i = 0; i < binarray.length * 4; i++) {
  		str += hex_tab.charAt((binarray[i >> 2] >> ((3 - i % 4) * 8 + 4)) & 0xF) + hex_tab.charAt((binarray[i >> 2] >> ((3 - i % 4) * 8  )) & 0xF);
  	}
  	return str;
  }
  
  function binb2b64(binarray) {
  	var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  	var str = "";
  	for (var i = 0; i < binarray.length * 4; i += 3) {
  		var triplet = (((binarray[i >> 2] >> 8 * (3 - i % 4)) & 0xFF) << 16) | (((binarray[i + 1 >> 2] >> 8 * (3 - (i + 1) % 4)) & 0xFF) << 8 ) | ((binarray[i + 2 >> 2] >> 8 * (3 - (i + 2) % 4)) & 0xFF);
  		for (var j = 0; j < 4; j++) {
  			if (i * 8 + j * 6 > binarray.length * 32)
  				str += b64pad;
  			else
  				str += tab.charAt((triplet >> 6 * (3 - j)) & 0x3F);
  		}
  	}
  	return str;
  }
  
  
  /**
   * A Twitter library in JavaScript
   *
   * @package codebird
   * @subpackage codebird-js
   */
  var Codebird = function () {
  
      /**
       * The OAuth consumer key of your registered app
       */
      var _oauth_consumer_key = null;
  
      /**
       * The corresponding consumer secret
       */
      var _oauth_consumer_secret = null;
  
      /**
       * The app-only bearer token. Used to authorize app-only requests
       */
      var _oauth_bearer_token = null;
  
      /**
       * The API endpoint base to use
       */
      var _endpoint_base = 'https://api.twitter.com/';
  
      /**
       * The API endpoint to use
       */
      var _endpoint = _endpoint_base + '1.1/';
  
      /**
       * The API endpoint to use for OAuth requests
       */
      var _endpoint_oauth = _endpoint_base;
  
      /**
       * API proxy endpoint
       */
      var _endpoint_proxy = 'https://api.jublo.net/codebird/';
  
      /**
       * Use JSONP for GET requests in IE7-9
       */
      var _use_jsonp = false;
  
      /**
       * Whether to access the API via a proxy that is allowed by CORS
       */
      var _use_proxy = false;
  
      /**
       * The Request or access token. Used to sign requests
       */
      var _oauth_token = null;
  
      /**
       * The corresponding request or access token secret
       */
      var _oauth_token_secret = null;
  
      /**
       * The current Codebird version
       */
      var _version = '2.4.0-dev';
  
      /**
       * Sets the OAuth consumer key and secret (App key)
       *
       * @param string key    OAuth consumer key
       * @param string secret OAuth consumer secret
       *
       * @return void
       */
      var setConsumerKey = function (key, secret) {
          _oauth_consumer_key = key;
          _oauth_consumer_secret = secret;
      };
  
      /**
       * Sets the OAuth2 app-only auth bearer token
       *
       * @param string token OAuth2 bearer token
       *
       * @return void
       */
      var setBearerToken = function (token) {
          _oauth_bearer_token = token;
      };
  
      /**
       * Gets the current Codebird version
       *
       * @return string The version number
       */
      var getVersion = function () {
          return _version;
      };
  
      /**
       * Sets the OAuth request or access token and secret (User key)
       *
       * @param string token  OAuth request or access token
       * @param string secret OAuth request or access token secret
       *
       * @return void
       */
      var setToken = function (token, secret) {
          _oauth_token = token;
          _oauth_token_secret = secret;
      };
  
      /**
       * Enables or disables CORS proxy
       *
       * @param bool use_proxy Whether to use CORS proxy or not
       *
       * @return void
       */
      var setUseProxy = function (use_proxy) {
          _use_proxy = !! use_proxy;
      };
  
      /**
       * Parse URL-style parameters into object
       *
       * @param string str String to parse
       * @param array array to load data into
       *
       * @return object
       */
      function parse_str(str, array) {
          // Parses GET/POST/COOKIE data and sets global variables
          //
          // version: 1109.2015
          // discuss at: http://phpjs.org/functions/parse_str    // +   original by: Cagri Ekin
          // +   improved by: Michael White (http://getsprink.com)
          // +    tweaked by: Jack
          // +   bugfixed by: Onno Marsman
          // +   reimplemented by: stag019    // +   bugfixed by: Brett Zamir (http://brett-zamir.me)
          // +   bugfixed by: stag019
          // -    depends on: urldecode
          // +   input by: Dreamer
          // +   bugfixed by: Brett Zamir (http://brett-zamir.me)    // %        note 1: When no argument is specified, will put variables in global scope.
          // *     example 1: var arr = {};
          // *     example 1: parse_str('first=foo&second=bar', arr);
          // *     results 1: arr == { first: 'foo', second: 'bar' }
          // *     example 2: var arr = {};    // *     example 2: parse_str('str_a=Jack+and+Jill+didn%27t+see+the+well.', arr);
          // *     results 2: arr == { str_a: "Jack and Jill didn't see the well." }
          var glue1 = '=',
              glue2 = '&',
              array2 = String(str).replace(/^&?([\s\S]*?)&?$/, '$1').split(glue2),
              i, j, chr, tmp, key, value, bracket, keys, evalStr, that = this,
              fixStr = function (str) {
                  return unescape(str).replace(/([\\"'])/g, '\\$1').replace(/\n/g, '\\n').replace(/\r/g, '\\r');
              };
          if (!array) {
              array = this.window;
          }
  
          for (i = 0; i < array2.length; i++) {
              tmp = array2[i].split(glue1);
              if (tmp.length < 2) {
                  tmp = [tmp, ''];
              }
              key = fixStr(tmp[0]);
              value = fixStr(tmp[1]);
              while (key.charAt(0) === ' ') {
                  key = key.substr(1);
              }
              if (key.indexOf('\0') !== -1) {
                  key = key.substr(0, key.indexOf('\0'));
              }
              if (key && key.charAt(0) !== '[') {
                  keys = [];
                  bracket = 0;
                  for (j = 0; j < key.length; j++) {
                      if (key.charAt(j) === '[' && !bracket) {
                          bracket = j + 1;
                      } else if (key.charAt(j) === ']') {
                          if (bracket) {
                              if (!keys.length) {
                                  keys.push(key.substr(0, bracket - 1));
                              }
                              keys.push(key.substr(bracket, j - bracket));
                              bracket = 0;
                              if (key.charAt(j + 1) !== '[') {
                                  break;
                              }
                          }
                      }
                  }
                  if (!keys.length) {
                      keys = [key];
                  }
                  for (j = 0; j < keys[0].length; j++) {
                      chr = keys[0].charAt(j);
                      if (chr === ' ' || chr === '.' || chr === '[') {
                          keys[0] = keys[0].substr(0, j) + '_' + keys[0].substr(j + 1);
                      }
                      if (chr === '[') {
                          break;
                      }
                  }
                  evalStr = 'array';
                  for (j = 0; j < keys.length; j++) {
                      key = keys[j];
                      if ((key !== '' && key !== ' ') || j === 0) {
                          key = "'" + key + "'";
                      } else {
                          key = eval(evalStr + '.push([]);') - 1;
                      }
                      evalStr += '[' + key + ']';
                      if (j !== keys.length - 1 && eval('typeof ' + evalStr) === 'undefined') {
                          eval(evalStr + ' = [];');
                      }
                  }
                  evalStr += " = '" + value + "';\n";
                  eval(evalStr);
              }
          }
      }
  
      /**
       * Main API handler working on any requests you issue
       *
       * @param string   fn            The member function you called
       * @param array    params        The parameters you sent along
       * @param function callback      The callback to call with the reply
       * @param bool     app_only_auth Whether to use app-only auth
       *
       * @return mixed The API reply encoded in the set return_format
       */
  
      var __call = function (fn, params, callback, app_only_auth) {
          if (typeof params == 'undefined') {
              var params = {};
          }
          if (typeof app_only_auth == 'undefined') {
              var app_only_auth = false;
          }
          if (typeof callback != 'function' && typeof params == 'function') {
              callback = params;
              params = {};
              if (typeof callback == 'bool') {
                  app_only_auth = callback;
              }
          } else if (typeof callback == 'undefined') {
              var callback = function (reply) {};
          }
          switch (fn) {
              case "oauth_authenticate":
              case "oauth_authorize":
                  return this[fn](params, callback);
                  break;
  
              case "oauth2_token":
                  return this[fn](callback);
          }
          // parse parameters
          var apiparams = {};
          if (typeof params == 'object') {
              apiparams = params;
          } else {
              parse_str(params, apiparams); //TODO
          }
  
          // map function name to API method
          var method = '';
  
          // replace _ by /
          var path = fn.split('_');
          for (var i = 0; i < path.length; i++) {
              if (i > 0) {
                  method += '/';
              }
              method += path[i];
          }
          // undo replacement for URL parameters
          var url_parameters_with_underscore = ['screen_name'];
          for (i = 0; i < url_parameters_with_underscore.length; i++) {
              var param = url_parameters_with_underscore[i].toUpperCase();
              var replacement_was = param.split('_').join('/');
              method = method.split(replacement_was).join(param);
          }
  
          // replace AA by URL parameters
          var method_template = method;
          var match = [];
          if (match = method.match(/[A-Z_]{2,}/)) {
              for (var i = 0; i < match.length; i++) {
                  var param = match[i];
                  var param_l = param.toLowerCase();
                  method_template = method_template.split(param).join(':' + param_l);
                  if (typeof apiparams[param_l] == 'undefined') {
                      for (j = 0; j < 26; j++) {
                          method_template = method_template.split(String.fromCharCode(65 + j)).join('_' + String.fromCharCode(97 + j));
                      }
                      console.warn('To call the templated method "' + method_template + '", specify the parameter value for "' + param_l + '".');
                  }
                  method = method.split(param).join(apiparams[param_l]);
                  delete apiparams[param_l];
              }
          }
  
          // replace A-Z by _a-z
          for (i = 0; i < 26; i++) {
              method = method.split(String.fromCharCode(65 + i)).join('_' + String.fromCharCode(97 + i));
              method_template = method_template.split(String.fromCharCode(65 + i)).join('_' + String.fromCharCode(97 + i));
          }
  
          var httpmethod = _detectMethod(method_template, apiparams);
          var multipart = _detectMultipart(method_template);
  
          return _callApi(
              httpmethod,
              method,
              method_template,
              apiparams,
              multipart,
              app_only_auth,
              callback
          );
      };
  
      /**
       * Gets the OAuth authenticate URL for the current request token
       *
       * @return string The OAuth authenticate URL
       */
      var oauth_authenticate = function (params, callback) {
          if (typeof params.force_login == "undefined") {
              params.force_login = null;
          }
          if (typeof params.screen_name == "undefined") {
              params.screen_name = null;
          }
          if (_oauth_token == null) {
              console.warn('To get the authenticate URL, the OAuth token must be set.');
          }
          var url = _endpoint_oauth + 'oauth/authenticate?oauth_token=' + _url(_oauth_token);
          if (params.force_login === true) {
              url += "?force_login=1";
              if (params.screen_name !== null) {
                  url += "&screen_name=" + params.screen_name;
              }
          }
          callback(url);
          return true;
      };
  
      /**
       * Gets the OAuth authorize URL for the current request token
       *
       * @return string The OAuth authorize URL
       */
      var oauth_authorize = function (params, callback) {
          if (typeof params.force_login == "undefined") {
              params.force_login = null;
          }
          if (typeof params.screen_name == "undefined") {
              params.screen_name = null;
          }
          if (_oauth_token == null) {
              console.warn('To get the authorize URL, the OAuth token must be set.');
          }
          var url = _endpoint_oauth + 'oauth/authorize?oauth_token=' + _url(_oauth_token);
          if (params.force_login === true) {
              url += "?force_login=1";
              if (params.screen_name !== null) {
                  url += "&screen_name=" + params.screen_name;
              }
          }
          callback(url);
          return true;
      };
  
      /**
       * Gets the OAuth bearer token
       *
       * @return string The OAuth bearer token
       */
  
      var oauth2_token = function (callback) {
          if (_oauth_consumer_key == null) {
              console.warn('To obtain a bearer token, the consumer key must be set.');
          }
  
          if (typeof callback == "undefined") {
              var callback = function (reply) {};
          }
  
          var post_fields = "grant_type=client_credentials";
          var url = _endpoint_oauth + "oauth2/token";
  
          if (_use_proxy) {
              url = url.replace(
                  _endpoint_base,
                  _endpoint_proxy
              );
          }
  
          var xml;
          xml = Ti.Network.createHTTPClient();
          xml.open("POST", url, true);
          xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
          xml.setRequestHeader(
              (_use_proxy ? "X-" : "") + "Authorization",
              "Basic " + base64_encode(_oauth_consumer_key + ":" + _oauth_consumer_secret)
          );
  
          xml.onreadystatechange = function () {
              if (xml.readyState >= 4) {
                  var httpstatus = 12027;
                  try {
                      httpstatus = xml.status;
                  } catch (e) {}
                  var reply = _parseApiReply("oauth2/token", xml.responseText);
                  reply.httpstatus = httpstatus;
                  if (httpstatus == 200) {
                      setBearerToken(reply.access_token);
                  }
                  callback(reply);
              }
          };
          xml.send(post_fields);
  
      };
  
      /**
       * Signing helpers
       */
  
      /**
       * URL-encodes the given data
       *
       * @param mixed data
       *
       * @return mixed The encoded data
       */
      var _url = function (data) {
          if (typeof data == 'array') {
              return array_map([ // TODO
              this, '_url'], data);
          } else if ((/boolean|number|string/).test(typeof data)) {
              return encodeURIComponent(data).replace(/!/g, '%21').replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29').replace(/\*/g, '%2A');
          } else {
              return '';
          }
      }
  
      /**
       * Gets the base64-encoded SHA1 hash for the given data
       *
       * @param string data The data to calculate the hash from
       *
       * @return string The hash
       */
      var _sha1 = function (data) {
          if (_oauth_consumer_secret == null) {
              console.warn('To generate a hash, the consumer secret must be set.');
          }
          if (typeof b64_hmac_sha1 != 'function') {
              console.warn('To generate a hash, the Javascript SHA1.js must be available.');
          }
          b64pad = '=';
          return b64_hmac_sha1(_oauth_consumer_secret + '&' + (_oauth_token_secret != null ? _oauth_token_secret : ''), data);
      };
  
      var base64_encode = function (data) {
          // http://kevin.vanzonneveld.net
          // +   original by: Tyler Akins (http://rumkin.com)
          // +   improved by: Bayron Guevara
          // +   improved by: Thunder.m
          // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
          // +   bugfixed by: Pellentesque Malesuada
          // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
          // +   improved by: Rafał Kukawski (http://kukawski.pl)
          // *     example 1: base64_encode('Kevin van Zonneveld');
          // *     returns 1: 'S2V2aW4gdmFuIFpvbm5ldmVsZA=='
          // mozilla has this native
          // - but breaks in 2.0.0.12!
          //if (typeof this.window['btoa'] == 'function') {
          //    return btoa(data);
          //}
          var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
          var o1, o2, o3, h1, h2, h3, h4, bits, i = 0,
              ac = 0,
              enc = "",
              tmp_arr = [];
  
          if (! data) {
              return data;
          }
  
          do { // pack three octets into four hexets
              o1 = data.charCodeAt(i++);
              o2 = data.charCodeAt(i++);
              o3 = data.charCodeAt(i++);
  
              bits = o1 << 16 | o2 << 8 | o3;
  
              h1 = bits >> 18 & 0x3f;
              h2 = bits >> 12 & 0x3f;
              h3 = bits >> 6 & 0x3f;
              h4 = bits & 0x3f;
  
              // use hexets to index into b64, and append result to encoded string
              tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4);
          } while (i < data.length);
  
          enc = tmp_arr.join('');
  
          var r = data.length % 3;
  
          return (r ? enc.slice(0, r - 3) : enc) + '==='.slice(r || 3);
      };
  
      var http_build_query = function (formdata, numeric_prefix, arg_separator) {
          // http://kevin.vanzonneveld.net
          // +     original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
          // +     improved by: Legaev Andrey
          // +     improved by: Michael White (http://getsprink.com)
          // +     improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
          // +     improved by: Brett Zamir (http://brett-zamir.me)
          // +        revised by: stag019
          // +     input by: Dreamer
          // +     bugfixed by: Brett Zamir (http://brett-zamir.me)
          // +     bugfixed by: MIO_KODUKI (http://mio-koduki.blogspot.com/)
          // %                note 1: If the value is null, key and value is skipped in http_build_query of PHP. But, phpjs is not.
          var value, key, tmp = [],
              that = http_build_query;
  
          var _http_build_query_helper = function (key, val, arg_separator) {
              var k, tmp = [];
              if (val === true) {
                  val = "1";
              } else if (val === false) {
                  val = "0";
              }
              if (val != null) {
                  if(typeof(val) === "object") {
                      for (k in val) {
                          if (val[k] != null) {
                              tmp.push(_http_build_query_helper(key + "[" + k + "]", val[k], arg_separator));
                          }
                      }
                      return tmp.join(arg_separator);
                  } else if (typeof(val) !== "function") {
                      return _url(key) + "=" + _url(val);
                  } else {
                      throw new Error('There was an error processing for http_build_query().');
                  }
              } else {
                  return '';
              }
          };
  
          if (!arg_separator) {
              arg_separator = "&";
          }
          for (key in formdata) {
              value = formdata[key];
              if (numeric_prefix && !isNaN(key)) {
                  key = String(numeric_prefix) + key;
              }
              var query=_http_build_query_helper(key, value, arg_separator);
              if(query != '') {
                  tmp.push(query);
              }
          }
  
          return tmp.join(arg_separator);
      };
  
      /**
       * Generates a (hopefully) unique random string
       *
       * @param int optional length The length of the string to generate
       *
       * @return string The random string
       */
      var _nonce = function (length) {
          if (typeof length == 'undefined') {
              var length = 8;
          }
          if (length < 1) {
              console.warn('Invalid nonce length.');
          }
          var nonce = '';
          for (var i = 0; i < length; i++) {
              var character = Math.floor(Math.random() * 61);
              nonce += '0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz'.substring(character, character + 1);
          }
          return nonce;
      };
  
      var _ksort = function (inputArr) {
          // http://kevin.vanzonneveld.net
          // +   original by: GeekFG (http://geekfg.blogspot.com)
          // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
          // +   improved by: Brett Zamir (http://brett-zamir.me)
          var tmp_arr = {},
          keys = [],
          sorter, i, k, that = this,
          strictForIn = false,
          populateArr = {};
  
          sorter = function (a, b) {
              var aFloat = parseFloat(a),
              bFloat = parseFloat(b),
              aNumeric = aFloat + '' === a,
              bNumeric = bFloat + '' === b;
              if (aNumeric && bNumeric) {
                  return aFloat > bFloat ? 1 : aFloat < bFloat ? -1 : 0;
              } else if (aNumeric && !bNumeric) {
                  return 1;
              } else if (!aNumeric && bNumeric) {
                  return -1;
              }
              return a > b ? 1 : a < b ? -1 : 0;
          };
  
          // Make a list of key names
          for (k in inputArr) {
              if (inputArr.hasOwnProperty(k)) {
                  keys.push(k);
              }
          }
          keys.sort(sorter);
          return keys;
      };
  
      /**
       * Clone objects
       * 
       * @param object obj    The object to clone
       *
       * @return object clone The cloned object
       */
      var _clone = function (obj) {
          var clone = {};
          for (var i in obj) {
              if (typeof(obj[i]) == "object") {
                  clone[i] = clone(obj[i]);
              } else {
                  clone[i] = obj[i];
              }
          }
          return clone;
      };
  
      /**
       * Generates an OAuth signature
       *
       * @param string          httpmethod    Usually either 'GET' or 'POST' or 'DELETE'
       * @param string          method        The API method to call
       * @param array  optional params        The API call parameters, associative
       * @param bool   optional append_to_get Whether to append the OAuth params to GET
       *
       * @return string Authorization HTTP header
       */
      var _sign = function (httpmethod, method, params, append_to_get) {
          if (typeof params == 'undefined') {
              var params = {};
          }
          if (typeof append_to_get == "undefined") {
              var append_to_get = false;
          }
          if (_oauth_consumer_key == null) {
              console.warn('To generate a signature, the consumer key must be set.');
          }
          var sign_params = {
              consumer_key: _oauth_consumer_key,
              version: '1.0',
              timestamp: Math.round(new Date().getTime() / 1000),
              nonce: _nonce(),
              signature_method: 'HMAC-SHA1'
          };
          var sign_base_params = {};
          for (var key in sign_params) {
              var value = sign_params[key];
              sign_base_params['oauth_' + key] = _url(value);
          }
          if (_oauth_token != null) {
              sign_base_params['oauth_token'] = _url(_oauth_token);
          }
          oauth_params = _clone(sign_base_params);
          for (var key in params) {
              var value = params[key];
              sign_base_params[key] = _url(value);
          }
          var keys = _ksort(sign_base_params);
          var sign_base_string = '';
          for (var i=0;i<keys.length;i++) {
              var key = keys[i];
              var value = sign_base_params[key];
              sign_base_string += key + '=' + value + '&';
          }
          sign_base_string = sign_base_string.substring(0, sign_base_string.length - 1);
          var signature = _sha1(httpmethod + '&' + _url(method) + '&' + _url(sign_base_string));
  
          params = append_to_get ? sign_base_params : oauth_params;
          params['oauth_signature'] = signature;
          if (append_to_get) {
              var authorization = '';
              for(var key in params) {
                  var value = params[key];
                  authorization += key + "=" + _url(value) + "&";
              }
              return authorization.substring(0, authorization.length - 1);
          }
          var authorization = 'OAuth ';
          for (var key in params) {
              var value = params[key];
              authorization += key + '="' + _url(value) + '", ';
          }
          return authorization.substring(0, authorization.length - 2);
      };
  
      /**
       * Detects HTTP method to use for API call
       *
       * @param string method The API method to call
       * @param array  params The parameters to send along
       *
       * @return string The HTTP method that should be used
       */
      var _detectMethod = function (method, params) {
          // multi-HTTP method endpoints
          switch(method) {
              case 'account/settings':
                  method = params.length ? method + '__post' : method;
                  break;
          }
  
          var httpmethods = {};
          httpmethods['GET'] = [
              // Timelines
              'statuses/mentions_timeline',
              'statuses/user_timeline',
              'statuses/home_timeline',
              'statuses/retweets_of_me',
  
              // Tweets
              'statuses/retweets/:id',
              'statuses/show/:id',
              'statuses/oembed',
  
              // Search
              'search/tweets',
  
              // Direct Messages
              'direct_messages',
              'direct_messages/sent',
              'direct_messages/show',
  
              // Friends & Followers
              'friendships/no_retweets/ids',
              'friends/ids',
              'followers/ids',
              'friendships/lookup',
              'friendships/incoming',
              'friendships/outgoing',
              'friendships/show',
              'friends/list',
              'followers/list',
  
              // Users
              'account/settings',
              'account/verify_credentials',
              'blocks/list',
              'blocks/ids',
              'users/lookup',
              'users/show',
              'users/search',
              'users/contributees',
              'users/contributors',
              'users/profile_banner',
  
              // Suggested Users
              'users/suggestions/:slug',
              'users/suggestions',
              'users/suggestions/:slug/members',
  
              // Favorites
              'favorites/list',
  
              // Lists
              'lists/list',
              'lists/statuses',
              'lists/memberships',
              'lists/subscribers',
              'lists/subscribers/show',
              'lists/members/show',
              'lists/members',
              'lists/show',
              'lists/subscriptions',
  
              // Saved searches
              'saved_searches/list',
              'saved_searches/show/:id',
  
              // Places & Geo
              'geo/id/:place_id',
              'geo/reverse_geocode',
              'geo/search',
              'geo/similar_places',
  
              // Trends
              'trends/place',
              'trends/available',
              'trends/closest',
  
              // OAuth
              'oauth/authenticate',
              'oauth/authorize',
  
              // Help
              'help/configuration',
              'help/languages',
              'help/privacy',
              'help/tos',
              'application/rate_limit_status'
          ];
          httpmethods['POST'] = [
              // Tweets
              'statuses/destroy/:id',
              'statuses/update',
              'statuses/retweet/:id',
              'statuses/update_with_media',
  
              // Direct Messages
              'direct_messages/destroy',
              'direct_messages/new',
  
              // Friends & Followers
              'friendships/create',
              'friendships/destroy',
              'friendships/update',
  
              // Users
              'account/settings__post',
              'account/update_delivery_device',
              'account/update_profile',
              'account/update_profile_background_image',
              'account/update_profile_colors',
              'account/update_profile_image',
              'blocks/create',
              'blocks/destroy',
              'account/update_profile_banner',
              'account/remove_profile_banner',
  
              // Favorites
              'favorites/destroy',
              'favorites/create',
  
              // Lists
              'lists/members/destroy',
              'lists/subscribers/create',
              'lists/subscribers/destroy',
              'lists/members/create_all',
              'lists/members/create',
              'lists/destroy',
              'lists/update',
              'lists/create',
              'lists/members/destroy_all',
  
              // Saved Searches
              'saved_searches/create',
              'saved_searches/destroy/:id',
  
              // Places & Geo
              'geo/place',
  
              // Spam Reporting
              'users/report_spam',
  
              // OAuth
              'oauth/access_token',
              'oauth/request_token',
              'oauth2/token',
              'oauth2/invalidate_token'
          ];
          for (var httpmethod in httpmethods) {
              if (httpmethods[httpmethod].indexOf(method) > -1) {
                  return httpmethod;
              }
          }
          console.warn('Can\'t find HTTP method to use for "' + method + '".');
      };
  
      /**
       * Detects if API call should use multipart/form-data
       *
       * @param string method The API method to call
       *
       * @return bool Whether the method should be sent as multipart
       */
      var _detectMultipart = function (method) {
          var multiparts = [
              // Tweets
              'statuses/update_with_media',
  
              // Users
              'account/update_profile_background_image',
              'account/update_profile_image',
              'account/update_profile_banner'
          ];
          return multiparts.indexOf(method) > -1;
      };
  
      /**
       * Builds the complete API endpoint url
       *
       * @param string method           The API method to call
       * @param string method_template  The API method template to call
       *
       * @return string The URL to send the request to
       */
      var _getEndpoint = function (method, method_template) {
          if (method.substring(0, 5) == 'oauth') {
              var url = _endpoint_oauth + method;
          } else {
              var url = _endpoint + method + '.json';
          }
          return url;
      };
  
      /**
       * Calls the API using cURL
       *
       * @param string          httpmethod      The HTTP method to use for making the request
       * @param string          method          The API method to call
       * @param string          method_template The templated API method to call
       * @param array  optional params          The parameters to send along
       * @param bool   optional multipart       Whether to use multipart/form-data
       * @param bool   optional $app_only_auth  Whether to use app-only bearer authentication
       * @param function        callback        The function to call with the API call result
       *
       * @return mixed The API reply, encoded in the set return_format
       */
  
      var _callApi = function (httpmethod, method, method_template, params, multipart, app_only_auth, callback) {
          if (typeof params == 'undefined') {
              var params = {};
          }
          if (typeof multipart == 'undefined') {
              var multipart = false;
          }
          if (typeof app_only_auth == 'undefined') {
              var app_only_auth = false;
          }
          if (typeof callback != 'function') {
              var callback = function (reply) {};
          }
  
          var url = _getEndpoint(method, method_template);
          var authorization = null;
  
          var xml = Ti.Network.createHTTPClient();
          if (httpmethod == 'GET') {
              var url_with_params = url;
              if (JSON.stringify(params) != "{}") {
                  url_with_params += '?' + http_build_query(params);
              }
              authorization = _sign(httpmethod, url, params);
  
              // append auth params to GET url for IE7-9, to send via JSONP
              if (_use_jsonp) {
                  if (JSON.stringify(params) != "{}") {
                      url_with_params += '&';
                  } else {
                      url_with_params += '?';
                  }
                  var callback_name = _nonce();
                  window[callback_name] = function (reply) {
                      reply.httpstatus = 200;
                      callback(reply);
                  };
                  params.callback = callback_name;
                  url_with_params = url + "?" + _sign(httpmethod, url, params, true);
                  var tag = document.createElement("script");
                  tag.type = "text/javascript";
                  tag.src = url_with_params;
                  var body = document.getElementsByTagName("body")[0];
                  body.appendChild(tag);
                  return;
  
              } else if (_use_proxy) {
                  url_with_params = url_with_params.replace(
                      _endpoint_base,
                      _endpoint_proxy
                  );
              }
              xml.open(httpmethod, url_with_params, true);
          } else {
              if (_use_jsonp) {
                  console.warn('Sending POST requests is not supported for IE7-9.');
                  return;
              }
              authorization = _sign(httpmethod, url, {});
              if (! multipart) {
                  authorization = _sign(httpmethod, url, params);
                  params        = http_build_query(params);
              }
              post_fields = params;
              if (_use_proxy) {
                  url = url.replace(
                      _endpoint_base,
                      _endpoint_proxy
                  );
              }
              xml.open(httpmethod, url, true);
              if (multipart) {
                  xml.setRequestHeader("Content-Type", "multipart/form-data");
              } else {
                  xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
              }
          }
          if (app_only_auth) {
              if (_oauth_consumer_key == null) {
                  console.warn('To make an app-only auth API request, the consumer key must be set.');
              }
              // automatically fetch bearer token, if necessary
              if (_oauth_bearer_token == null) {
                  return oauth2_token(function (reply) {
                      _callApi(httpmethod, method, method_template, params, multipart, app_only_auth, callback);
                  });
              }
              authorization = 'Bearer ' + _oauth_bearer_token;
          }
          if (authorization !== null) {
              xml.setRequestHeader((_use_proxy ? "X-" : "") + "Authorization", authorization);
          }
          xml.onreadystatechange = function () {
              if (xml.readyState >= 4) {
                  var httpstatus = 12027;
                  try {
                      httpstatus = xml.status;
                  } catch (e) {}
                  
                  if(!httpstatus){
                  	httpstatus = 12027;
                  }
                  
                  var reply = _parseApiReply(method_template, xml.responseText);
                  if(!reply){
                  	callback(null);
                  }
                  reply.httpstatus = httpstatus;
                  callback(reply);
              }
          };
          xml.send(httpmethod == "GET" ? null : post_fields);
          return true;
      };
  
      /**
       * Parses the API reply to encode it in the set return_format
       *
       * @param string method The method that has been called
       * @param string reply  The actual reply, JSON-encoded or URL-encoded
       *
       * @return array|object The parsed reply
       */
      var _parseApiReply = function (method, reply) {
          if (reply == '[]' || reply == null) {
              return [];
          }
          var parsed = false;
          try {
              parsed = JSON.parse(reply);
          } catch (e) {
              parsed = {};
              if (reply.indexOf('<' + '?xml version="1.0" encoding="UTF-8"?' + '>') === 0) {
                  // we received XML...
                  // since this only happens for errors,
                  // don't perform a full decoding
                  parsed["request"] = reply.match(/<request>(.*)<\/request>/)[1];
                  parsed["error"] = reply.match(/<error>(.*)<\/error>/)[1];
              } else {
                  // assume query format
                  var elements = reply.split("&");
                  for (var i = 0; i < elements.length; i++) {
                      var element = elements[i].split("=", 2);
                      if (element.length > 1) {
                          parsed[element[0]] = unescape(element[1]);
                      } else {
                          parsed[element[0]] = null;
                      }
                  }
              }
          }
          return parsed;
      };
  
      return {
          setConsumerKey: setConsumerKey,
          getVersion: getVersion,
          setToken: setToken,
          setBearerToken: setBearerToken,
          setUseProxy: setUseProxy,
          __call: __call,
          oauth_authenticate: oauth_authenticate,
          oauth_authorize: oauth_authorize,
          oauth2_token: oauth2_token
      };
  };
  
  module.exports = Codebird;