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

//-----------------------  c4cJob ---------------------

// Static part
var c4cJob_jobs = new Array();
var c4cJob_countDownTID = null;

function c4cJob_registerJob(job) {
	c4cJob_jobs.push(job);

	// handle countdown
	// first job
	if (c4cJob_jobs.length == 1) {
		c4cJob_countDownTID = window.setInterval(function()	{c4cJob_updateCountDownTabsLabel();}, 300);
	}
}

function c4cJob_unregisterJob(job) {
	c4cUti_removeValue(c4cJob_jobs, job);
	
	c4cJob_restoreCountDownTabLabel(job.tab);
	if (c4cJob_jobs.length == 0) {
		if (c4cJob_countDownTID) window.clearInterval(c4cJob_countDownTID);
		c4cJob_countDownTID = null;
	}
	
}

function c4cJob_updateCountDownTabsLabel() {
	for (var x in c4cJob_jobs) {
	var job = c4cJob_jobs[x];
	if (!job.tab) continue;
	if (!job.params.doCountDown) continue;
	
	var str = "loading...";
	if (job.isState(c4cJob_STATE_RUNNING)) {
		var remaining = job.timeToPulse();
		remaining = Math.round(remaining/1000);
		str = c4cUti_timeInRelavantUnits(remaining, true);
	}
	
	c4cSystem_tabHack_setLabel(job.tab, str + "|" + job.tab.getAttribute('label'));
	}
}

function c4cJob_restoreCountDownTabLabel(tab) {
	c4cSystem_tabHack_resetLabel(tab);
}


// msg is the response from the server.
// if its not "OK!" show an alert box.
function c4cJob_sendEmailProcessResponse(msg) {
	if (c4cUti_endsWith(msg,"OK!")) return;
	
	c4cUti_alert(window, "Email Notification Error", msg);
}


// CONSTS
var c4cJob_STATE_RUNNING = 1;
var c4cJob_STATE_RELOADING = 2;
var c4cJob_STATE_STOPPED = 3;
var c4cJob_STATE_PAUSED = 4;


//-------------------- c4cJob object
/*design
job ia an object containing info about a c4c job.

job.params - object with user set parameters like duration and notification options
job.meta - object containing browser info about the job like: location, post data, referer, selected text, scroll position.
job.state - current state of job. can be RUNNING, RELOADING.
job has a pulse that is always firing. If state is not RUNNING it is ignored. If not job.doRefresh is called.

Overideable functions
doRefresh() - do a refresh of the current page
handlePageLoad(isPulse, status) - handle tab loading. return TRUE if we are on track and check shoud be made. returning FALSE means no pulse! Its up to the system to create pageloads to fix it. useful for multi step processes.
c4cJob_hasChanged() - test for changes. 
c4cJob_goAgain() - prepare to go again after change
*/


function c4cJob() {
	this.constructor = c4cJob_constructor;
	this.setParams = c4cJob_setParams;
	this.setupFromTab = c4cJob_setupFromTab;
	this.updateJob = c4cJob_updateJob;
	this.start = c4cJob_start;
	this.stop = c4cJob_stop;
	this.pause = c4cJob_pause;
	this.unpause = c4cJob_unpause;
	this.pulse = c4cJob_pulse;	
	this.killPulse = c4cJob_killPulse;
	this.pulseCallback = c4cJob_pulseCallback;
	this.doRefresh = c4cJob_doRefresh;
	this.pageLoadCallback = c4cJob_pageLoadCallback;
	this.pageLoadAfterDelayCallback = c4cJob_pageLoadAfterDelayCallback;
	this.handlePageLoad = c4cJob_handlePageLoad;
	this.testForChange = c4cJob_testForChange;	
	this.goAgain = c4cJob_goAgain;	
	this.hasChanged = c4cJob_hasChanged;
	this.onLocationChange = c4cJob_onLocationChange;
	this.doNotifications = c4cJob_doNotifications;
	this.sendEmail = c4cJob_sendEmail;	
	this.setState = c4cJob_setState;
	this.isState = c4cJob_isState;
	this.logStart = c4cJob_logStart;
	this.logChange = c4cJob_logChange;
	this.logEnd = c4cJob_logEnd;
	this.nextRefreshInterval = c4cJob_nextRefreshInterval;
	this.timeToPulse = c4cJob_timeToPulse;
	this.isRunning = c4cJob_isRunning;
	this.isAlive = c4cJob_isAlive;
	this.restoreSelection = c4cJob_restoreSelection;
	this.pulseNow = c4cJob_pulseNow;
	this.prepareEmail = c4cJob_prepareEmail;

	//---------------------------------
	this.setState(c4cJob_STATE_STOPPED);
}

function c4cJob_constructor(params, tab) {
	this.setParams(params);
	
	this.tab = tab;
	this.win = c4cUti_getWindowFromTab(this.tab);
	
	this.setupFromTab();
}

function c4cJob_setParams(params) {
	this.params = params;
}

// setup meta info from tab. take locatio, selection, scroll, post, referer
function c4cJob_setupFromTab() {

	// Save Meta Data Info
	this.meta = {};
	// save location. toString so we have the actual string and not the reference to the location object.
	this.meta.location = this.win.document.location.toString();
	// get post and refererer
	var metaData = c4cUti_getTabMetaData(this.tab);
	this.meta.postData = metaData.postData;
	this.meta.referrer = metaData.referrer;
	
	this.meta.selectionObj = new c4cSelection(this.win, this.params.doAbsPos);
}

// update job configuration. update params and selection if provided. if not, use existing.
function c4cJob_updateJob(params) {
	// existing job
	this.setParams(params);			
	// if there is a selection, update the ranges.
	// otherwise leave ranges as is.
	if (c4cUti_hasSelection(this.win)) {
		this.setupFromTab(this.tab);
	}
	
	this.stop();
	this.start();
}


function c4cJob_start() {	
	// save start TS
	this.startTS = new Date().toLocaleString();

	// update statistics
	c4cStats_newJob(this);

	// handle log
	this.logStart();

	// set icon
	c4cSystem_tabHack_hack(this.tab);
	c4cSystem_tabHack_setIcon(this.tab, c4cSystem_getC4CTabIconFile());

	// set state
	this.setState(c4cJob_STATE_RUNNING);
	
	// be findable
	c4cJob_registerJob(this);
	
	// start the first pulse
	this.pulse(true);	
}

// This function kills the job.
function c4cJob_stop() {
	c4cSystem_tabHack_unhack(this.tab);
	
	this.killPulse();
	
	// handle log
	this.logEnd();

	this.setState(c4cJob_STATE_STOPPED);
	
	c4cJob_unregisterJob(this);
}

function c4cJob_pause() {
	this.killPulse();
	this.setState(c4cJob_STATE_PAUSED);
}

function c4cJob_unpause() {
	if (this.isState(c4cJob_STATE_PAUSED)) {
		this.setState(c4cJob_STATE_RUNNING);
		this.pulse(true);	
	}
}


// The timeout pulse. Automatically starts another one.
function c4cJob_pulse(isFirst) {
	var time = this.nextRefreshInterval();
	// set callback
	var curJob = this;
	this.pulseTID = window.setTimeout(function() {curJob.pulse();}, time, null);
	
	// store pulse time
	this.pulseTime = new Date().getTime() + time;
	
	if (isFirst !== true) {
		// call the pulse callback
		this.pulseCallback();
	}	
}

function c4cJob_killPulse() {
	window.clearTimeout(this.pulseTID);
	this.pulseTID = null;
}


function c4cJob_pulseCallback() {
try{
	if (!c4cUti_validTab(this.tab)) {
		this.stop();
		return;
	}

	//*****Reasons not to reload
	// not running
	if (!this.isState(c4cJob_STATE_RUNNING)) return;

	// if tab is being reloaded don't reload (means c4c acted while user click page was loading)
	// if stopWhenViewing is selected and tab is visible don't reload, instead reset the timer.
    if ((c4cUti_tabBusy(this.tab)) || 
	    (this.params.doStopWhenViewing && c4cUti_tabIsCurrent(this.tab))) {
			return;
	}
		
	
	// Do the reloading
	this.setState(c4cJob_STATE_RELOADING);

	// NOTE: move this 'if' into doRefresh function
	if (this.params.dontReload) {
		this.pageLoadAfterDelayCallback(0);
	} else {
		this.doRefresh();
	}

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


// do actual refreshing of the current page.
// should get pageLoadCallback called.
// OVERRIDEABLE
function c4cJob_doRefresh() {
	// reload the location. This should eventually get the reloadedCallback called.
	c4cUti_loadURL(this.tab, this.meta.location, this.meta.postData, this.meta.referrer);
}

// the system callback when the tab is loaded.
// handles the internals and calls the handlePageLoad function which can be overrided.
function c4cJob_pageLoadCallback(status) {
try{
	// wait delay to let page settle down. 
	// settling down may include more page loads.
	var delay = parseInt(this.params.delayDuration)*1000;
	if (isNaN(delay)) delay = 3000; // Just In Case.

	// if reloaded while delaying, reset the delay
	if (this.delayTID != null) {
		window.clearTimeout(this.delayTID);	
	}
	
	// wait the delay
	var curJob = this;
	this.delayTID = window.setTimeout(function(){curJob.pageLoadAfterDelayCallback(status);},delay, null);
	

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

// this callback is called after the delay when page has settled for some time.
function c4cJob_pageLoadAfterDelayCallback(status) {
	this.delayTID = null;
	
	// update statistics
	c4cStats_refresh();

	// if we are running its a user created event
	// NOTE this will cause a user generated multi step in which there are custom states, to be marked as pulse.
	var isPulse = true;
	if (this.isRunning()) {
		isPulse = false;
	}

	var res = this.handlePageLoad(isPulse,status);
	if (res) {
		// if should check it means we are back on track.
		this.setState(c4cJob_STATE_RUNNING);
		this.testForChange();
	}	
}


// handle the page load.
// return BOOLEAN if should testForChange()
// OVERRIDEABLE
function c4cJob_handlePageLoad(isPulse, status) {
	// check for any error (such as net time out).
	// currently, all errors cause a silent continuation of monitoring.
	if (status > 0) {
		this.setState(c4cJob_STATE_RUNNING);	
		return false;
	}
	
	// if its not the same location
	if (this.win.document.location != this.meta.location) {
		// if this is a reload+rediect report error unrecoverable error and die.
		// how would we know its this and not a navaway?
		// if it was a result of a pulse, it must be.
		// if navaway is disabled. The popup should have gotten it.
		// note: we are missing the case where navaway is allowed and user pressed F5 and got redirected. Tough cookie...
		if (isPulse || (!this.params.doAllowNav)) {
			c4cUti_focusTab(this.tab);
			c4cUti_alert(window, "Check4Change Error", "Tab location changed after loading. Check4Change stoped.");
			this.stop();
			return false;
		}		
		// as this is a non pulse, just ignore it:
		// At pulse either we get back, or we report the error above.
		return false;
	}
	
	// alls well. check page.

	// restore page state
	if (this.params.doReselect) {
		this.restoreSelection();
	}

	return true;
    
}


function c4cJob_testForChange() {
try{
	// if change detected notify.
	if (this.hasChanged()) {
	
		// update statistics
		c4cStats_change();

		// do notifications
		this.doNotifications();
			
		// should keep going?
		var again = this.params.doContinue;
		// check if we should continue
		if (again) {
			this.goAgain();
		} else {
			this.stop();
		}
	} 

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


// prepare to continue monitoring after a change was found.
// OVERRIDEABLE
function c4cJob_goAgain() {
	this.meta.selectionObj.updateStoredText(this.win);
}


// return BOOLEAN if change has been detected
// OVERRIDEABLE
function c4cJob_hasChanged() {
	return this.meta.selectionObj.hasSelectionChanged(this.win, this.params.doAbsPos);
}



// we want to alert for c4c nav away but only if its a user initiated event.
// so we decide its not (and return true) if:
// 0. its the monitored url.
// 1. tab is not infront
// 2. change is within the waiting time for the actual testing. (this catches immediate redirects)
function c4cJob_onLocationChange(url) {
try{
	var res = true;
	if (this.params.doAllowNav) return res;
	if (!this.isState(c4cJob_STATE_RUNNING)) return res;
	if (!c4cUti_validTab(this.tab)) return res;
	if (url == this.meta.location) return res;
	if (!c4cUti_tabIsCurrent(this.tab)) return res;
	
	// otherwise, its a navaway.
	res = c4cUti_confirm(window, "Navigate Away?", "You are navigating away from a C4C enabled page.\nAre you sure you want to go and stop C4C?\n\n(If this is a false alarm, you can remove this alert from the Options window.)");
	if (res) {
  	    this.stop();	
	}
	return res;

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



function c4cJob_doNotifications() {
		// do notifications!!!!
		if (this.params.notifyAnim)
			c4cSystem_doAnimation(this.tab);

		if (this.params.notifyTaskBar)
			c4cUti_flashTaskBar();

		if (this.params.notifyPopup)
			c4cSystem_popupAlert(this.tab, "Change Found!", this.meta.location);

		if (this.params.notifySound)
			c4cSystem_playSound(this.params.notifySoundContinuous);

		if (this.params.notifyFocus)
			c4cUti_focusTab(this.tab);

		this.logChange(); // test internally
		
		if (this.params.notifyEmail)
			this.sendEmail();
		

}

function c4cJob_prepareEmail() {
	var obj = new Object();	
	obj["location"] = this.meta.location;
	obj["orig_txt"] = this.meta.selectionObj.getStoredText();
	obj["new_txt"] = this.meta.selectionObj.getSelectionText(this.win);
	obj["startTS"] = this.startTS;
	obj["changeTS"] = new Date().toLocaleString();
	obj["duration"] = this.params.duration + " " + this.params.units;
	var data = c4cUti_Base64.encode(JSON.stringify(obj));
	
	var res = {};
	res.url = "http://www.check4change.com/vip/doEmail.php";
	res.data = data;
	return res;
}


function c4cJob_sendEmail() {
	var res = this.prepareEmail();
	
	c4cUti_urlPost(res.url+"?"+c4cStats_getQueryString(), res.data, 
												c4cJob_sendEmailProcessResponse, 
												function() {c4cJob_sendEmailProcessResponse("Failure connecting to Check4Change server.");}); 
}


function c4cJob_logStart() {
  if (!this.params.notifyLog) return;
  
  var topWindow = c4cUti_getWindowFromTab(this.tab);
  var now = new Date();
  var logname = topWindow.document.location.hostname + "_" + now.getFullYear() + "-" + (now.getMonth()+1) + "-" + now.getDate() + "_" + now.getHours() + "-" + now.getMinutes() + "-" + now.getSeconds()+'.txt';
  var logfile = c4cUti_appendFileToDir(this.params.logDir, logname);
  this.logFile = logfile;
  
  var str = "Check4Change job started at " + new Date().toLocaleString();
      str += "\r\nInterval : " + this.params.duration + " " + this.params.units + "\r\nURL : " + this.meta.location+"\r\n";
      str += "\r\n";  

 
  c4cUti_appendToFile(this.logFile, str);
  
  this.logChange(); // to show initial value
}

function c4cJob_logChange() {
try {
  if (!this.params.notifyLog) return;
  
  var changestr = this.meta.selectionObj.getSelectionText(this.win);
  if (changestr == null || changestr == "null" || changestr=="") changestr = "(*c4c: page structure changed.*)";
  
  var str =  new Date().toLocaleTimeString() + " | " + changestr + "\r\n";

  c4cUti_appendToFile(this.logFile, str);

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

function c4cJob_logEnd() {
  if (!this.params.notifyLog) return;
  
  var str = "\r\nCheck4Change job ended at " + new Date().toLocaleString() + "\r\n";
  str +=  "--------------------------------------------------------------------------------------------------------\r\n\r\n";
  
  c4cUti_appendToFile(this.logFile, str);
  
  
}

// calculate the time for the next refresh
function c4cJob_nextRefreshInterval() {
	var time = parseInt(this.params.duration);
	if (this.params.units == "hours") time*=3600;
	if (this.params.units == "minutes") time*=60;
	time = time * 1000;
	
	if (this.params.doRandom) {
		var p = parseInt(this.params.randomPercentage)/100.0;
		var minTime = (1-p)*time;
		if (minTime < 5) minTime = 5;
		var maxTime = (1+p)*time;

		// creata a random value between minTime and maxTime
		time = minTime + Math.random()*(maxTime-minTime);
		time = Math.floor(time);
	}
	
	// max time is 2^31-1
	if (time > 2147483647) time = 2147483647;

	return time;
}

function c4cJob_setState(state) {
	this.state = state;
}

function c4cJob_isState(state) {
	return (state == this.state);
}

function c4cJob_isRunning() {
	return this.isState(c4cJob_STATE_RUNNING);
}

function c4cJob_isAlive() {
	return !this.isState(c4cJob_STATE_STOPPED);
}


function c4cJob_timeToPulse() {
	return this.pulseTime - (new Date().getTime());
}

function c4cJob_restoreSelection() {
	this.meta.selectionObj.restoreSelection(this.win);
	this.meta.selectionObj.restoreScroll(this.win);	
}	

function c4cJob_pulseNow() {
	this.killPulse();
	this.pulse();
}
	