// ijax_v1.js
//
// Ijax - object for making javascript requests to an Iguana2 Ijax server
//
// Copyright: Iguana2 Pty. Ltd.
//
// Refer to http://iguana2.com/ijax-doc for documentation.
//

"use strict";

/*global document, XMLHttpRequest, ActiveXObject, Math, Date */

var Ijax;

Ijax = {
 version: 1,
 copyright: '(c) 2010 Iguana2 Pty. Ltd.',

 // AJAX methods 

 METHOD_DYNAMIC_SCRIPTS: 1,
 METHOD_XML_HTTP_REQUEST: 2,

 // Errors

 ERR_ITEM_NOT_FOUND: 1,
 ERR_EXCHANGE_NOT_FOUND: 2,
 ERR_EXCHANGE_NOT_ALLOWED: 3,
 ERR_CODE_NOT_ALLOWED: 4,
 ERR_PARAM_TOO_LONG: 5,
 ERR_PARAM_BAD_CHARS: 6,
 ERR_STOCK_NOT_INCLUDED: 7,
 ERR_INDEX_NOT_INCLUDED: 8,
 ERR_NOTHING_TO_DO: 9,
 ERR_FMT_NOT_ALLOWED: 10,
 ERR_SITE_REQUIRED: 11,
 ERR_MODULE_NOT_ALLOWED: 12,
 ERR_BAD_VALUE: 13,
 ERR_LIVE_GUEST_NOT_ALLOWED: 14,
 ERR_LIVE_USER_NOT_ALLOWED: 15,
 ERR_DELAYED_DATA_NOT_AVAILABLE: 16,
 ERR_STOCKS_NOT_ALLOWED: 17,
 ERR_INDICES_NOT_ALLOWED: 18,
 ERR_DEPTH_LEVELS_EXCEEDED: 19,
 ERR_ORDERS_NOT_ALLOWED: 20,
 ERR_EXT_ORDER_INFO_NOT_ALLOWED: 21,
 ERR_VERSION: 22,
 ERR_STOCK_NOT_CURRENT: 23,
 ERR_FIRM_NOT_ALLOWED: 24,
 ERR_ORDER_TOTALS_NOT_ALLOWED: 25,
 ERR_STORE_NOT_ALLOWED: 26,
 ERR_STORE_NOT_FOUND: 27,
 ERR_STORE_FAILED: 28,
 ERR_STOCK_NEEDED: 29,
 ERR_INDEX_NEEDED: 30,
 ERR_ISSUER_NEEDED: 31,
 ERR_RECENT_NEWS_NOT_ALLOWED: 32,
 ERR_RECENT_NEWS_COUNT_EXCEEDED: 33,
 ERR_DATE_NOT_ALLOWED: 34,
 ERR_MAX_COUNT_EXCEEDED: 35,
 ERR_PARAM_OVERRIDEN: 36,
 ERR_NOTHING_TO_DO_YET: 37,
 ERR_REQUEST_MUST_BE_LIVE: 38,
 ERR_REQUEST_MUST_NOT_BE_LIVE: 39,
 ERR_GZIP_NOT_ALLOWED: 40,
 ERR_ORIGIN_NOT_ALLOWED: 41,
 ERR_IP_NOT_ALLOWED: 42,
 ERR_AUTH_REQUIRED: 43,
 ERR_AUTH_FAILED: 44,
 ERR_AUTH_NOT_FOUND: 45,
 ERR_STOCK_INFO_NOT_ALLOWED: 46,

 ERR_HTTP: 90,
 ERR_JSON: 91,
 ERR_EMPTY_RESPONSE: 92,
 ERR_NO_TRANSPORT: 93
};

///////////////////////////////////////////////////////////////////////////////
//                                                                           //
//                               Initialization                              //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

Ijax.initialize = function(site, default_params) 
{  
  Ijax.set_default_param('site', site);
  Ijax.set_default_param('auth', '');
  Ijax.set_default_param('exchange', 'ASX');
  Ijax.set_default_param('baseurl', 'http://ijax.iguana2.com/');
  Ijax.set_default_param('news_baseurl', 'http://news.iguana2.com/');
  Ijax.set_default_param('method', Ijax.METHOD_DYNAMIC_SCRIPTS);
  Ijax.set_default_param('onFailure', function(error) {});
  Ijax.set_default_param('onAuthenticate', function() {});
  Ijax.set_default_params(Ijax.internal.maybe(default_params));
  return true;
};

Ijax.set_default_param = function(param, value) 
{  
  Ijax.internal.default_params[param] = value;
};

Ijax.set_default_params = function(params) 
{  
  for (var key in params) {
    if (params.hasOwnProperty(key)) {
      Ijax.internal.default_params[key] = params[key];
    }
  }
};

Ijax.authenticate = function(token) 
{  
  Ijax.set_default_param('auth', token);
  ++Ijax.internal.reauth_counter;
};

///////////////////////////////////////////////////////////////////////////////
//                                                                           //
//                                  Requests                                 //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

Ijax.quote = function(code, on_result, params) 
{
    params = Ijax.internal.maybe(params);
    params.code = code;
    params.onResult = on_result;
    if (on_result && !params.onSuccess) {
        params.onSuccess = function(response) {
            params.response = response;
            params.onResult(response.stock, 
                            response.quote, 
                            Ijax.internal.maybe(response.recent_news_items));
        };
    }
    return Ijax.internal.request('quote', params);
};

Ijax.index_quote = function(code, on_result, params) 
{
    params = Ijax.internal.maybe(params);
    params.code = code;
    params.onResult = on_result;
    if (params.onResult && !params.onSuccess) {
        params.onSuccess = function(response) {
            params.response = response;
            params.onResult(response.index, 
                            response.index_quote);
        };
    }
    return Ijax.internal.request('index-quote', params);
};

Ijax.depth = function(code, on_result, params) 
{
    params = Ijax.internal.maybe(params);
    params.code = code;
    params.onResult = on_result;
    if (params.onResult && !params.onSuccess) {
        params.onSuccess = function(response) {
            params.response = response;
            params.onResult(response.stock, 
                            response.quote, 
                            Ijax.internal.maybe(response.depth.bids.levels),
                            Ijax.internal.maybe(response.depth.offers.levels),
                            Ijax.internal.maybe(response.recent_news_items));
        };
    }
    return Ijax.internal.request('depth', params);
};

Ijax.trades = function(code, on_result, params) 
{
    params = Ijax.internal.maybe(params);
    params.code = code;
    params.onResult = on_result;
    if (params.onResult && !params.onSuccess) {
        params.onSuccess = function(response) {
            params.response = response;
            params.onResult(response.stock, 
                            response.quote, 
                            Ijax.internal.maybe(response.trades.trades_list));
        };
    }
    return Ijax.internal.request('trades', params);
};

Ijax.watchlist = function(codes, on_result, params) 
{
    params = Ijax.internal.maybe(params);
    params.codes = codes;
    params.onResult = on_result;
    if (params.onResult && !params.onSuccess) {
        params.onSuccess = function(response) {
            params.response = response;
            params.onResult(response.watchlist_members);
        };
    }
    return Ijax.internal.request('watchlist', params);
};

Ijax.indexlist = function(code, on_result, params) 
{
    params = Ijax.internal.maybe(params);
    params.code = code;
    params.onResult = on_result;
    if (params.onResult && !params.onSuccess) {
        params.onSuccess = function(response) {
          params.response = response;
          params.onResult(response.index, response.quote, response.index_members);
        };
    }
    return Ijax.internal.request('indexlist', params);
};

Ijax.recent_news = function(exchange, on_result, params) 
{
    params = Ijax.internal.maybe(params);
    params.exchange = exchange;
    params.onResult = on_result;
    if (params.onResult && !params.onSuccess) {
        params.onSuccess = function(response) {
            params.response = response;
            params.onResult(response.exchange,
                            response.recent_news.news_items);
        };
    }
    return Ijax.internal.request('recent-news', params);
};

Ijax.news_history = function(code, on_result, params) 
{
    params = Ijax.internal.maybe(params);
    params.code = code;
    params.onResult = on_result;
    if (params.onResult && !params.onSuccess) {
        params.onSuccess = function(response) {
            params.response = response;
            params.onResult(response.issuer,
                            response.news_history.news_items);
        };
    }
    return Ijax.internal.request('news-history', params);
};

Ijax.stock_history = function(code, on_result, params) 
{
    params = Ijax.internal.maybe(params);
    params.code = code;
    params.onResult = on_result;
    if (params.onResult && !params.onSuccess) {
        params.onSuccess = function(response) {
            params.response = response;
            params.onResult(response.stock,
                            response.stock_history.stock_days);
        };
    }
    return Ijax.internal.request('stock-history', params);
};

Ijax.index_history = function(code, on_result, params) 
{
    params = Ijax.internal.maybe(params);
    params.code = code;
    params.onResult = on_result;
    if (params.onResult && !params.onSuccess) {
        params.onSuccess = function(response) {
            params.response = response;
            params.onResult(response.index,
                            response.index_history.index_days);
        };
    }
    return Ijax.internal.request('index-history', params);
};

Ijax.market_scan = function(keyword, on_result, params)
{
    params = Ijax.internal.maybe(params);
    params.keyword = keyword;
    params.onResult = on_result;
    if (params.onResult && !params.onSuccess) {
        params.onSuccess = function(response) {
            params.response = response;
            params.onResult(response.market_scan.market_scan_items);
        };
    }
    return Ijax.internal.request('market', params);
};

Ijax.top_rises = function(on_result, params)
{
    return Ijax.market_scan('RISES', on_result, params);
};

Ijax.top_falls = function(on_result, params)
{
    return Ijax.market_scan('FALLS', on_result, params);
};

Ijax.top_volume = function(on_result, params)
{
    return Ijax.market_scan('VOLUME', on_result, params);
};

Ijax.top_value = function(on_result, params)
{
    return Ijax.market_scan('VALUE', on_result, params);
};

Ijax.market_summary = function(on_result, params)
{
    params = Ijax.internal.maybe(params);
    params.onResult = on_result;
    if (params.onResult && !params.onSuccess) {
        params.onSuccess = function(response) {
            params.response = response;
            params.onResult(response.market_summary);
        };
    }
    return Ijax.internal.request('market-summary', params);
};

Ijax.code_search = function(code_prefix, keyword, on_result, params) 
{
    params = Ijax.internal.maybe(params);
    params.code = code_prefix;
    params.keyword = keyword;
    params.onResult = on_result;
    if (params.onResult && !params.onSuccess) {
        params.onSuccess = function(response) {
            params.response = response;
            params.onResult(response.matches.matched_items);
        };
    }
    return Ijax.internal.request('code-search', params);
};

///////////////////////////////////////////////////////////////////////////////
//                                                                           //
//                                 Formatters                                //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

Ijax.set_fmt_style = function(field, value)
{
  Ijax.internal.fmt[field] = value;
};

Ijax.set_fmt_styles = function(values)
{
  for (var key in values) {
    if (values.hasOwnProperty(key)) {
      Ijax.internal.fmt[key] = values[key];
    }
  }
};

Ijax.fmt_price = function(price)
{
  return (Ijax.internal.fmt.price_in_dollars ? 
          Ijax.fmt_dollars_price(price) : 
          Ijax.fmt_cents_price(price));
};

Ijax.fmt_dollars_price = function(price)
{
  if (price === 0) {
    return Ijax.internal.fmt.no_price_html;
  }
  // format a stock price in dollars to *at least* 2 decimal places
  var s = price.toFixed(4).replace(/0?0$/, '');
  return Ijax.internal.fmt.dollars_price_prefix_html+s;
};

Ijax.fmt_cents_price = function(price)
{
  if (price === 0) {
    return Ijax.internal.fmt.no_price_html;
  }
  // format a stock price in cents
  return (price*100).toFixed(2).replace(/0+$/, '').replace(/\.$/, '');
};

Ijax.fmt_volume = function(volume)
{
  if (volume === 0) {
    return Ijax.internal.fmt.no_volume_html;
  }
  // format volume with commas
  var s = volume.toFixed(0).replace(/(.)(...)$/, '$1,$2');
  for (var i = 0; i < 6; i++) {
        s = s.replace(/([0-9])(...,)/, '$1,$2');
  }
  return s;
};

Ijax.fmt_turnover = function(turnover)
{
  if (turnover === 0) {
    return Ijax.internal.fmt.no_turnover_html;
  }
  // format turnover with commas to 0 decimal places
  var s = turnover.toFixed(0).replace(/(.)(...)$/, '$1,$2');
  for (var i = 0; i < 6; i++) {
        s = s.replace(/([0-9])(...,)/, '$1,$2');
  }
  return Ijax.internal.fmt.turnover_prefix_html+s;
};

Ijax.fmt_value = function(value)
{
  if (value === 0) {
    return Ijax.internal.fmt.no_value_html;
  }
  // format value with commas to 2 decimal places
  var s = value.toFixed(2).replace(/(.)(...)\./, '$1,$2.');
  for (var i = 0; i < 6; i++) {
        s = s.replace(/([0-9])(...,)/, '$1,$2');
  }
  return Ijax.internal.fmt.value_prefix_html+s;
};

Ijax.fmt_change = function(price_change)
{
  return (Ijax.internal.fmt.price_in_dollars ? 
          Ijax.fmt_dollars_change(price_change) : 
          Ijax.fmt_cents_change(price_change));
};

Ijax.fmt_dollars_change = function(change)
{
  if (Math.abs(change) < 0.0001) {
    return Ijax.internal.fmt.no_change_html;
  }
  // format a stock price change in dollars to *at least* 2 decimal places
  // optionally prefix a sign and wrap it in a span tag
  var s = Math.abs(change).toFixed(4).replace(/0?0$/, '');
  return Ijax.internal.fmt_change(change, s);
};

Ijax.fmt_cents_change = function(change)
{
  if (Math.abs(change) < 0.0001) {
    return Ijax.internal.fmt.no_change_html;
  }
  // format a stock price change in cents
  // optionally prefix a sign and wrap it in a span tag
  var s = Math.abs(change*100).toFixed(2).replace(/0+$/, '').replace(/\.$/,'');
  return Ijax.internal.fmt_change(change, s);
};

Ijax.fmt_index_price = function(price)
{
  if (price === 0) {
    return Ijax.internal.fmt.no_price_html;
  }
  // format an index price to 1 or 2 decimal places
  return price.toFixed(2).replace(/0$/, '');
};

Ijax.fmt_index_change = function(change)
{
  if (Math.abs(change) < 0.01) {
    return Ijax.internal.fmt.no_change_html;
  }
  // format an index change
  // optionally prefix a sign and wrap it in a span tag
  var s = Math.abs(change).toFixed(2).replace(/0$/, '');
  return Ijax.internal.fmt_change(change, s);
};

Ijax.fmt_pct_change = function(pct_change)
{
  if (Math.abs(pct_change) < 0.01) {
    return Ijax.internal.fmt.no_pct_change_html;
  }
  // format a pct change
  // optionally prefix a sign and wrap it in a span tag
  var s = Math.abs(pct_change).toFixed(2).replace(/(\.0)?0$/, '');
  s += Ijax.internal.fmt.pct_change_suffix_html;
  return Ijax.internal.fmt_change(pct_change, s);
};

Ijax.fmt_date = function(date)
{
  return Ijax.internal.fmt_time(date, Ijax.internal.fmt.date_fmt);
};

Ijax.fmt_time = function(time)
{
  return Ijax.internal.fmt_time(time, Ijax.internal.fmt.time_fmt);
};

Ijax.fmt_datetime = function(time)
{
  return Ijax.internal.fmt_time(time, Ijax.internal.fmt.datetime_fmt);
};

Ijax.fmt_time_ex = function(time, fmt)
{
  return Ijax.internal.fmt_time(time, fmt);
};

///////////////////////////////////////////////////////////////////////////////
//                                                                           //
//                            Set Document Elements                          //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

Ijax.set_elem = function(id, text)
{
        var elem = document.getElementById(id);
        if (elem) {
                elem.innerHTML = text;
        }
};

Ijax.set_price_elem = function(id, price)
{
        Ijax.set_elem(id, Ijax.fmt_price(price));
};

Ijax.set_change_elem = function(id, price_change)
{
        Ijax.set_elem(id, Ijax.fmt_change(price_change));
};

Ijax.set_volume_elem = function(id, volume)
{
        Ijax.set_elem(id, Ijax.fmt_volume(volume));
};

Ijax.set_turnover_elem = function(id, turnover)
{
        Ijax.set_elem(id, Ijax.fmt_turnover(turnover));
};

Ijax.set_value_elem = function(id, value)
{
        Ijax.set_elem(id, Ijax.fmt_value(value));
};

Ijax.set_index_price_elem = function(id, price)
{
        Ijax.set_elem(id, Ijax.fmt_index_price(price));
};

Ijax.set_index_change_elem = function(id, index_change)
{
        Ijax.set_elem(id, Ijax.fmt_index_change(index_change));
};

Ijax.set_pct_change_elem = function(id, pct_change)
{
        Ijax.set_elem(id, Ijax.fmt_pct_change(pct_change));
};

Ijax.set_time_elem = function(id, time)
{
        Ijax.set_elem(id, Ijax.fmt_time(time));
};

Ijax.set_date_elem = function(id, date)
{
        Ijax.set_elem(id, Ijax.fmt_date(date));
};

Ijax.set_datetime_elem = function(id, datetime)
{
        Ijax.set_elem(id, Ijax.fmt_datetime(datetime));
};

///////////////////////////////////////////////////////////////////////////////
//                                                                           //
//                                  Helpers                                  //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

Ijax.parse_date = function(time)
{
  var v = time.split(/[ :\-]/);
  if (v !== null) {
    if (v.length == 3) {
      return new Date(v[0], v[1] - 1, v[2]);
    }
    else if (v.length == 6) {
      return new Date(v[0], v[1] - 1, v[2], v[3], v[4], v[5]);
    }
  }
  return null;
};

Ijax.iguana_news_url = function(issuer, item)
{
  return (Ijax.internal.default_params.news_baseurl+
	  Ijax.internal.default_params.site+'/'+
	  issuer.exchange+'/'+
	  issuer.code+'/'+
	  item.reference);
};

///////////////////////////////////////////////////////////////////////////////
//                                                                           //
//                                Internal Use                               //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

Ijax.internal = { 
 default_params: {
 },

 fmt: {
  price_in_dollars: 1,
  dollars_price_prefix_html: '',
  no_price_html: '',
  up_class: '',
  up_prefix_html: '+',
  down_class: '',
  down_prefix_html: '-',
  no_change_html: '',
  no_pct_change_html: '',
  no_volume_html: '',
  turnover_prefix_html: '$',
  no_turnover_html: '',
  value_prefix_html: '$',
  no_value_html: '',
  pct_change_suffix_html: '%',
  date_fmt: 'Y-m-d',
  time_fmt: 'H:i:s',
  datetime_fmt: 'Y-m-d H:i:s'
 },

 valid_params: {
   site: 1,
   live: 1,
   gzip: 1,
   store: 1,
   store_id: 1,
   from: 1,
   count: 1,
   code: 1,
   codes: 1,
   exchange: 1,
   include_stocks: 1,
   include_indices: 1,   
   include_options: 1,   
   keyword: 1,   
   depth_levels: 1,   
   include_orders: 1,
   include_ext_order_info: 1,
   include_order_totals: 1,
   include_recent_news: 1,
   sensitive_news_only: 1,
   recent_news_count: 1,
   include_related_news: 1,
   group_by_price: 1,
   include_off_market: 1,
   time_order: 1,
   from_date: 1,
   to_date: 1,
   date_range: 1,
   benchmark_shares_only: 1
 },
 
 maybe: 
  function(a) { try { var x = a[0]; return a; } catch (e) { return []; } },

 counter: 0,
 requests: {},
 reauth_counter: 0,
 min_reauth_counter: -1
};

Ijax.internal.url = function(module, params) 
{
  var key, url = Ijax.internal.default_params.baseurl;
  if (url.indexOf('?') == -1) {
    url += module+'?';
  }
  else {
    url += module+'&';
  }
  url += 'fmt=json';
  if (Ijax.internal.default_params.auth) {
    url += '&auth='+Ijax.internal.default_params.auth;
  }
  for (key in params) {
    if (Ijax.internal.valid_params[key]) {
      url += '&'+key+'='+params[key];
    }
  }
  for (key in Ijax.internal.default_params) {
    if (!params[key] && Ijax.internal.valid_params[key]) {
      url += '&'+key+'='+Ijax.internal.default_params[key];
    }
  }
  return url;
};

Ijax.internal.request = function(module, params) 
{
  if (Ijax.internal.default_params.auth == 'reauthenticate') {
    return null;
  }
  var req = {
    url: Ijax.internal.url(module, params),
    reauth_counter: Ijax.internal.reauth_counter,
    onSuccess: (params.onSuccess ? 
                params.onSuccess : 
                Ijax.internal.default_params.onSuccess),
    onFailure: (params.onFailure ? 
                params.onFailure : 
                Ijax.internal.default_params.onFailure)
  };
  if (Ijax.internal.default_params.method == Ijax.METHOD_DYNAMIC_SCRIPTS) {
    var id = ++Ijax.internal.counter;
    Ijax.internal.requests[id] = req;
    Ijax.internal.open_jsonp(id, req.url);
    return req;
  }  

  req.readystate = 0;
  req.transport = Ijax.internal.ajax_transport();

  if (!req.transport) {
    req.onFailure(Ijax.internal.error(Ijax.ERR_NO_TRANSPORT, 
				      'Cannot create an AJAX transport'));
    return null;
  }
    
  req.transport.onreadystatechange = function() { 
    this.readystate = req.transport.readystate;
    if (req.transport.readyState == 4) {
      req.transport.onreadystatechange = function() {};
      if (req.transport.status != 200) {
        req.onFailure(Ijax.internal.error(Ijax.ERR_HTTP, 
					  'HTTP response: '+req.transport.status));
      }
      else {
        var data = req.transport.responseText;      
        if (!data) {
          req.onFailure(Ijax.internal.error(Ijax.ERR_EMPTY_RESPONSE, 
                                            'Empty Response'));
        }
        else {
          var result;
          try {
            result = eval('('+data+')');
          }
          catch (e) {
            req.onFailure(Ijax.internal.error(Ijax.ERR_JSON, 
					      'Bad JSON data'));
            result = null;
          }      

          if (result !== null) {
            var response = result.ijax_response;
            if (response.error_code > 0) {
              Ijax.internal.fail(req, response);
            }
            else if (response.error) {
              Ijax.internal.fail(req, response.error);
            }
            else {
              req.onSuccess(response);
            }
          }
        }
      }
    }
  };

  req.transport.open('GET', req.url, true);
  req.transport.send(null);

  return req;
};

Ijax.internal.error = function(errno, reason) {
  return {
    error_code: errno,
    reason: reason,
    param: '',
    value: '',
    code: '',
    exchange: ''
  };
};

Ijax.internal.open_jsonp = function(id, url) 
{
  var n = (new Date()).getTime();
  url += ('&callback=Ijax.internal.callback_jsonp&callback_param='+id+
          '&nocache='+n);
  var script = document.createElement("script");                
  script.setAttribute("type", "text/javascript");
  script.setAttribute("src", url);
  script.setAttribute("id", "ijax_script_"+id);
  var head = (document.getElementsByTagName("head")[0] || 
              document.documentElement);
  head.insertBefore(script, head.firstChild);
};

Ijax.internal.callback_jsonp = function(response) 
{
  var id = response.callback_param;
  var req = Ijax.internal.requests[id];
  if (req) {
    if (response.error_code > 0) {
      Ijax.internal.fail(req, response);
    }
    else if (response.error) {
      Ijax.internal.fail(req, response.error);
    }
    else {
      req.onSuccess(response);
    }
    Ijax.internal.requests[id] = undefined;
  }
  var head = (document.getElementsByTagName("head")[0] || 
              document.documentElement);
  var script = document.getElementById("ijax_script_"+id);
  if (head && script) {
    head.removeChild(script);
  }
};

Ijax.internal.ajax_transport = function() 
{
  try { return new XMLHttpRequest(); } catch (e1) {}
  try { return new ActiveXObject('Msxml2.XMLHTTP'); } catch (e2) {}
  try { return new ActiveXObject('Microsoft.XMLHTTP'); } catch (e3) {}
  return null;
};

Ijax.internal.fail = function(req, error)
{
  var reauth = 0;

  if (error.error_code == Ijax.ERR_AUTH_REQUIRED) {
    if (req.reauth_counter > Ijax.internal.min_reauth_counter) {
      Ijax.internal.min_reauth_counter = req.reauth_counter;
      reauth = 1;
    }
  }

  req.onFailure(error);

  if (reauth) {
    Ijax.internal.default_params.onAuthenticate();
  }
};

Ijax.internal.fmt_change = function(change, str)
{
  var t = (change > 0) ? 'up' : 'down';
  var s = Ijax.internal.fmt[t+'_prefix_html']+str;
  var c = Ijax.internal.fmt[t+'_class'];
  return c ? "<span class=\""+c+"\">"+s+"</span>" : s;
};

Ijax.internal.short_days = [ "Sun", "Mon", "Tue", "Wed", 
			     "Thu", "Fri", "Sat" ];

Ijax.internal.long_days = [ "Sunday", "Monday", "Tuesday", "Wednesday", 
			    "Thursday", "Friday", "Saturday" ];

Ijax.internal.short_months = [ "Jan", "Feb", "Mar", "Apr",
			       "May", "Jun", "Jul", "Aug",
			       "Sep", "Oct", "Nov", "Dec" ];

Ijax.internal.long_months = [ "January", "February", "March", "April",
			      "May", "June", "July", "August",
			      "September", "October", "November", "December" ];

Ijax.internal.dim = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];

Ijax.internal.is_leap = function(y)
{
  return ((y % 4) === 0) && (((y % 100) !== 0) || ((y % 400) === 0));
};

Ijax.internal.fmt_time = function(time, fmt)
{
  var v = time.split(/[ :\-]/);
  var yyyy = 0, mm = 0, dd = 0, hh = 0, mi = 0, ss = 0;
  if (v !== null) {
    if (v.length >= 3) {
      yyyy = parseInt(v[0], 10);
      mm = parseInt(v[1], 10);
      dd = parseInt(v[2], 10);
      if (v.length >= 6) {
	hh = parseInt(v[3], 10);
	mi = parseInt(v[4], 10);
	ss = parseInt(v[5], 10);
      }
    }
  }
  var s = '';
  var f = fmt.split('');
  var t, n;
  for (var i = 0; i < f.length; i++) {
    switch (f[i]) {
    case 'a': s += (hh < 12 ? 'am' : 'pm'); break;
    case 'A': s += (hh < 12 ? 'AM' : 'PM'); break;
    case 'B': /* unimplemented */ break;
    case 'c': /* unimplemented */ break;
    case 'd': s += (dd < 10 ? '0' + dd : dd); break;
    case 'D': 
      t = new Date(yyyy, mm - 1, dd, hh, mi, ss);
      s += Ijax.internal.short_days[t.getDay()];
      break;
    case 'F': s += Ijax.internal.long_months[mm - 1]; break;
    case 'g': s += (hh === 0 ? 12 : (hh > 12 ? hh - 12 : hh)); break;
    case 'G': s += hh; break;
    case 'h': 
      n = (hh === 0 ? 12 : (hh > 12 ? hh - 12 : hh)); 
      s += (n < 10 ? '0' + n : n);
      break;
    case 'H': s += (hh < 10 ? '0' + hh : hh); break;
    case 'i': s += (mi < 10 ? '0' + mi : mi); break;
    case 'I': /* unimplemented */ break;
    case 'j': s += dd; break;
    case 'l': 
      t = new Date(yyyy, mm - 1, dd, hh, mi, ss);
      s += Ijax.internal.long_days[t.getDay()];
      break;
    case 'L': /* unimplemented */ break;
    case 'm': s += (mm < 10 ? '0' + mm : mm); break;
    case 'M': s += Ijax.internal.short_months[mm - 1]; break;
    case 'O': /* unimplemented */ break;
    case 'P': /* unimplemented */ break;
    case 'r': /* unimplemented */ break;
    case 's': s += (ss < 10 ? '0' + ss : ss); break;
    case 'S': 
      switch (dd) {
      case 1: case 21: case 31: s += 'st'; break;
      case 2: case 22: s += 'nd'; break;
      case 3: case 23: s += 'rd'; break;
      default: s += 'th'; break;
      }
      break;
    case 't': s += ((mm == 2 && Ijax.internal.is_leap(yyyy)) ? 
		     29 : Ijax.internal.dim[mm]);
	      break;
    case 'U': s += Math.round(t.getTime()/1000); break;
    case 'w': s += t.getDay(); break;
    case 'W': /* unimplemented */ break;
    case 'y': s += (yyyy % 100); break;
    case 'Y': s += yyyy; break;
    case 'z': /* unimplemented */ break;
    case 'Z': /* unimplemented */ break;
    case '%': if (i < f.length - 1) { s += f[++i]; } break;
    default: s += f[i]; break;
    }
  }
  return s;
};

