//Script for javascript error handling support
//should be included FIRST to all html files using javascript
//Copyright (c) 2005-2006 by Dmitry Kochin (dco@mail.ru) for haddan.ru project
//If you want to use this script or the underlying ideas
//you can do it freely. The only condition - you should insert my
//copyright note and a link to haddan.ru

//Заменители для модальных вызовов,
//сохраняющие стэк
function eh_alert(msg){
  var svStack = eh_delay();
  alert(msg);
  eh_continue(svStack);
}

function eh_confirm(msg){
  var svStack = eh_delay();
  var ret = confirm(msg);
  eh_continue(svStack);
  return ret;
}

function eh_prompt(msg, defVal){
  var svStack = eh_delay();
  var ret = prompt(msg, defVal);
  eh_continue(svStack);
  return ret;
}

//Должна вызываться в начале глобального скоупа
function ehge(scriptname, hint){ //Global enter
  eh_describe_stack_if_leaked();
  eh_enter(null,null,scriptname,g_eh_ctx);
  if(hint) g_eh_ctx._eh_hint = hint;
  return g_eh_ctx;
}

//Должна вызываться в начале глобального скоупа, если предоставляется подсказка...
function ehgh(hint){ //Global enter
  eh_describe_stack_if_leaked();
  eh_enter(null,null,null,g_eh_ctx);
  if(hint) g_eh_ctx._eh_hint = hint;
  return g_eh_ctx;
}

//Должна вызываться в конце глобального скоупа
function ehgx(ret){ //Global exit
  var str;
  if(top.g_eh_callstack.length != 1){
    str = 'Несогласованный глобальный выход!\nСтэк:\n' + eh_callstack_str();
    top.g_eh_callstack.length = 1;
  }
  eh_exit(g_eh_ctx);
  delete g_eh_ctx._eh_hint;
  if(str != null)
    alert(str);
  return ret;
}

//Вход в функцию (передавать нужно только первые три параметра, третий - необязательно)
function eh_enter(funcname, args, scriptname, ctx){ //Local enter
  var idx = top.g_eh_callstack.length;
  if(ctx == null)
    ctx = new EH_Context(idx);
  top.g_eh_callstack[idx] = new EH_FuncCall(funcname, args, ctx, scriptname);
/* Профайлер
  if(top.g_eh_ctx._eh_profile != null){
    var p = top.g_eh_ctx._eh_profile;
    if(p[funcname] == null)
      p[funcname] = 1;
    else
      p[funcname] += 1;
  }
*/
  return ctx;
}

//Выход из функции (передавать нужно возвращаемое и eh_enter значение)
function eh_exit(ctx, ret){  //Local exit
  var idx = ctx._eh_req_n;
  if(top.g_eh_callstack.length != idx+1){
    eh_alert('Ошибка!\nНесогласованный вызов eh_exit('+idx+')\nСтэк:\n' + eh_callstack_str());
  }
  //Нельзя увеличивать длину ни при каких обстоятельствах!
  if(top.g_eh_callstack.length > idx)
    top.g_eh_callstack.length=idx;
  return ret;
}

//Восстанавливает стэк после броска исключения
function eh_restore(ctx){
  var idx = ctx._eh_req_n;
  if(top.g_eh_callstack.length < idx+1){
    eh_alert('Ошибка!\nНесогласованный вызов eh_restore('+idx+')');
  }else{ 
    //Нельзя увеличивать длину!
    top.g_eh_callstack.length=idx+1;
  }
}

//Останавливает текущее выполнение (перед alert, confirm и прочими модальными штуками)
function eh_delay(){
  var svStack = top.g_eh_callstack;
  top.g_eh_callstack = new Array();
  return svStack;
}

//Восстанавливает текущее выполнение после возврата из alert, confirm и т.д.
function eh_continue(svStack){
  eh_describe_stack_if_leaked();
  top.g_eh_callstack = svStack;
}

//Проверка включения файлов.
//В качестве аргумента должны передаваться имена файлов скриптов (без расширения)
function eh_check_inc(){
  var _c = eh_enter('eh_check_inc',arguments,'eh');
  var ver;
  for(var i=0;i<arguments.length; ++i){
    _c._eh_include = arguments[i];
    eval('ver = g_eh_version_' + _c._eh_include); //Если файл не включен, здесь должна сгенерироваться ошибка
  }
  eh_exit(_c);
}

var ieVersion = 0;
var ieFilters = false;

function ie_initVersion(){
  var arVersion = navigator.appVersion.split("MSIE");
  ieVersion = parseFloat(arVersion[1]);
  ieFilters = ieVersion >= 5.5;
}

ie_initVersion();

//Вспомогательные функции, которые нужны везде
function img_getTag(id, src, w, h, title, attribs){
  var _c = eh_enter('img_getTag',arguments,'eh');
  if(title) title = title.replace(/"/g, '&quot;');
  if(ieFilters && document.body && document.body.filters){
/*    var imgID = id ? 'id="' + id + '" ' : '';
    var imgTitle = title ? 'title="' + title + '" ' : '';
    var imgStyle = 'display:inline-block;font-size:1px;';
    var a = ['<span ', imgID, imgTitle, ' style="width:', w, 'px; height:', h, 'px;', imgStyle, ';',
      'filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'',
      src, '\', sizingMethod=\'scale\');"></span>']; */
    var imgID = id ? 'id="' + id + '" ' : '';
    if(!attribs) attribs = '';
    var imgTitle = title ? 'title="' + title + '" alt="' + title +'" ' : '';
    var a = ['<img ', attribs, ' ', imgID, imgTitle, ' style="filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'',
      src, '\', sizingMethod=\'scale\');" width="' + w + '" height="' + h +'" src="/images/s.gif" border="0" realSrc="',src,'">'];
  }else{
    var imgID = id ? 'id="' + id + '" ' : '';
    var imgTitle = title ? 'title="' + title + '" alt="' + title +'" ' : '';
    var a = ['<img ', imgID, imgTitle, ' width="', w, '" height="', h, '" src="', src, '" border="0">'];
  }
  eh_exit(_c);
  return a.join('');
}

function img_replaceTags(str){
  var _c = eh_enter('img_replaceTags',arguments,'eh');
  var re = /<img\s*([^\(>]*)\(([^,]+),([^,]+),([^,]+)(,"(.*)"(?=\)>))?\)>/i;
  var a;
  while(a = str.match(re)){
    str = str.replace(a[0], img_getTag(null, a[2], a[3], a[4], a[6], a[1]));
  }
  eh_exit(_c);
  return str;
}


function img_getDim(name, w, h){
  var _c = eh_enter('img_getDim',arguments,'eh');
  var o,a = name.match(/_(\d+)x(\d+)\./);
  if(a != null)
    o = {width: a[1], height: a[2]};
  if(!o && w && h)
    o = {width: w, height: h};
  eh_exit(_c);
  return o;
}

function img_set(img, name, w, h){
  var _c = eh_enter('img_set',arguments,'eh');
  var src = img.getAttribute('realSrc');
  var imgpng = (src && img.runtimeStyle);
  if(!src) src = img.src;
  if(src.length < name.length || src.substr(src.length-name.length, name.length) != name){
    var o = img_getDim(name);
    if(w && h){
      img.width = w;
      img.height = h;
    }else if(o){
      img.width = o.width;
      img.height = o.height;
    }
    if(imgpng){
      img.setAttribute('realSrc', name);
      img.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + name + "',sizingMethod='scale')";
    }else{
      img.src = name;
    }
  }
  eh_exit(_c);
  return o;
}

function img_preload(base){
  var _c = eh_enter('img_preload',arguments,'eh');
  if(document.images){
    if(!document.img_storage)
      document.img_storage = [];
    for(var i=1; i<arguments.length; ++i){
      var img = new Image;
      img.src = base + arguments[i];
      document.img_storage.push(img);
    }
  }
  eh_exit(_c);
}

function img_swap(e, name){
  var _c = eh_enter('img_swap',arguments,'eh');
  img_set(e.srcElement, name);
  eh_exit(_c);
}

///////////////////////////////////////////////////////
// Работа с массивами
////////////////////////////////////////////////////////

//Удаление i=того элемента из массива
function arrayDelete(arr, i){
  var _c = eh_enter('arrayDelete',arguments,'eh');
  if(i >= 0){
    for(; i+1 < arr.length; i++){
      arr[i] = arr[i+1];
    }
    if(i < arr.length)
      arr.length = arr.length - 1;
  }
  eh_exit(_c);
}

function arrayFind(arr, val, property){
  var _c = eh_enter('arrayFind',arguments,'eh');
  var ret = -1;
  for(var i=0; i < arr.length; i++){
    var x = arr[i];
    if(typeof(x) == 'undefined') continue;
    if((property!=null ? x[property] : x) == val){
      ret = i;
      break;
    }
  }
  eh_exit(_c);
  return ret;
}

function arrayBFind(arr, val, property, bound){
  var _c = eh_enter('arrayFind',arguments,'eh');
  var l = 0, u = arr.length - 1, i;
  _c.ret = -1;
  while(l <= u){
    i = (l + u) >> 1;
    var s = (property == null ? arr[i] : arr[i][property]);
    if     (s > val) u=i-1;
    else if(s < val) l=i+1;
    else{ _c.ret = i; break; }
  }
  if(_c.ret < 0 && bound)
    _c.ret = l;
  eh_exit(_c);
  return _c.ret;
}

function arrayCmp(elem1, elem2, property){
  var _c = eh_enter('arrayFind',arguments,'eh');
  var x = (property == null ? elem1 : elem1[property]);
  var y = (property == null ? elem2 : elem2[property]);
  var ret = 0;
  if(x > y) ret = 1;
  else if(x < y) ret = -1;
  eh_exit(_c);
  return ret;
}

function arrayInsert(arr, i, elem){
  var _c = eh_enter('arrayInsert',arguments,'eh');
  for(j=arr.length; j > i; --j)
    arr[j] = arr[j-1];
  arr[i] = elem;
  eh_exit(_c);
}

//Для ускорения конкатенации строк их надо сначала просто складывать в массив
//К сожалению, push есть только в IE5.5+
//Для других броузеров делаем заменитель
if(!Array.prototype.push){
  Array.prototype.push = function(){
    var i,l=arguments.length,c=this.length;
    for(i=0; i<l; ++i){
      this[c+i] = arguments[i];
    }
  }
}

//Форматирует число и ставит ему соответствующее существительное
function num_fmt(num, n1, n2, n5){
  var _c = eh_enter('num_fmt',arguments,'eh');
  var n10 = num%10;
  var n100 = num%100;
  var str = '';
  if(n10 == 1 && (n100 < 10 || n100 > 20)){
    str = num + ' ' + n1;
  }else if(n10 >= 2 && n10 <= 4 && (n100 < 10 || n100 > 20)){
    str = num + ' ' + n2;
  }else{
    str = num + ' ' + n5;
  }
  eh_exit(_c);
  return str;
}


//
// Остальные функции снаружи не вызываются!!!
//

var c_eh_numErrors = 3; //Число отсылаемых ошибок на 1 URL

var g_eh_callstack=[];
window.onerror = fnErrorTrap;
var g_eh_ctx = new EH_Context(0);
var g_eh_errImage = new Array(c_eh_numErrors); //Массив запросов к базе
var g_eh_curErrNum = 0; //Номер текущей ошибки
var g_eh_logScript = '/elog/elog.php';

var g_eh_defaultStatusOld; //Сохраним умолчательный статус
if(top.g_eh_defaultStatusOld == null)
  top.g_eh_defaultStatusOld = top.defaultStatus;

var g_eh_version_eh = '%@version@%';
ehge('eh');

function EH_Context(n){
  this._eh_req_n = n;
}

function EH_FuncCall(funcname, args, ctx, scriptname){
  this.funcname = funcname;
  this.args = args;
  this.ctx = ctx;
  this.href = window.location.href;
  if(scriptname != null){
    eval('this.version = g_eh_version_' + scriptname);
    this.location = scriptname + '.js';
  }else{
    this.version = g_eh_version;
    this.location = this.href;
  }
  this.global_ctx = g_eh_ctx;
}

function eh_format(val){
  var _c = eh_enter('eh_format', arguments, 'eh');
  var str = '';
  var t = typeof(val);
  switch(t){
  case 'number':
  case 'boolean':
    str = val;
    break;
  case 'string':
    str = '"' + val + '"';
    if(str.length > 50)
      str = str.substr(0, 47) + '..."(' + str.length + ')';
    break;
  case 'object':
    if(val == null){
      str = 'null';
    }else if(val.length != null){ //Массив
      str = '[Array(' + val.length + ')]';
    }else{ //Объект
      str = '' + val;
      if(str == '[object]' || str.length > 50){
        str = '[object';
        if(str.objectType != null)
          str += ' ' + str.objectType;
        str += ']';
      }
    }
    break;
  case 'function':
    str = val;
    break;
  case 'undefined':
    str = 'null';
    break;
  default:
    str = val;
    break;
  }
  eh_exit(_c);
  return str;
}

function eh_getTextCall(call){
  var _c = eh_enter('eh_getTextCall', arguments, 'eh');
  var str = '';
  if(call.funcname != null){
    str += call.funcname + '(';
    for(var i=0; i<call.args.length; ++i){
      if(i != 0) str += ', ';
      var val = call.args[i];
      str += eh_format(val);
    }
    str += ') ';
  }else{
    str += 'global code ';
  }
  str += 'in ' + call.location + ' (' + call.version + ')';
  eh_exit(_c);
  return str;
}

function eh_getTextCtx(ctx){
  var _c = eh_enter('eh_getTextCtx', arguments, 'eh');
  var str = '';
  for(name in ctx){
    str += name + '=' + eh_format(ctx[name]) + '\n';
  }
  eh_exit(_c);
  return str;
}

function eh_callstack_str(sl){
  if(sl == null) sl = top.g_eh_callstack.length;
  var _c = eh_enter('eh_callstack_str', arguments, 'eh');
  var str = '';
  var x = top.g_eh_callstack;
  for(var i=sl-1; i>=0; --i){
    var call = x[i];
    str += eh_getTextCall(call) + '\n';
  }
  eh_exit(_c);
  return str;
}

//Проверяет, что стэк исполнения пуст.
//Если нет, то выводит его юзеру и очищает.
//Делает это в цикле, чтобы за время показа стека юзеру его никто не занял
function eh_describe_stack_if_leaked(){
  while(top.g_eh_callstack.length != 0){
    //Скорее всего, просто броузер не поддерживает вызов window.onerror
    var gc = eh_getLastCall();
    var o = new Object();
    o.url = escape(top.location.href);
    o.url0 = gc ? escape(gc.href) : '';
    o.line = '0';
    o.d = escape(new Date());
    o.msg = escape('Не очищен глобальный стэк');
    o.ehstack = escape(eh_callstack_str());
    o.ehcontext = escape(gc ? eh_getTextCtx(gc.ctx) : '');
    eh_send(o);
    eh_clear(); //Сотрем весь имеющийся стек.
  }
}

function eh_clear(){
  top.g_eh_callstack.length = 0;
}

function eh_checkLocal(sUrl, sl){
  var _c = eh_enter('eh_checkLocal', arguments, 'eh');
  if(sUrl == null){
    var gc = eh_getLastCall(sl);
    if(gc != null) sUrl = gc.href;
  }
  //Если неизвестно ничего, то лучше отправить
  _c.ret = (sUrl == null || sUrl.search(/^https?:/i) == -1 || sUrl.search('localhost') != -1);
  eh_exit(_c);
  return _c.ret;
}

function eh_getLastCall(sl){
  if(sl == null) sl = top.g_eh_callstack.length;
  var _c = eh_enter('eh_getLastCall', arguments, 'eh');
  _c.ret = sl > 0 ? top.g_eh_callstack[sl-1] : null;
  eh_exit(_c);
  return _c.ret;
}

function getHTTPObject() {
        var _c = eh_enter('getHTTPObject', arguments, 'eh');
        var xmlhttp;

/*@cc_on
@if (@_jscript_version >= 5)
        try {
                xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
        } catch (e) {
                try {
                        xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
                } catch (E) {
                        xmlhttp = false;
                }
        }
@else
        xmlhttp = false;
@end @*/
        if (!xmlhttp && typeof XMLHttpRequest != 'undefined') {
//@if (@_jscript_version >= 5)
                try {
//@end
                        xmlhttp = new XMLHttpRequest();
//@if (@_jscript_version >= 5)
                } catch (e) {
                        xmlhttp = false;
                }
//@end
        }
        eh_exit(_c);
        return xmlhttp;
}

function eh_send(o){
  return null;
  var _c = eh_enter('eh_send', arguments, 'eh');
  var xmlhttp = getHTTPObject();
  if(xmlhttp){
    var a = window.location.href.match(/(http:\/\/[a-z\-\.]+)/i);
    if(a){
      xmlhttp.open('POST', a[0] + g_eh_logScript, true);
      xmlhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
      var sBody = '';
      for(var n in o){
        if(sBody != '') sBody += '&';
       sBody += n + '=' + o[n];
      }
      xmlhttp.send(sBody);
    }else{
      _c.ret = 'Ошибка отсылки запроса! "' + window.location.href + '" не содержит адреса сервера';
    }
  }else{
    //придется в куки засовывать :(
    var cpi = g_eh_logScript.search(/\/[^\/]*$/), cp='';
    if(cpi >= 0) cp = g_eh_logScript.substr(0, cpi);
    if(cp == '') cp = '/'; //Подготовили путь к куки

    var urlLog = g_eh_logScript + '?url=' + o.url + '&line=' + o.line + '&url0=' + o.url0 + '&d=' + o.d;
    if(o.msg.length < 80){ //Можно рискнуть в адрес положить
      urlLog += '&msg=' + o.msg;
    }else{
      document.cookie = "msg=" + o.msg + "; path=" + cp + ";";
    }

    //Ограничиваем длинные строки по длине (в сумме не больше 4000)
    var strStack = o.ehstack, strCtx = o.ehcontext;
    var sum = strStack.length + strCtx.length;
    if(sum > 4000){
      if(strStack.length <= 4000){
        strCtx = eh_cut(strCtx, sum - 4000);
      }else{
        strStack = eh_cut(strStack, (sum - 4000)*strStack.length/sum);
        strCtx = eh_cut(strCtx, (sum - 4000)*strCtx.length/sum);
      }
    }

    document.cookie = "ehstack=" + strStack + "; path=" + cp + ";";
    document.cookie = "ehcontext=" + strCtx + "; path=" + cp + ";";

    g_eh_errImage[g_eh_curErrNum] = new Image();
    g_eh_errImage[g_eh_curErrNum].src = urlLog;
  }
  eh_exit(_c);
  return _c.ret;
}

function fnErrorTrap(sMsg,sUrl,sLine){
  var strAlert = '';
//@if (@_jscript_version >= 5)
  try{
//@end
  var sl = top.g_eh_callstack.length; //Сохраняем длину стека, который надо показать
  eh_enter('fnErrorTrap', arguments, 'eh'); //Входим в функцию обработки стека
  var bLocal = eh_checkLocal(sUrl, sl);
  if(bLocal){
    var str="Ошибка времени выполнения.\n\n";
    str+="Error: " + sMsg + "\n";
    str+="Line: " + sLine + "\n";
    str+="URL: " + sUrl + "\n";
    str+="Call stack:\n";
    str += eh_callstack_str(sl);
    var gc = eh_getLastCall(sl);
    if(gc != null) str += "Context:\n" + eh_getTextCtx(gc.ctx);
    strAlert = str;
  }else if (g_eh_curErrNum < c_eh_numErrors) {
    var gc = eh_getLastCall(sl);

    ++g_eh_curErrNum;
    if(gc && gc.ctx._eh_include)
      g_eh_curErrNum = c_eh_numErrors; //Если ошибка включения файла, то остальные можно не обрабатывать...

    // Построение запроса
    var o = new Object();
    o.url = escape(sUrl);
    o.url0 = gc ? escape(gc.href) : '';
    o.line = escape(sLine);
    o.d = escape(new Date());
    o.msg = escape(sMsg);
    o.ehstack = escape(eh_callstack_str(sl));
    o.ehcontext = escape(gc ? eh_getTextCtx(gc.ctx) : '');
    var str = eh_send(o);

    if(!str) str = (new Date()).toLocaleString() + ': ошибка JavaScript (обработана): ' + sMsg + ' в ' + sUrl + '(' + sLine + ')';

    top.defaultStatus = str;
    top.setTimeout('ehge("eh"); eh_restore_status(); ehgx()', 10000);
  }else{
    eh_clear();
    top.defaultStatus = '' + (new Date()) + ': ошибка JavaScript (не обработана, слишком много ошибок)';
    top.setTimeout('ehge("eh"); eh_restore_status(); ehgx()', 10000);
  }

//@if (@_jscript_version >= 5)
  } catch (e) {
    strAlert = "Ошибка в блоке обработки ошибок!\n\nТип: " + e.name + "\nКод: " + e.number + "\nСообшение: " + e.message + "\nОписание: " + e.description;
    strAlert += "\n\nПожалуйста, сообщите об этой ошибке разработчикам по адресу haddan@mail.ru";
  }
//@end
  eh_clear(); //Вместо выхода
  if(strAlert != '') alert(strAlert);
  return true;
}

//Обрезаем строку так, чтобы не испортить %XX и %uXXXX
function eh_cut(str, howmuch){
  var _c = eh_enter('eh_cut', arguments, 'eh');
  howmuch = Math.ceil(howmuch);
  str = str.substr(0, str.length - howmuch);
  var pos = str.search(/%[^%]*$/);
  if(pos >= 0 && pos >= str.length-5)
    str = str.substr(0, pos);
  eh_exit(_c);
  return str;
}

function eh_restore_status(){
  var _c = eh_enter('eh_restore_status',arguments,'eh');
  top.defaultStatus = top.g_eh_defaultStatusOld;
  eh_exit(_c);
}

ehgx();
