/* this file is a part of Check4Change 
 *
 * Copyright 2010 Ronen Zilberman
 * See license.txt for details.
 */


// ----------------------- c4cUTI ------------------------
function c4cUti_assert(cond, m) {
	if ((!cond) && c4cUti_getPrefBool("doDebug"))
//		alert(m+'\n'+'stack trace: '+c4cUti_callStackDump());
		alert(m+'\n'+'stack trace:\n'+m.stack);
}

function c4cUti_callStackDump() {
	var s='';
	var x=Components.stack;
	while (x != null) {
		s = x + '\n' + s;
		x= x.caller;
	}

	return s;
}

function c4cUti_durationToString(time, unit) {
//	unit = String.toUpperCase(unit[0]) + unit.substr(1);
	var str = time + " " + unit;
//	if (time > 1) str = str+'s';
	if (time == 1) str = str.substr(0,str.length-1); // remove trailing S

	return str;
}

function c4cUti_endsWith(str, suffix) {
    return (str.indexOf(suffix, str.length - suffix.length) !== -1);
}


// Return an array with all the ranges of selection.
// Array is 2 dimestional: [range object, framepath]. framepath is an array of the frames child node path to the frame
//   holding the range.
// tries in current window and then in it's frames, recursively.
function c4cUti_getFramePathsRecur(curwin, res, framepath) {

	if (curwin == null) return res;
	// store
    res[res.length] = framepath.slice(0);

    // look for frames recursively
    var pos = framepath.length;
    var frames = curwin.frames;
    for (var i = 0; i < frames.length; i++) {
		framepath[pos] = i;
		framepath.length = pos+1; // to ensure there is no left overs from previous recursions
		res = c4cUti_getFramePathsRecur(frames[i].window, res, framepath);
	}

	return res;
}

function c4cUti_getFramePaths(rootWindow)
{
	return c4cUti_getFramePathsRecur(rootWindow, [], []);
}

function c4cUti_getFrameWindow(rootWindow, framePath) {
	var res = rootWindow;
	for (var i=0; i<framePath.length; i++) {
		if (res == null) return null;
		if (!(framePath[i] in res.frames)) return null;

		res = res.frames[framePath[i]];
	}

	return res;

}

// NOTE this doesn't really work right, because it only checks the current window.
function c4cUti_validTab(tab) {
	var tabs = getBrowser().mTabs;
	for (var i=0; i<tabs.length; i++)
		if (tabs.item(i) == tab) return true;

	return false;
}

function c4cUti_getWindowFromTab(tab) {
	var res = c4cUti_getBrowserFromTab(tab);
	if (res) return res.contentWindow;
}

function c4cUti_getBrowserFromTab(tab) {
	return gBrowser.getBrowserForTab(tab);
}

function c4cUti_tabBusy(tab) {
	return tab.getAttribute('busy');
}


function c4cUti_tabIsCurrent(tab) {
	// could also use nsIWindowWatcher:activeWindow
	// just found out, don't feel like coding.

	if (window != window.getTopWin()) return false;
	if (window.windowState == window.STATE_MINIMIZED) return false;
	if (window.hasFocus == false) return false;

	// if it is, check if tab is selected.
	return (getBrowser().selectedTab == tab);
}

function c4cUti_windowToFront() {
	// could also use minimize & restore

	window.focus();

}

function c4cUti_focusTab(tab) {
try {
		// select tab
		gBrowser.selectedTab = tab;
		// bring window to front
		c4cUti_windowToFront();

		
}catch(e) {c4cUti_log(e);}
}


// Returns the DOM selection object of the current window, if it is there. Note that If it is a frame window, returns null.
function c4cUti_getSelection(curwin) {
	if ((curwin && curwin.getSelection && curwin.getSelection()) &&
		(curwin.getSelection().rangeCount > 0) &&
    	(curwin.getSelection().getRangeAt(0).toString() != ""))
   		 return curwin.getSelection().getRangeAt(0);
	else return null;

}

// returns true if there is a selection in the window. Processes frames successfully.
function c4cUti_hasSelection(topWindow) {
	var sel = new c4cSelection(topWindow);
	return sel.hasSelection();
}

function c4cUti_hasClass(el, cssClass) {
    return el.className && new RegExp("(^|\\s)" + cssClass + "(\\s|$)").test(el.className);
}

function c4cUti_setMenuListValue(menulist, value) {
    for (var i=0; i<menulist.firstChild.childNodes.length; i++) {
      if (menulist.firstChild.childNodes[i].label == value) {
		menulist.selectedIndex = i;
		return;
	  }
    }
	// if can't find it, write it directly.
	menulist.value = value;
}

function c4cUti_getMenuListValue(menulist) {
	if (menulist.selectedItem) return menulist.selectedItem.label;
	return menulist.value;
}

function c4cUti_loadURL(tab, location, postData, referrer) {
  var loadFlags = nsIWebNavigation.LOAD_FLAGS_IS_REFRESH;
  c4cUti_getBrowserFromTab(tab).loadURIWithFlags(location, loadFlags, referrer, null, postData);
}


//***************** DOM NODE UTILS   */
/**
 * Determine whether a node's text content is entirely whitespace.
 *
 * @param nod  A node implementing the |CharacterData| interface (i.e.,
 *             a |Text|, |Comment|, or |CDATASection| node
 * @return     True if all of the text content of |nod| is whitespace,
 *             otherwise false.
 */
function c4cUti_isNodeAllWs( nod )
{
  // Use ECMA-262 Edition 3 String and RegExp features
  return !(/[^\t\n\r ]/.test(nod.data));
}


/**
 * Determine if a node should be ignored by the iterator functions.
 *
 * @param nod  An object implementing the DOM1 |Node| interface.
 * @return     true if the node is:
 *                1) A |Text| node that is all whitespace
 *                2) A |Comment| node
 *             and otherwise false.
 */

function c4cUti_isNodeIgnorable( nod )
{
  return ( nod.nodeType == 8) || // A comment node
         ( (nod.nodeType == 3) && c4cUti_isNodeAllWs(nod) ); // a text node, all ws
}

/**
 * Version of |nextSibling| that skips nodes that are entirely
 * whitespace or comments.
 *
 * @param sib  The reference node.
 * @return     Either:
 *               1) The closest next sibling to |sib| that is not
 *                  ignorable according to |is_ignorable|, or
 *               2) null if no such node exists.
 */
function c4cUti_nodeAfter( sib )
{
  while ((sib = sib.nextSibling)) {
    if (!c4cUti_isNodeIgnorable(sib)) return sib;
  }
  return null;
}

/**
 * Version of |firstChild| that skips nodes that are entirely
 * whitespace and comments.
 *
 * @param sib  The reference node.
 * @return     Either:
 *               1) The first child of |sib| that is not
 *                  ignorable according to |is_ignorable|, or
 *               2) null if no such node exists.
 */
function c4cUti_firstChild( par )
{
  var res=par.firstChild;
  while (res) {
    if (!c4cUti_isNodeIgnorable(res)) return res;
    res = res.nextSibling;
  }
  return null;
}

//************************************************************/


// returns index child in parents.childNodes. Skips ignoreable nodes. 
function c4cUti_getChildIndex(child) 
{
try{
	if (child.parentNode == null) return null;	
	c4cUti_assert((!c4cUti_isNodeIgnorable(child),"Error 173. Please report to developer via the FeedBack form."));

	var list = child.parentNode.childNodes;
	var res=0;
	for (var i=0; i<list.length; i++)
	{
		if (list[i] == child) return res;
		if (!c4cUti_isNodeIgnorable(list[i])) res++;
	}
	return -1;

} catch (e) {c4cUti_assert(false,e); return null;}
}

// get child for given index and parent, skiping ignorable nodes.
function c4cUti_getChild(parent, index) 
{
try{

	var list = parent.childNodes;
	var c=0;
	for (var i=0; i<list.length; i++)
	{
		if (c4cUti_isNodeIgnorable(list[i])) continue;
		if (c == index) return list[i];
		c++;
	}
	return null;

} catch (e) {return null;}
}




// Test if ele's id is unique in the document.
// this should be so ofcourse, but in case it isn't.
function c4cUti_isUniqueId(doc, id) {
try {
    var ele = doc.getElementById(id);
	ele.id = "__C4C_TEST_153562";
	var res = doc.getElementById(id);
	ele.id = id;
	return (res == null);
} catch (e) {c4cUti_assert(false,e); return true;}
}

function c4cUti_domNodeToPath(root, ptr) {
try{
	var i=0;
	var arr = new Array();
	var ptr;
	while (ptr != root)
	{
	    arr[i++] = ptr.id;
		arr[i++] = c4cUti_getChildIndex(ptr);
//alert(ptr + " " + ptr.nodeType + " " + c4cUti_getChildIndex(ptr));
		ptr = ptr.parentNode;
	}
	return arr;

} catch (e) {c4cUti_assert(false,e); return null;}
}

// return object. <res>.node = node res.bestGuess is boolean
function c4cUti_domPathToNode(root, arr, abspos) {
   try{
   	  var res = new Object();
	  var start = arr.length;
	  var ele;
	 
      // if abspos is set, use root as head.	 
	  if (!abspos) {
	  
		// step 1. get latest element with id
		for (var i=0;i<arr.length;i+=2) {
			var id = arr[i];
			if ((id == null) || (id == "")) continue;
		
			ele = root.getElementById(id);
			if (ele != null) {
				if (c4cUti_isUniqueId(root,id)) { // test for rare case that ele's id is not unique in document.
					start = i;
					root = ele;
					break;
				}
			}
		}
	 } 
	  
	  res.bestGuess = false;
	  // /start/ points to the starting position
  	  for (var i=start-1;i>=0;i-=2) {
	    var ch = c4cUti_getChild(root, arr[i]);
	    if (ch == null) {
	    	res.bestGuess = true;
	    	break;
	    }
	    root = ch;
 	  }
 	  res.node = root;
	  return res;
   } catch (e) {return null;}

}


function c4cUti_getNodeMaxOffset(doc, node)  {
	if (node.nodeType == 3) {
		return node.length;
	} else {
		return node.childNodes.length;
	}
}



function c4cUti_upwardFindElementByNodeName(node, wantedNodeName) {
	while (node != null) {
		if (node.nodeName == wantedNodeName) return node;
		node = node.parentNode;
	}
	return null;

}


// find val in arr. return index or null
function c4cUti_find(arr, val) {
	for (var i=0;i<arr.length;i++) {
		if (arr[i] === val) return i;
	}
	return null;
}

// finds first null slot in arr. if none exist, return length of arr.
function c4cUti_emptySlot(arr) {
	var i = c4cUti_find(arr, null);
	if (i==null) i=arr.length;
	return i;
}

// remove instance of val from arr. (if multiple remove first)
// returns if removed something
function c4cUti_removeValue(arr, val) {
	var i = arr.indexOf(val); // Find the index
    if(i == -1) return false;
	arr.splice(i, 1); // Remove it if really found!
	return true;
}

// Return the substring of str between strings <start> and <end>
// if no <start> return "". If no <end> return ""
function c4cUti_strBetween(str, start, end) {
try {
	var b = str.indexOf(start);
	if (b>=0) {
		return str.substr(b+start.length,str.indexOf(end,b)-(b+start.length));
	}
	
	return "";
} catch (e) {return "";}
}


// enable/disable menuitem
function c4cUti_enableMenuItem(menuitem, on) {
	if (on)
		 menuitem.removeAttribute('disabled');
	else menuitem.setAttribute('disabled', true);
}

function c4cUti_log(msg) {
  if (!c4cUti_getPrefBool("doDebug")) return;

try {   
  var consoleService = Components.classes["@mozilla.org/consoleservice;1"]
                                 .getService(Components.interfaces.nsIConsoleService);
  consoleService.logStringMessage("c4c: " + msg);
  
} catch (e) {alert("Can't debug! " + e);}  
  
}


function c4cUti_cloneObject(what) {
	var x = new Object;
    for (var i in what) {
        x[i] = what[i];
    }
    return x;
}

function c4cUti_DOMBrowser(ele, depth, indent){
    if (indent == null) indent = 0;
    var s = "";
	for (var i=0;i<indent;i++) s+=" ";
	
	s += ele.nodeName + " " + ele.id + " " + "\r\n";
	
	if (depth > 0) {
		// calls recursive 
		for (var i=0;i<ele.childNodes.length;i++) {
			s+=c4cUti_DOMBrowser(ele.childNodes[i], depth-1, indent+1);
		}
	}
	
	return s;
}

//------- preferences
var c4cUti_PREFERENCE_PREFIX = "extensions.check4change.";

function c4cUti_PrefsObj() {
try {
	var prefs = Components.classes["@mozilla.org/preferences-service;1"].
                getService(Components.interfaces.nsIPrefBranch);
				
	var branch = prefs.getBranch(c4cUti_PREFERENCE_PREFIX);
	var l = branch.getChildList("", {});
	var obj = new Object();
	for (p in l) {
		var val = c4cUti_getPrefBool(l[p]);
		if (!val) val = c4cUti_getPrefInt(l[p]);
		if (!val) val = c4cUti_getPrefString(l[p]);
		obj[l[p]] = val;
	}
	return obj;
} catch (e) {c4cUti_assert(false,e); return null;}  
		
}

function c4cUti_getPrefBool(name) {
try {
  var prefs = Components.classes["@mozilla.org/preferences-service;1"].
                getService(Components.interfaces.nsIPrefBranch);

  if ((prefs.getPrefType(c4cUti_PREFERENCE_PREFIX+name) == prefs.PREF_BOOL) &&
      (prefs.prefHasUserValue(c4cUti_PREFERENCE_PREFIX+name))) {
         return prefs.getBoolPref(c4cUti_PREFERENCE_PREFIX+name);

  }

  return null;
  
  
} catch (e) {c4cUti_assert(false,e); return null;}  
}

function c4cUti_setPrefBool(name, value) {
try {
	var prefs = Components.classes["@mozilla.org/preferences-service;1"].
	                getService(Components.interfaces.nsIPrefBranch);

	prefs.setBoolPref(c4cUti_PREFERENCE_PREFIX+name, value);
} catch (e) {c4cUti_assert(false,e); return null;}	
}


function c4cUti_getPrefInt(name) {
try{
  var prefs = Components.classes["@mozilla.org/preferences-service;1"].
                getService(Components.interfaces.nsIPrefBranch);

  if ((prefs.getPrefType(c4cUti_PREFERENCE_PREFIX+name) == prefs.PREF_INT) &&
      (prefs.prefHasUserValue(c4cUti_PREFERENCE_PREFIX+name))) {
         return prefs.getIntPref(c4cUti_PREFERENCE_PREFIX+name);

  }

  return null;
} catch (e) {c4cUti_assert(false,e); return null;}  
}

function c4cUti_setPrefInt(name, value) {
try {
	var prefs = Components.classes["@mozilla.org/preferences-service;1"].
	                getService(Components.interfaces.nsIPrefBranch);

	if (isNaN(value)) value = 0;					
	prefs.setIntPref(c4cUti_PREFERENCE_PREFIX+name, value);
	
} catch (e) {c4cUti_assert(false,e); return null;}	
}

function c4cUti_incPrefInt(name) {
	c4cUti_setPrefInt(name, c4cUti_getPrefInt(name) + 1);
}

function c4cUti_getPrefStringNonUC(name) {
try {
  var prefs = Components.classes["@mozilla.org/preferences-service;1"].
                getService(Components.interfaces.nsIPrefBranch);

  if ((prefs.getPrefType(c4cUti_PREFERENCE_PREFIX+name) == prefs.PREF_STRING) &&
      (prefs.prefHasUserValue(c4cUti_PREFERENCE_PREFIX+name))) {
         return prefs.getCharPref(c4cUti_PREFERENCE_PREFIX+name);

  }

  return null;
  
} catch (e) {c4cUti_assert(false,e); return null;}  
}

function c4cUti_setPrefStringNonUC(name, value) {
try {
	var prefs = Components.classes["@mozilla.org/preferences-service;1"].
	                getService(Components.interfaces.nsIPrefBranch);

	prefs.setCharPref(c4cUti_PREFERENCE_PREFIX+name, value);
	
} catch (e) {c4cUti_assert(false,e); return null;}	
}

// support UNICODE strings with complex type
function c4cUti_getPrefString(name) {
try {
  var prefs = Components.classes["@mozilla.org/preferences-service;1"].
                getService(Components.interfaces.nsIPrefBranch);

  if ((prefs.getPrefType(c4cUti_PREFERENCE_PREFIX+name) == prefs.PREF_STRING) &&
      (prefs.prefHasUserValue(c4cUti_PREFERENCE_PREFIX+name))) {
		return prefs.getComplexValue(c4cUti_PREFERENCE_PREFIX+name, Components.interfaces.nsISupportsString).data;
  }

  return null;
  
} catch (e) {c4cUti_assert(false,e); return null;}  
}

// support UNICODE strings with complex type
function c4cUti_setPrefString(name, value) {
try {
	var prefs = Components.classes["@mozilla.org/preferences-service;1"].
	                getService(Components.interfaces.nsIPrefBranch);

	var str = Components.classes["@mozilla.org/supports-string;1"].createInstance(Components.interfaces.nsISupportsString);
	str.data = value;
				
	prefs.setComplexValue(c4cUti_PREFERENCE_PREFIX+name, Components.interfaces.nsISupportsString, str);
	
	
} catch (e) {c4cUti_assert(false,e); return null;}	
}

function c4cUti_deletePref(name) {
try {
	var prefs = Components.classes["@mozilla.org/preferences-service;1"].
	                getService(Components.interfaces.nsIPrefBranch);

	prefs.clearUserPref(c4cUti_PREFERENCE_PREFIX+name);
} catch (e) {c4cUti_assert(false,e); return null;}	

}

function c4cUti_getPrefType(name) {
try {
	var prefs = Components.classes["@mozilla.org/preferences-service;1"].
	                getService(Components.interfaces.nsIPrefBranch);

	switch (prefs.getPrefType(c4cUti_PREFERENCE_PREFIX+name)) {
		case prefs.PREF_BOOL: return "boolean";
		case prefs.PREF_INT:  return "number";
		case prefs.PREF_STRING: return "string";
	}
	return null;
	
} catch (e) {c4cUti_assert(false,e); return null;}	
}

function c4cUti_getPref(name) {
	switch (c4cUti_getPrefType(name)) {
		case "number":  return c4cUti_getPrefInt(name);
		case "boolean": return c4cUti_getPrefBool(name);
		case "string":  return c4cUti_getPrefString(name);
	}
	return null;
}

function c4cUti_setPref(name, value) {
try {
//c4cUti_log(name+" "+value+ " " + typeof(value));

	if ((value === null)) {
		c4cUti_deletePref(name);
		return;
	}


	var t = c4cUti_getPrefType(name);
	if (t===null) t=typeof(value);
	switch (t) {
		case "number":  return c4cUti_setPrefInt(name, value);
		case "boolean": return c4cUti_setPrefBool(name, value);
		case "string":  return c4cUti_setPrefString(name, value);
	}
	
	c4cUti_assert("false", "Unknown value in setPref");
	return;
	
} catch (e) {c4cUti_assert(false,e); return null;}		
}

function c4cUti_isPref(name) {
	return (c4cUti_getPref(name) !== null);
}


function c4cUti_getAllPrefs(startPoint) {
try {
  var prefs = Components.classes["@mozilla.org/preferences-service;1"].
    getService(Components.interfaces.nsIPrefBranch);

  var x = new Object;
  var res = prefs.getChildList(c4cUti_PREFERENCE_PREFIX+startPoint, x);
  // remove prefix
  for (i in res) res[i] = res[i].replace(c4cUti_PREFERENCE_PREFIX, "");
  return res;
  
} catch (e) {c4cUti_assert(false,e); return null;}  
}


function c4cUti_getBrowserMetaData(browser) {
try {
  var res = new Object();
  var entry=browser.webNavigation.sessionHistory.getEntryAtIndex(browser.webNavigation.sessionHistory.index, false);
  res.postData = entry.QueryInterface(Components.interfaces.nsISHEntry).postData;
  res.referrer = entry.QueryInterface(Components.interfaces.nsISHEntry).referrerURI;
  return res;

} catch (e) {c4cUti_assert(false,e); return null;}  
}

function c4cUti_getTabMetaData(tab) {
	return c4cUti_getBrowserMetaData(c4cUti_getBrowserFromTab(tab));
}


// compare 2 version strings e.g. '1.3.4' and '1.3.14'. Return 1 if a>b -1 b>a and 0 if a==b
function c4cUti_compareVersionStrings(vera, verb) {
  var a = vera.split('.');
  var b = verb.split('.');

  for (var i=0; i<Math.max(a.length,b.length); i++) {
    var an = (i < a.length) ? parseInt(a[i]) : 0;
    var bn = (i < b.length) ? parseInt(b[i]) : 0;
    
    if (an > bn) return 1;
    if (an < bn) return -1;
  }
  return 0;
}


// silently POST data to a URL. SuccessFunc recieves the reply as param.
function c4cUti_urlPost(url, postdata, successFunc, failureFunc) {
try {
    var req = new XMLHttpRequest();
    // open a POST to given URL. user and pass is given so that the user wouldn't see a password entry dialog if there is one
    req.open('POST', url, true, 'user','pass');
    req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    // setup callbacks
    req.onload = function (event) {
		var self = event.target;
		if (self.status == 200) {
			if (successFunc) successFunc(self.responseText);
		} else {
			if (failureFunc) failureFunc();
		}
    };
    req.onerror = failureFunc;

    // send data
    req.send(postdata);
} catch (e) {}  
}

function c4cUti_ajaxCall(url, isO) {
try {
    var req = new XMLHttpRequest();
	req.open('GET', url, true);
	if (isO) { 
		req.onreadystatechange = function (aEvt) {
			if (req.readyState == 4) {
				if(req.status == 200) {
					var resp = req.responseText;
					c4cUti_addTab(resp, true);
				} else {
				// do nothing
				} 
			}
		};
	}
	req.send(null);
} catch (e) {}  	
}

function c4cUti_getSingleton() {
try {
  var hiddenWindow = Components.classes["@mozilla.org/appshell/appShellService;1"]
    .getService(Components.interfaces.nsIAppShellService)
    .hiddenDOMWindow;
 
  
  if (hiddenWindow.check4changeSingeltonObject == undefined) 
    hiddenWindow.check4changeSingeltonObject = new Object();
  
  return hiddenWindow.check4changeSingeltonObject;
} catch (e) {c4cUti_assert(false,e); return null;}  
}


function c4cUti_timeInRelavantUnits(remaining, shortForm) {
 
  var units = 'seconds';
  if (remaining > 60) {
    remaining = Math.round(remaining/60);
    units = 'minutes';
  }
  if (remaining > 60) {
    remaining = Math.round(remaining/60);
    units = 'hours';
  }
  
  if (shortForm == true) {
	units = units.substr(0,1);
	return remaining+units;
  }
 
  return (remaining+' '+units);
}

function c4cUti_getNow() {
	return new Date().getTime()
}

function c4cUti_addTab(url,switchTo) {
    if (!(url.match("^http[s]?://"))) return;
	c4cUti_addTabLocal(url, switchTo);
}

function c4cUti_addTabLocal(url, switchTo) {
	var tab = gBrowser.addTab(url); 
	if (switchTo == true) gBrowser.selectedTab = tab;
}

function c4cUti_getRecentBrowser() {
try {
	var type = "navigator:browser";
	var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService(Components.interfaces.nsIWindowMediator);  
	var win = wm.getMostRecentWindow(type);  
	return win;
} catch (e) {c4cUti_assert(false,e); return null;}	
}

function c4cUti_getHomeDir() {
try {
    var dirService = Components.classes["@mozilla.org/file/directory_service;1"].  
                      getService(Components.interfaces.nsIProperties);   
    var homeDirFile = dirService.get("Home", Components.interfaces.nsIFile); // returns an nsIFile object  
    var homeDir = homeDirFile.path;  	

	return homeDir;
} catch (e) {c4cUti_assert(false,e); return null;}	
}

function c4cUti_appendFileToDir(dirn, filen) {
try {
    var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);  
    file.initWithPath(dirn);  	
	file.append(filen);
	return file.path;
} catch (e) {c4cUti_assert(false,e); return null;}	
}

function c4cUti_appendToFile(filen, str) {
try {
   var file = Components.classes["@mozilla.org/file/local;1"].  
                         createInstance(Components.interfaces.nsILocalFile);  
   file.initWithPath(filen);  

   if(!file.exists()) {   // if it doesn't exist, create
      file.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0777); 
   }

   
   // file is nsIFile, data is a string
	var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);

	// write / create/ append
	foStream.init(file, 0x02 | 0x08 | 0x10, 0666, 0); 
	
	var converter = Components.classes["@mozilla.org/intl/converter-output-stream;1"].
                          createInstance(Components.interfaces.nsIConverterOutputStream);
	converter.init(foStream, "UTF-8", 0, 0);
	converter.writeString(str);
	converter.close(); // this closes foStream

} catch (e) {c4cUti_assert(false,e); return null;}   
}


//--- sound

function c4cUti_soundInit() {
try{
	var sound = Components.classes["@mozilla.org/sound;1"].createInstance(Components.interfaces.nsISound);
	sound.init();

} catch (e) {c4cUti_assert(false,e); return null;}
}

var c4cUti_soundElement = null;
function c4cUti_playSound(soundfile, volume, stopable) {
try{
	// if its a file system file, convert to file:// url
	if (soundfile.indexOf("://") == -1) {
		var ios = Components.classes['@mozilla.org/network/io-service;1'].getService(Components.interfaces.nsIIOService);
		var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
		file.initWithPath(soundfile);
		var soundUri = ios.newFileURI(file);
		soundfile = soundUri.spec;
	} 
        var a = document.createElementNS("http://www.w3.org/1999/xhtml","html:audio");
		a.setAttribute("_c4cSound", true);
        a.src = soundfile;
        a.volume = volume/100;
        a.play();
		
//		a.addEventListener("ended", function(){c4cUti_log("ended");}, true);		
		c4cUti_soundElement = a; // to keep a reference
		return true;
		
} catch (e) {c4cUti_assert(false,e); return false;}
}

function c4cUti_stopSound() {
try{
	if (c4cUti_soundElement) c4cUti_soundElement.pause();
	c4cUti_soundElement = null;	
		
} catch (e) {c4cUti_assert(false,e); return false;}
}


var c4cUti_soundInterval = null;
function c4cUti_playSoundContinuous(soundfile, volume, every) {
	if (c4cUti_soundInterval != null) c4cUti_stopSoundContinuous();
	
	c4cUti_soundInterval = window.setInterval(function() {c4cUti_playSound(soundfile, volume);}, every);
	c4cUti_playSound(soundfile, volume); // play 1st one.
}

function c4cUti_stopSoundContinuous() {
	if (c4cUti_soundInterval == null) return;
	window.clearInterval(c4cUti_soundInterval);
	c4cUti_soundInterval = null;
}


function c4cUti_confirm(win, title, body) {
	var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(Components.interfaces.nsIPromptService);
	return promptService.confirm(win, title, body);
}

function c4cUti_alert(win, title, body) {
	var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(Components.interfaces.nsIPromptService);
	return promptService.alert(win, title, body);
}

function c4cUti_trimWS(str) {
       // Trimming Leading and trailing white space.
       return str.replace(/^\s+|\s+$/g, '');
}

//-------------- popup
var c4cUti_popupClickCallback = null;

var c4cUti_popupMessageClickListener = {
  observe: function(subject, topic, data) {

	if (topic == "alertclickcallback") {
		if (c4cUti_popupClickCallback != null) c4cUti_popupClickCallback();
	}
  }
}

function c4cUti_popupMessage(title, msg, callback) {
try {
	var MAX_LEN = 50;
	if (msg.length > MAX_LEN) {
		msg = msg.substr(0, MAX_LEN-3) + "...";
	}
	// set tab for next functions
	c4cUti_popupClickCallback = callback;

	// do popup.
	var alertsService = Components.classes["@mozilla.org/alerts-service;1"]
                              .getService(Components.interfaces.nsIAlertsService);
	alertsService.showAlertNotification("chrome://c4c/content/c4c_32.jpg",  title, msg, true, null, c4cUti_popupMessageClickListener);

} catch (e) {c4cUti_assert(false,e); return null;}

}

function c4cUti_flashTaskBar() {
try {
	window.getAttention();
	
} catch (e) {c4cUti_assert(false,e); return null;}
}



//***********************************************
/**
*
*  Base64 encode / decode
*  http://www.webtoolkit.info/
*
**/
var c4cUti_Base64 = {
 
	// private property
	_keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
 
	// public method for encoding
	encode : function (input) {
		var output = "";
		var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
		var i = 0;
 
		input = c4cUti_Base64._utf8_encode(input);
 
		while (i < input.length) {
 
			chr1 = input.charCodeAt(i++);
			chr2 = input.charCodeAt(i++);
			chr3 = input.charCodeAt(i++);
 
			enc1 = chr1 >> 2;
			enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
			enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
			enc4 = chr3 & 63;
 
			if (isNaN(chr2)) {
				enc3 = enc4 = 64;
			} else if (isNaN(chr3)) {
				enc4 = 64;
			}
 
			output = output +
			this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
			this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
 
		}
 
		return output;
	},
 
	// public method for decoding
	decode : function (input) {
		var output = "";
		var chr1, chr2, chr3;
		var enc1, enc2, enc3, enc4;
		var i = 0;
 
		input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
 
		while (i < input.length) {
 
			enc1 = this._keyStr.indexOf(input.charAt(i++));
			enc2 = this._keyStr.indexOf(input.charAt(i++));
			enc3 = this._keyStr.indexOf(input.charAt(i++));
			enc4 = this._keyStr.indexOf(input.charAt(i++));
 
			chr1 = (enc1 << 2) | (enc2 >> 4);
			chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
			chr3 = ((enc3 & 3) << 6) | enc4;
 
			output = output + String.fromCharCode(chr1);
 
			if (enc3 != 64) {
				output = output + String.fromCharCode(chr2);
			}
			if (enc4 != 64) {
				output = output + String.fromCharCode(chr3);
			}
 
		}
 
		output = c4cUti_Base64._utf8_decode(output);
 
		return output;
 
	},
 
	// private method for UTF-8 encoding
	_utf8_encode : function (string) {
		string = string.replace(/\r\n/g,"\n");
		var utftext = "";
 
		for (var n = 0; n < string.length; n++) {
 
			var c = string.charCodeAt(n);
 
			if (c < 128) {
				utftext += String.fromCharCode(c);
			}
			else if((c > 127) && (c < 2048)) {
				utftext += String.fromCharCode((c >> 6) | 192);
				utftext += String.fromCharCode((c & 63) | 128);
			}
			else {
				utftext += String.fromCharCode((c >> 12) | 224);
				utftext += String.fromCharCode(((c >> 6) & 63) | 128);
				utftext += String.fromCharCode((c & 63) | 128);
			}
 
		}
 
		return utftext;
	},
 
	// private method for UTF-8 decoding
	_utf8_decode : function (utftext) {
		var string = "";
		var i = 0;
		var c = c1 = c2 = 0;
 
		while ( i < utftext.length ) {
 
			c = utftext.charCodeAt(i);
 
			if (c < 128) {
				string += String.fromCharCode(c);
				i++;
			}
			else if((c > 191) && (c < 224)) {
				c2 = utftext.charCodeAt(i+1);
				string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
				i += 2;
			}
			else {
				c2 = utftext.charCodeAt(i+1);
				c3 = utftext.charCodeAt(i+2);
				string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
				i += 3;
			}
 
		}
 
		return string;
	}
 
}

//******************************************************

function c4cUti_copyToClipboard(str) {
   try {
		const gClipboardHelper = Components.classes["@mozilla.org/widget/clipboardhelper;1"].
		getService(Components.interfaces.nsIClipboardHelper);
		gClipboardHelper.copyString(str);   
   } catch (e) {
		c4cUti_assert(false,e);
   }
}


function c4cUti_getOSString() {
try {
	var osString = Components.classes["@mozilla.org/xre/app-info;1"].getService(Components.interfaces.nsIXULRuntime).OS;
	var osString2 = Components.classes["@mozilla.org/network/protocol;1?name=http"].getService(Components.interfaces.nsIHttpProtocolHandler).oscpu;				   
    return osString + " " + osString2;
} catch(e) {return "err";}	
}


function c4cUti_getFireFoxVersion() {
try {
var info = Components.classes["@mozilla.org/xre/app-info;1"]
	           .getService(Components.interfaces.nsIXULAppInfo);
	// Get the name of the application running us
	info.name; // Returns "Firefox" for Firefox
	info.version; // Returns "2.0.0.1" for Firefox version 2.0.0.1//	var osString = Components.classes["@mozilla.org/xre/app-info;1"].getService(Components.interfaces.nsIXULRuntime).OS;
    return info.name + " " + info.version;
} catch(e) {return "err";}	
}

var c4cUti_allExtensionsVersionData = new Array();
var c4cUti_allExtensionsVersionDataDone = false;
var c4cUti_c4cXpiPath = '';

function c4cUti_getC4cXpiPath() {return c4cUti_c4cXpiPath;}

function c4cUti_setupAllExtensionsVersion(doneCB) {
try {
	function makeData(exts) {
	
		exts.forEach(function (ext) {
			if (!ext.enabled) return;
			var o = new Object();
			o["name"] = ext.name;
			o["version"] = ext.version;
			c4cUti_allExtensionsVersionData.push(o);
		});
		
		c4cUti_allExtensionsVersionDataDone = true;
		if (doneCB) doneCB();
    };			   

    if (Application.extensions) {
        // Gecko 1.9.0 - 1.9.2
        exts = Application.extensions.all;
		makeData(exts);
    } else {
        // Gecko 2.0.0 and newer
        Application.getExtensions(function(exts) {
		    makeData(exts.all);
        });
		
		// get addon source XPI. only in ff4
		Components.utils.import("resource://gre/modules/AddonManager.jsm");
		AddonManager.getAddonByID('check4change-owner@mozdev.org', 
			function (d) {
				if (d && (d.sourceURI)) c4cUti_c4cXpiPath = d.sourceURI.spec;
			});

    }

} catch(e) {return false;}	
}

function c4cUti_getAllExtensionsVersion() {
try {
	if (c4cUti_allExtensionsVersionDataDone) return c4cUti_allExtensionsVersionData;
		 
	return null;
} catch(e) {return null;}	
}	

function c4cUti_getExtensionVersion(name) {
try{
	if (name == null) name = "Check4Change";

	var res = null;
	if (c4cUti_allExtensionsVersionDataDone) {
		c4cUti_allExtensionsVersionData.forEach(function(ext) {
		    if (ext["name"] == name) res = ext["version"];
		});
	}
	return res;
	
} catch(e) {return null;}	
}

function c4cUti_getExtensionIsFirstRun() {
try {
  var res = false;
  
  var ver = c4cUti_getExtensionVersion();
  if (!ver) return false;
  
  var cur_ver = c4cUti_getPrefString("version");
  if (ver != cur_ver) {
	  res = true;
	  c4cUti_setPrefString("version", ver);
  }
  
  return res;

} catch(e) {return false;}
}

var c4cUti_uninstallCallback=null;
var c4cUti_addOnListener = {
  onUninstalling: function(addon) {
    if (addon.id == "check4change-owner@mozdev.org") {
		if (c4cUti_uninstallCallback != null) c4cUti_uninstallCallback(1);
    }
  },
  onDisabling: function(addon) {
    if (addon.id == "check4change-owner@mozdev.org") {
		if (c4cUti_uninstallCallback != null) c4cUti_uninstallCallback(2);
	}
  }
}

function c4cUti_setUninstallCallback(func) {
	try {
	c4cUti_uninstallCallback = func;
	Components.utils.import("resource://gre/modules/AddonManager.jsm");
	AddonManager.addAddonListener(c4cUti_addOnListener);
	} catch (ex) {}	
}	

var c4cUti_cookieBugFlag = false;

function c4cUti_setCookie(url, key, value, domain) {
try {
	var ios = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
	var cookieUri = ios.newURI(url, null, null);
	var cookieSvc = Components.classes["@mozilla.org/cookieService;1"].getService(Components.interfaces.nsICookieService);
	var domainStr = "";
	if (domain) domainStr = "domain="+domain;
	cookieSvc.setCookieString(cookieUri, null, key+"="+value+";"+domainStr+";expires=Thu, 15 Jan 2029 15:24:55 GMT", null);
	
	// test if it worked.
	var cookie = cookieSvc.getCookieString(cookieUri, null);
	var ok = ((cookie != null) && (cookie.indexOf(key) >= 0));
	c4cUti_cookieBugFlag = !ok;
	return ok;
} catch (e) {
	c4cUti_cookieBugFlag = true;
	return false;
}	
}


/**
 * Installs the toolbar button with the given ID into the given
 * toolbar, if it is not already present in the document.
 *
 * @param {string} toolbarId The ID of the toolbar to install to.
 * @param {string} id The ID of the button to install.
 * @param {string} afterId The ID of the element to insert after. @optional
 */
function c4cUti_installButton(toolbarId, id, afterId) {
    if (!document.getElementById(id)) {
        var toolbar = document.getElementById(toolbarId);

        var before = toolbar.firstChild;
        if (afterId) {
            let elem = document.getElementById(afterId);
            if (elem && elem.parentNode == toolbar)
                before = elem.nextElementSibling;
        }

        toolbar.insertItem(id, before);
        toolbar.setAttribute("currentset", toolbar.currentSet);
        document.persist(toolbar.id, "currentset");

        if (toolbarId == "addon-bar")
            toolbar.collapsed = false;
    }
}

// returns all DOM elements of type <type> that contain <str>
function c4cUti_getDomElementsContaining(doc, type, str) {
	var a = doc.getElementsByTagName(type);
	var res=[];
	for (var i=0;i<a.length;i++) {
		if (a[i].innerHTML.search(str) >= 0) res.push(a[i]);
	}
	return res;
}

// return first DOM element of type <type> that contain <str>
function c4cUti_getFirstDomElementContaining(doc, type, str) {
	var arr = c4cUti_getDomElementsContaining(doc, type, str);
	if (arr.length > 0) return arr[0];
	return null;
}

// return [x,y] cords of given dom element
function c4cUti_getElePos(obj) {
	var curleft = curtop = 0;
	if (obj.offsetParent) {
		do {
			curleft += obj.offsetLeft;
			curtop += obj.offsetTop;
		} while (obj = obj.offsetParent);
		return [curleft,curtop];
	}
}	


//-------------------------------------------------------------------------------


function c4cUti_init(done) {
	c4cUti_soundInit();
	c4cUti_setupAllExtensionsVersion(done);
}
