# Marijn's Ajax request library that deals with requests that can
#	1) be cancelled
#	2) be queued
#	3) continue in the background


var req_id = 0;
# 3 types of ajax channels
var ch=[], enFG=0, enQ=1, enBG=2, enOL=3;

#Additional stuff for the default channel
var http;
# Additional stuff for the queued channels (mainly enQ but also enBG)
var qchan_busy=[], qchan_queue=[], qchan_info=[];
var ajax_abort=false;

function createRequestObject() {
	dbg('AJAX','fns createRequestObject');
	var ro;
	var browser = navigator.appName;
	if(browser == "Microsoft Internet Explorer"){
		ro = new ActiveXObject("Microsoft.XMLHTTP");
	}else{
		ro = new XMLHttpRequest();
	}
	return ro;
}

function init_qchan(chan)
{
	qchan_queue[chan]=new Array();
	qchan_queue[chan].log=[];
	qchan_queue[chan].ntot=0;
	ch[chan]=createRequestObject();	
}
function ajax_init()
{
	init_qchan(enQ);
	init_qchan(enBG);
	init_qchan(enFG);
	init_qchan(enOL);
	http=ch[enFG];	// nicer access to the default channel in callbacks
}

function channel_correct(chan)
{
	if (!chan){
		if (!ch[enFG]) 
			ch[enFG]=createRequestObject();
		chan = ch[enFG];
	}	
	return chan;
}

function abort_req(chan)
{
	ajax_abort=true
	chan.abort();
	ajax_abort=false;
}

function ajax_complete(chan,cb,timeout){
	if(chan.readyState != 4) return;
	var timeout=0;
	var c=chan2flag(chan);
	if(c==enFG)	{
		qchan_log_end(c);
		if(ajax_abort) return;
	}
	if(window.catch_errors_disable){
		cb(chan,timeout);	
	}else{
		try{
			cb(chan);
		}catch(e){
			var reason='Unknown error on ajax request. Please report this code to support: ';
			if(e.name && e.message) reason+= e.name+':'+e.message;
			reason+='<br> ['+str_trim(qchan_info[c],70)+']';
			set_status(reason,'warning');
			timeout=5000;
		}
	}
	if(c==enQ || c==enBG || c==enOL)
		setTimeout(function(){queue_do_next(c)},timeout);
	
};



# --- Handle actual sending of ajax http requests ---
# Send non cachable "GET" request 
function sndReq(action,cb,chan)
{
	fault_check_begin();
	dbg("AJAX","fns sndReq: "+str_trim(action,35));
	if(!chan) {
		chan=channel_correct(chan);	
		abort_req(chan)
		qchan_log_start(enFG, action);
	}else{
		chan=channel_correct(chan);	
	}
	req_id++;
	response={};
	var request = cgibase+action+"&sid="+sid+"&if=ajax&zid="+req_id+"-"+mtime();
	store_info(chan,action);
	chan.open('get', request);
	chan.onreadystatechange = function(timeout){ajax_complete(chan,cb,timeout);};
	chan.send(null);
	fault_check_end();
}

# Yay a standards way to support this now!!
# http://www.webdavsystem.com/ajax/programming/cross_origin_requests
function sndCorsReq(baseurl,action,cb,chan)
{
	fault_check_begin();
	dbg("AJAX","fns sndReq: "+str_trim(action,35));
	req_id++;
	response={};
	var request = baseurl+"?"+action+"&zid="+req_id+"-"+mtime();
	store_info(chan,action);
	chan.open('get', request);
	chan.onreadystatechange = function(timeout){ajax_complete(chan,cb,timeout);};
	chan.send(null);
	fault_check_end();
}




# Send cachable "GET" request
function sndReqCached(action,cb, chan)
{
	fault_check_begin();
	dbg("AJAX","fns sndReqCached: "+str_trim(action,35));
	if(!chan) qchan_log_start(enFG, action);
	chan=channel_correct(chan);	
	req_id++;
	response={};
	var request = cgibase+action;
	store_info(chan,action);
	chan.open('get', request);
	chan.onreadystatechange = function(){ajax_complete(chan,cb);};
	chan.send(null);
	fault_check_end();
}
function sndReqCached2(action,cb, chan)
{
	fault_check_begin();
	dbg("AJAX","fns sndReqCached: "+str_trim(action,35));
	if(!chan) qchan_log_start(enFG, action);
	chan=channel_correct(chan);	
	req_id++;
	response={};
	var request = action;
	store_info(chan,action);
	chan.open('get', request);
	chan.onreadystatechange = function(){ajax_complete(chan,cb);};
	chan.send(null);
	fault_check_end();
}


# Send non cachable "POST" request
function sndReqPost(action,cb, chan)
{
	fault_check_begin();
	dbg("AJAX","fns sndReqPost: "+str_trim(action,35));
	if(!chan) qchan_log_start(enFG, action);
	chan=channel_correct(chan);	
	req_id++;
	response={};
	var params=action+"&sid="+sid+"&if=ajax&zid="+req_id;
	store_info(chan,action);
	chan.open('POST', cgibase);
	chan.setRequestHeader("Content-type", "application/x-www-form-urlencoded; charset=UTF-8");
	chan.onreadystatechange = function(){ajax_complete(chan,cb);};
	chan.send(params);
	fault_check_end();
}

function sndReqFile(action,file,cb, chan, paste)
{
	fault_check_begin();
	dbg("AJAX","fns sndReqPost: "+str_trim(action,35));
	if(!chan) qchan_log_start(enFG, action);
	chan=channel_correct(chan);
	req_id++;
	response={};
	var request = cgibase+action+"&sid="+sid+"&zid="+req_id+"-"+mtime();
	store_info(chan,action);
	chan.open('POST', request);
	chan.setRequestHeader('X-Surgeweb-Ajax-Upload', 'true');
	var name = file.name ? file.name : file.fileName;
	var size = file.size ? file.size : file.fileSize;
	if(paste){
		name = "pasted_image.png";
		size = file.length;
		chan.setRequestHeader("Content-Type", "application/octet-stream");
	}
	chan.setRequestHeader('X-File-Name-Enc', encodeURIComponent(name));
	try{
		chan.setRequestHeader('X-File-Name', name);
	}catch(e){
#		// This sometimes fails, ignore but keep for surgemail binary downgrade compatibility
	}
	
	chan.setRequestHeader('X-File-Size', size);
	chan.onreadystatechange = function(){ajax_complete(chan,cb);};
	if(paste){
		chan.sendAsBinary(file);
	}else{
		chan.send(file);	
	}
	fault_check_end();
}

XMLHttpRequest.prototype.sendAsBinary = function(datastr) 
{
    function byteValue(x) {
        return x.charCodeAt(0) & 0xff;
    }
    var ords = Array.prototype.map.call(datastr, byteValue);
    var ui8a = new Uint8Array(ords);
    this.send(ui8a.buffer);
}

function post_form_field(jmsg,i,boundary)
{
	var hdr = 'Content-Disposition: form-data; name="'+(i=='msg_html'||i=='msg_text'?'msg_body':i)+'"';
	return boundary+'\r\n'+hdr+'\r\n\r\n'+jmsg[i]+'\r\n';
}

# Send non cachable "POST" request (with preformatted form data)
function sndReqPost2(action,jmsg,cb,chan)
{
	var param={};
	param.to=jmsg.to; param.cc=jmsg.cc; param.bcc=jmsg.bcc;
	param.subject=jmsg.subject;
	if(jmsg.msg_body_type=='text'){
		param.msg_text=jmsg.msg_text;
		param.msg_body_type=jmsg.msg_body_type;
	}else{
		param.msg_html=jmsg.msg_html;
	}

	return ajaxPost(action,param,cb,chan);
}

function ajaxPost(action,param,cb,chan)
{
	fault_check_begin();
	var requestbody = ''
	dbg("AJAX","fns sndReqPost2: "+str_trim(action,35));
	if(!chan) qchan_log_start(enFG, action);
	chan=channel_correct(chan);	
	var boundaryString = 'surgeweb_123';
	var boundary = '--' + boundaryString;
	for (i in param) {
		requestbody+=post_form_field(param,i,boundary);
	}
	requestbody += boundary+'--\r\n';
	req_id++;
	response={};
	var request = cgibase+action+"&sid="+sid+"&zid="+req_id+"-"+mtime();
	store_info(chan,action);
	chan.open('POST', request);
	chan.setRequestHeader("Content-type", "multipart/form-data; charset=UTF-8; boundary=\"" + boundaryString + "\"");
	chan.onreadystatechange = function(){ajax_complete(chan,cb);};
	chan.send(requestbody);
	fault_check_end();
}

function ajax_param(param,name,value)
{
	if(!value)return;

#	var txt = '';
#	if(typeof value == 'object'){
#		txt = serialise(o,false,false);
#	}else{
#		txt = value;
#	}
	param[name]=value;
}


function apiGet(action,cb,chan)
{
	fault_check_begin();
	dbg("AJAX","fns apiGet: "+str_trim(action,35));
	if(!chan) {
		chan=channel_correct(chan);	
		abort_req(chan)
		qchan_log_start(enFG, action);
	}else{
		chan=channel_correct(chan);	
	}
	req_id++;
	response={};
	var request = apibase+action+"&sid="+sid; //+"&if=ajax&zid="+req_id+"-"+mtime();
	store_info(chan,action);
	chan.open('get', request);
	chan.onreadystatechange = function(timeout){ajax_complete(chan,cb,timeout);};
	chan.send(null);
	fault_check_end();
}
function apiPost(action,param,cb,chan)
{
	fault_check_begin();
	var requestbody = ''
	dbg("AJAX","fns apiPost: "+str_trim(action,35));
	if(!chan) qchan_log_start(enFG, action);
	chan=channel_correct(chan);	
	var boundaryString = 'surgeweb_123';
	var boundary = '--' + boundaryString;
	for (i in param) {
		requestbody+=post_form_field(param,i,boundary);
	}
	requestbody += boundary+'--\r\n';
	req_id++;
	response={};
	var request = apibase+action+"&sid="+sid; //+"&zid="+req_id+"-"+mtime();
	store_info(chan,action);
	chan.open('POST', request);
	chan.setRequestHeader("Content-type", "multipart/form-data; charset=UTF-8; boundary=\"" + boundaryString + "\"");
	chan.onreadystatechange = function(){ajax_complete(chan,cb);};
	chan.send(requestbody);
	fault_check_end();
}


# --- Handle request queueing ---

# Allow some requests to be queued until channel is ready
function queued_req_add(ch,info,fn,p1,p2,p3,p4,p5)
{
	if (!qchan_busy[ch]){
#		dbg('queued_req_add do directly')
		qchan_busy[ch]=true;
		if (ch==enQ) { class_add(sw.busy,'busy_busy'); class_remove(sw.busy,'busy_loading');}
		qchan_log_start(ch,info);
		fn(p1,p2,p3,p4,p5);
#		{action do_delete, param selecton}	
	}else{
		dbg('queued_req_add add to queue')
		o=new Object();
		o.info=info;
		o.fn=fn; o.p1=p1; o.p2=p2; o.p3=p3; o.p4=p4; o.p5=p5;
#		might need more of an environment of stuff that we need to use later like 'current folder'
#		but this aught to do for now
		qchan_queue[ch].push(o);
	}
}
function queue_do_next(ch)
{
	qchan_log_end(ch);
	if(qchan_busy[ch] && qchan_queue[ch].length>0){
#		dbg('queue_do_next do another')
		x=qchan_queue[ch].shift();
		qchan_log_start(ch,x.info);
		x.fn(x.p1,x.p2,x.p3,x.p4,x.p5);
	}else{
#		dbg('queue_do_next done')
		qchan_busy[ch]=false;
		if (ch==enQ) {class_remove(sw.busy,'busy_busy'); class_remove(sw.busy,'busy_loading');}
	}
}

# --- Generic response handling ---	Still needs major tidyup

# Response handling functions

# Javascript fragment expected so just run it
function actionReqJs(chan){				// response to request
	if (chan.status!=200){
		success_string=err_comms(7);
		return false;
	}
	var txt=chan.responseText;
	success=true;
	dbg('fns actionReqJs (bytes='+txt.length+" "+firstlines(txt,'js')+')');			
	eval(txt);
	return success;
}

// Html fragment only (CURRENTLY called from ~edit.htm & ~options.htm page response)
function actionReqSethtml(txt,nodeid,revert_main){				// response to request
	var frag;
	dbg('fns actionReq2b (bytes='+txt.length+" "+firstlines(txt,'html')+')');			

	//do different stuff depending on what node we want to fill
	if(!sw.is_popup && sw.active && nodeid!=sw.active.id){
		window_manager_minimise(sw.active,revert_main);
	}

	var dummy=document.getElementById("dummy");
	dummy.innerHTML=txt;
	
	action_html_result();
	var nodes=dummy.childNodes;
	for(var i=0;i<nodes.length;i++)
		if (nodes[i].tagName=="div" || nodes[i].tagName=="DIV")
			frag=nodes[i];

	if (!frag) return;
	
	sw.util.appendChild(frag);
#	sw.util.insertBefore(frag,sw.util.firstChild);
	return frag;
}

// Javascript fragment response to command
function actionReqJsStat(chan){				// response to request
	if (chan.status!=200){
		success_string=err_comms(8);
		return false;
	}
	var txt=chan.responseText;
	success=true; success_string="";
	dbg('fns actionReqJsStat (bytes='+txt.length+" "+firstlines(txt,'html')+')');			
	eval(txt);
	if(!success) set_status(success_string,'warning');
	return success;
}

#// Html fragment - with javascript result embedded in html <span> (yuck!!)
#// This can be called on requests from cache so no channel status checking here!
function actionReqSethtmlStat(txt,node){				// response to request
	dbg('fns actionReq2a (bytes='+txt.length+" "+firstlines(txt,'html')+')');			
	if(!node) node='xdummy';
	var o=document.getElementById(node);
	o.innerHTML=txt;
	scroll_top(o);

	temp_stuff=txt;
	
	action_html_result();
	return true;	// not strictly true yet...
}

#// All a bit yuck, must be a better way...
function action_html_result()
{
	var node=document.getElementById('result');
	if (node){
		eval(node.innerHTML);
		node.parentNode.removeChild(node);
	}
}

var success;		// This is DEFINITELY NOT the final solution, but will do for now
var success_string;
function add_error(type,txt)
{
	success = false;	
	if (success_string.length>0) success_string+="<br>";
	success_string += '<span class="error_'+(type=='surgeweb'?type:'other')+'">'+txt+'</span>';
}
function reset_errors()
{
	success_string = "";
}

function store_info(chan,action)
{
	var c=chan2flag(chan);
	qchan_info[c]=action;
}
function chan2flag(chan)
{
	switch(chan){
		case ch[enBG]: return enBG;
		case ch[enQ]: return enQ;
		case ch[enFG]: return enFG;
	}		
}

function qchan_debug()
{
	var arr=[];
	arr.push(qchan_show_queue(enFG));
	arr.push(qchan_show_queue(enQ));
	arr.push(qchan_show_queue(enBG));
	return arr.join('');
}

function qchan_show_queue(chan)
{
	var arr=[''];
	var q=qchan_queue[chan];
	var len=q.length;
	var log=q.log;
	var q_name=(chan==enBG?'$$st_status_bg$$':chan==enQ?'$$st_status_fg$$':'$$st_status_unqueued$$');
	var queued=(chan!=enFG);
	var extra=(queued?' pending='+len:'');				
	
	arr.push('* '+q_name+' <span style="font-size:10px">total='+(q.ntot+len)+extra+'</span><br>');
	if(len>0){
		for(i=len-1;i>=0;i--){
			extra=(queued?' (pending)':'');				
			arr.push('<span style="color:blue;font-size:10px;"> - '+time_str2(log[i].t1)+' '+q[i].info+extra+'</span><br>');
		}
	}

	len=log.length-1;
	if(len<0) return arr.join('');
	if(log[len].status=='started'){
		extra=(true?' (active '+(mtime()-log[len].t1)+'ms)':'');	
		arr.push('<span style="color:red;font-size:10px;"> - '+time_str2(log[len].t1)+' '+log[len].info+extra+'</span><br>');
		len--;
	}
	for(i=len;i>=0;i--){
		extra=(true?' ( '+(log[i].t2-log[i].t1)+'ms)':'');
		arr.push('<span style="color:#777;font-size:10px;"> - '+time_str2(log[i].t1)+' '+log[i].info+extra+'</span><br>');
	}
	return arr.join('');
}	
function qchan_log_start(ch,txt)
{
	var q=qchan_queue[ch]
	if(ch==enFG){
		txt=txt.substring(0,38)+'...';
	}
	q.ntot++;
	q.log.ntot++;
	q.log.push({'info':txt,'status':'started',t1:mtime()});
	if (q.log.length>15) q.log.shift()
}
function qchan_log_end(ch)
{
	var q=qchan_queue[ch]
	var item=q.log[q.log.length-1];
	if(!item) return;
	item.status='completed';
	item.t2=mtime();
}

var fault_check=false;
function fault_check_begin()
{
	dbg('***** ++')
	if(fault_check && isChrome){		// IE triggers this when cached web requests are used!
		x_alert(
			'<div style="margin-left:60px;">'+
			'Google Chrome bug - requests to server will fail. '+
			'If you are NOT running google chrome 4.0.233.16 please report this fault to surgemail-support@netwinsite.com, '+
			'along with the status info after clicking the round greeen info icon. <br><br>'+
			'You will need to login again to continue using surgeweb.'+
			'</div>');
	}
	fault_check=true;
}
function fault_check_end()
{
	dbg('***** --')
	fault_check=false;
}