/**
 *  $Id: helpers.js 251 2007-04-05 13:38:00Z wingedfox $
 *  $HeadURL: https://svn.debugger.ru/repos/jslibs/BrowserExtensions/trunk/helpers.js $
 *
 *  File contains differrent helper functions
 * 
 * @author Ilya Lebedev <ilya@lebedev.net>
 * @license LGPL
 * @version $Rev: 251 $
 */
//-----------------------------------------------------------------------------
//  Variable/property checks
//-----------------------------------------------------------------------------
/**
 *  Checks if property is undefined
 *
 *  @param {Object} prop value to check
 *  @return {Boolean} true if matched
 *  @scope public
 */
function isUndefined (prop /* :Object */) /* :Boolean */ {
  return (typeof prop == 'undefined');
}
/**
 *  Checks if property is function
 *
 *  @param {Object} prop value to check
 *  @return {Boolean} true if matched
 *  @scope public
 */
function isFunction (prop /* :Object */) /* :Boolean */ {
  return (typeof prop == 'function');
}
/**
 *  Checks if property is string
 *
 *  @param {Object} prop value to check
 *  @return {Boolean} true if matched
 *  @scope public
 */
function isString (prop /* :Object */) /* :Boolean */ {
  return (typeof prop == 'string');
}
/**
 *  Checks if property is number
 *
 *  @param {Object} prop value to check
 *  @return {Boolean} true if matched
 *  @scope public
 */
function isNumber (prop /* :Object */) /* :Boolean */ {
  return (typeof prop == 'number');
}
/**
 *  Checks if property is the calculable number
 *
 *  @param {Object} prop value to check
 *  @return {Boolean} true if matched
 *  @scope public
 */
function isNumeric (prop /* :Object */) /* :Boolean */ {
  return (isNumber(prop)||isString(prop))&&!isNaN(parseInt(prop))&&isFinite(parseInt(prop));
}
/**
 *  Checks if property is array
 *
 *  @param {Object} prop value to check
 *  @return {Boolean} true if matched
 *  @scope public
 */
function isArray (prop /* :Object */) /* :Boolean */ {
  return (prop instanceof Array);
}
/**
 *  Checks if property is regexp
 *
 *  @param {Object} prop value to check
 *  @return {Boolean} true if matched
 *  @scope public
 */
function isRegExp (prop /* :Object */) /* :Boolean */ {
  return (prop instanceof RegExp);
}
/**
 *  Checks if property is a boolean value
 *
 *  @param {Object} prop value to check
 *  @return {Boolean} true if matched
 *  @scope public
 */
function isBoolean (prop /* :Object */) /* :Boolean */ {
  return ('boolean' == typeof prop);
}
/**
 *  Checks if property is a scalar value (value that could be used as the hash key)
 *
 *  @param {Object} prop value to check
 *  @return {Boolean} true if matched
 *  @scope public
 */
function isScalar (prop /* :Object */) /* :Boolean */ {
  return isNumeric(prop)||isString(prop);
}
/**
 *  Checks if property is empty
 *
 *  @param {Object} prop value to check
 *  @return {Boolean} true if matched
 *  @scope public
 */
function isEmpty (prop /* :Object */) /* :Boolean */ {
  if (isBoolean(prop)) return false;
  if (isRegExp(prop) && new RegExp("").toString() == prop.toString()) return true;
  if (isString(prop) || isNumber(prop)) return !prop;
  if (Boolean(prop)&&false != prop) {
    for (var i in prop) if(prop.hasOwnProperty(i)) return false
  }
  return true;
}

function dump (v, l) {
    var s = [];
    if (!l) l=0;
    if (l>2) return "***********8Recursion*************";
    var sp = "";
    for (var i=0;i<l;i++) sp+="    ";
    for (var i in v) {
//        if (!v.hasOwnProperty(i)) continue;
        var q = [sp,i,': '];
        try {
        if (!isScalar(v[i]) && !isFunction(v[i])) {
            q[q.length] = '{';
            s[s.length] = q.join("");            
            s[s.length] = dump(v[i],l+1);
            s[s.length] = '}';
        } else {
            q[q.length] = v[i];            
            s[s.length] = q.join(""); 
        }
        } catch (err) {} 
    }
    return s.join("\n");
} 
//-----------------------------------------------------------------------------
//  File paths functions
//-----------------------------------------------------------------------------
/**
 *  used to glue path's
 *
 *  @param {String} number of strings
 *  @return {String} glued path
 *  @scope public
 */
function gluePath () /* :String */ {
  var aL=arguments.length, i=aL-2, s = arguments[aL-1]; 
  for(;i>=0;i--)
    s = ((!isString(arguments[i])&&!isNumber(arguments[i]))||isEmpty(arguments[i])
        ?s
        :arguments[i]+'\x00'+s); 
  return s?s.replace(/\/*\x00+\/*/g,"/"):"";
}

/**
 *  return full path to the script
 *
 *  @param {String} sname script name
 *  @return {String, Null} mixed string full path or null
 *  @scope public
 */
function findPath (sname /* :String */) /* :String */{
  var sc = document.getElementsByTagName('script'),
      sr = new RegExp('^(.*/|)('+sname+')([#?]|$)');
  for (var i=0,scL=sc.length; i<scL; i++) {
    // matched desired script
    var m = String(sc[i].src).match(sr);
    if (m) {
      /*
      *  we've matched the full path
      */
      if (m[1].match(/^((https?|file)\:\/{2,}|\w:[\\])/)) return m[1];
      /*
      *  we've matched absolute path from the site root
      */
      if (m[1].indexOf("/")==0) return m[1];
      b = document.getElementsByTagName('base');
      if (b[0] && b[0].href) return b[0].href+m[1];
      /*
      *  return matching part of the document location and path to js file
      */
      return (document.location.href.match(/(.*[\/\\])/)[0]+m[1]).replace(/^\/+/,"");
    }
  }
  return null;
}
/**
 *  return parsed query string for the specified script name 
 *
 *  @param {String} sname script name
 *  @return {String, Null} mixed string full path or null
 *  @scope public
 */
function getScriptQuery (sname) {
    var sc=document.getElementsByTagName('script')
       ,sr=new RegExp('^(.*/|)('+sname+')([#?].*|$)');
    for (var i=0,scL=sc.length; i<scL; i++) {
        var m = String(sc[i].src).match(sr);
        if (m) return parseQuery(m[3].replace(/^[^?]*\?([^#]+)/,"$1"));
    }
    return {};
}
/**
 *  Function parses supplied query string and returns the hash with the values
 *  Multiple values are stored in the array  
 * 
 *  @param {String} q query string
 *  @return {Object}
 *  @scope public
 */
function parseQuery (q) {
    if ('string'!=typeof q || q.length<2) return {};
    q = q.split("&");
    for (var z=0,qL=q.length,rs={},kv,rkv;z<qL;z++) {
        kv=q[z].split("=");
        /*
        *  convert PHP and Perl-styled hashes to JS has keys 
        */
        kv[0]=kv[0].replace(/[{}\[\]]*$/,"");
        rkv = rs[kv[0]];
        /*
        *  replace all + with spaces, unescape skips this part
        */
        kv[1]=unescape(kv[1]?kv[1].replace("+"," "):"");
        if (rkv)
           if ('array'==typeof(rkv))rs[kv[0]][rs[kv[0]].length]=kv[1];
           else rs[kv[0]]=[rs[kv[0]],kv[1]];
        else rs[kv[0]]=kv[1];
    }
    return rs
}



//-----------------------------------------------------------------------------
//  Misc helpers
//-----------------------------------------------------------------------------
/**
 *  Method is used to convert table into the array
 *  
 * @param {String, HTMLTableElement, HTMLTBodyElement, HTMLTHeadElement, HTMLTFootElement} id
 * @param {Number} ci column indexes to put in the array
 * @param {String} section optional section type
 * @param {Object} subsection optional subsection index
 * @return {NULL, Array}
 * @scope public
 */
function table2array (id, ci, section, subsection) {
    if (isString(id)) id = document.getElementById(id);
    if (!id || !DOM.hasTagName(id, ['table','tbody,','thead','tfoot'])) return null;
    if (!isEmpty(section) && (!isString(section) || !(id = id.getElementsByTagName(section)))) return null;
    if (!isEmpty(subsection) && (!isNumber(subsection) || subsection<0 || !(id = id[subsection]))) return null;

    if (isUndefined(id.rows)) return null;
    var res = []
       ,span = document.createElement('span')
       ,ts = null
       ,ce = null
    for (var i=0, rL=id.rows.length; i<rL; i++) {
        var tr = [];
        if (isArray(ci)) {
            for (var z=0, cL=ci.length; z<cL; z++) {
                ce = id.rows[i].cells[ci[z]];
                if (ce) {
                    span.innerHTML = ce.innerText?ce.innerText:ce.innerHTML.replace(/<script\s+(.|\r?\n)*?<\/script>|<[^>]*>/g,"");
                    span.normalize();
                    tr[tr.length] = span.firstChild?span.firstChild.nodeValue.trim(" \xA0"):"";
                } else {
                    tr[tr.length] = "";
                }
            }
        } else {
            for (var z=0, tL=id.rows[i].cells.length; z<tL; z++) {
                cd = id.rows[i].cells[z];
                span.innerHTML = ce.innerText?ce.innerText:ce.innerHTML.replace(/<script\s+(.|\r?\n)*?<\/script>|<[^>]*>/g,"");
                span.normalize();
                tr[tr.length] = span.firstChild?span.firstChild.nodeValue.trim(" \xA0"):"";
            }
        }
        if (!isEmpty(tr)) res[res.length] = tr;
    }
    return res;
}

/**
 *  Creates element all-at-once
 *
 *  @param {String} tag name
 *  @param {Object} p element properties { 'class' : 'className',
 *                                         'style' : { 'property' : value, ... },
 *                                         'event' : { 'eventType' : handler, ... },
 *                                         'child' : [ child1, child2, ...],
 *                                         'param' : { 'property' : value, ... },
 *  @return {HTMLElement} created element or null
 *  @scope public
 */
document.createElementExt = function (tag /* :String */, p /* :Object */ ) /* :HTMLElement */{
  var L, i, k, el = document.createElement(tag);
  if (!el) return null;
  for (i in p) {
    if (!p.hasOwnProperty(i)) continue;
    switch (i) {
      case "class" : el.setAttribute('className',p[i]); el.setAttribute('class',p[i]); break;
      case "style" : for (k in p[i]) { if (!p[i].hasOwnProperty(k)) continue; el.style[k] = p[i][k]; } break;
      case "event" : for (k in p[i]) { if (!p[i].hasOwnProperty(k)) continue; el.attachEvent(k,p[i][k]); } break;
      case "child" : L = p[i].length; for (k = 0; k<L; k++) el.appendChild(p[i][k]); break;
      case "param" : for (k in p[i]) { if (!p[i].hasOwnProperty(k)) continue; try { el[k] = p[i][k] } catch(e) {} } break;
    }
  }
  return el;
}

/**
 * simple setInterval/setTimout wrappers
 *
 * @param {Function} f function to be launched
 * @param {Number} i interval
 * @param {Array} o optional function parameters to be applied
 * @return {Number} interval id
 * @scope public
 */
function playInterval (f /* :Function */, i /* :Number */, o /* :Array */) /* :Number */ { return setInterval(function(){(o instanceof Array)?f.apply(this,o):f.call(this,o)},i) }
function playTimeout (f /* :Function */, i /* :Number */, o /* :Array */) /* :Number */ { return setTimeout(function(){(o instanceof Array)?f.apply(this,o):f.call(this,o)},i) }

/**
 *  Clone object
 *
 *  @param optional object to clone
 *  @return cloned object
 *  @access public
 */
function cloneObject (obj) {
   if (typeof(obj) != "object") return obj;
   try { var newObject = new obj.constructor(); } catch(e) {return null;}
   for (var objectItem in obj) {
     if (!obj.hasOwnProperty(objectItem)) continue;
     newObject[objectItem] = obj.clone(obj[objectItem]);
   }
   return newObject;
}

/**
 *  Merge objects
 *
 *  @param {Object} obj1 source object
 *  @param {Object} obj2 target object
 *  @param {Boolean} overwrite same keys or no
 *  @return {Object} 
 *  @access public
 */
function mergeObject (obj1, obj2, overwrite) {
    /*
    *  overwrite by default
    */
    if (isUndefined(overwrite)) overwrite = true;
    for (var i in obj1) {
      if (!obj1.hasOwnProperty(i)) continue;
      if (isUndefined(obj2[i]) || (overwrite && typeof obj2[i] != typeof obj1))
        if (obj1[i] instanceof Array) obj2[i] = [];
        else if ('object' == typeof obj1[i]) obj2[i] = {};
      if (obj1[i] instanceof Array) obj2[i] = obj2[i].concat(obj1[i]);
      else if ('object' == typeof obj1[i]) obj2[i].merge(obj1[i], overwrite);
      else if (isUndefined(obj2[i]) || overwrite) obj2[i] = obj1[i];
    }
    return obj2;
}
