/*
 AJAX request methods.  Typical usage:
  
 <script>
 request('/some/url','handler(url,http_request)');
 
 function handler(url,http_request) {
   alert('call succeeded.\nurl: '+url+'\nmessage:'+http_request.responseText);
   var doc=http_request.responseXML;
   if (!doc) {
     alert('error reading response from: '+url);
     return;
    }
    var items=doc.getElementsByTagName('item');
		for (var i=0;i&lt;items.length;i++) {
		  var curr=items[i].firstChild.nodeValue;
		  sel.options[sel.options.length]=new Option(curr,curr);
		}
		var view=document.getElementById('view');
		// etc          
 }
 </script>


 the convenience method 
   serviceRequest(className,methodSig,params,handler) 
 may be useful for specifying service method calls.  ie:
   serviceRequest('org.my.SomeService','getSomething(String,Integer)','stringParam=val|intParam=2','handle(url,http_request)');
*/  
var url=null;
var includesRequest=true;
var NO_RESPONSE='*none*';
var NO_HANDLER=NO_RESPONSE;
var DEFAULT_ERROR_HANDLER='_onFail(url,http_request)';
// set to add detail to ajax loading, ensure to clear when done
var loadingDesc='';
// show the loading spinner
var showLoading=false;
var requestDebug=false;

var LOADING_IMAGE='<img src="/images/icons/loading2.gif"/>'
// do request to given url, calling method on success            
function request(url) {
  return requestWithError(url,null,DEFAULT_ERROR_HANDLER);
}

/**
 * Make request, calling given method on response.  Usage might resemble this:
 * <p/>
 * request('/svc/com.qtility.dms.DMSService?exec=getFactory(String)&amp;name=some',
 *   'onGet(url,http_request)');
 * <p/>
 * NOTE: the method must have two parameters: (url,http_request), as the string is eval'd and those are the names of the variables.
 * <p/>
 * A typical handler method would do something with the response, such as:
 * <pre>
  var requestDebug=true;
  
  function onGet(url,http_request) {
    if (requestDebug&&!confirm('Document:\n'+http_request.responseText))
      return;
    var doc=http_request.responseXML;
  
    alert('result/name is '+getByPath(doc,'result/name'));
  }
 * </pre>
 * 
 * @param url url to get xml from
 * @param responseMethod method to call on response, including parameters url,http_request
 */
function request(url,responseMethod) {
  return requestWithError(url,responseMethod);
}

// do request to given url, calling method on success and method on fail
function requestWithError(full,successMethod) {
  var params='xml=true&';
 url=full;
  var pos=full.indexOf('?');
  if (pos>-1) {
    params+=full.substring(pos+1);
    full=full.substring(0,pos);
  }
  
  var http_request = new XMLHttpRequest();
  http_request.onreadystatechange=function() {
//    alert('state: '+http_request.readyState);
    if (http_request.readyState==4) {
      document.getElementsByTagName("body")[0].style.cursor='default';
//    alert('state: '+http_request.readyState+" status: "+http_request.status);     
      var http_response=this;
//      if (http_request.status == 200 && successMethod!=NO_RESPONSE) {
    	if (_checkMethod(successMethod))
        eval(successMethod);
//      } else if (http_request.status == 500) {
//    alert('fail status: '+http_request.status+' calling: '+failMethod);
//    	if (_checkMethod(failMethod))
//    	  eval(failMethod);
//      }
    }
  };
  
  var bodyElems=document.getElementsByTagName("body");
  if (bodyElems && bodyElems.length>0)
    document.getElementsByTagName("body")[0].style.cursor='wait';
    
  http_request.open('POST', url, true);
  http_request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
  http_request.setRequestHeader("Content-length", params.length);
  http_request.setRequestHeader("Connection", "close");
  http_request.send(params);
  return http_request;
  
}      

function _checkMethod(methodSig) {
  if (!methodSig)
    return;
  
  /* empty is no handler */
  if (methodSig==NO_RESPONSE)
    return;
  
  var methodName=methodSig;
  var pos=methodSig.indexOf("(");
  if (pos>-1)
    methodName=methodSig.substring(0,pos);
  var isFunc=eval('typeof '+methodName+'=="function"');
  return isFunc; 
}

function _onFail(url,http_request) {
  throw 'call failed.\nurl: '+url+'\nmessage:'+http_request.responseText;
}      
function eat(url,http_request) {
}
function eatError(url,http_request) {
  throw 'ajax call failed.\nurl: '+url+'\nmessage:'+http_request.responseText;
}      
      
var retry=0;

function stateChange() {
  if (http_request.readyState >= 3) {
  if (requestDebug)
    alert('got response. status: '+http_request.status+'\nreadyState:'+http_request.readyState+'\nsuccessMethod:'+successMethod+'\nfailMethod:'+failMethod+'\n'+http_request.responseText);
    if (http_request.status == 200 && successMethod!=NO_RESPONSE) {
      var doc=http_request.responseXML;
      if (!doc)
        alert("no doc in: "+http_request.responseText);
      eval(successMethod);
    } else if (http_request.status == 500) {
      eval(failMethod);
    }
  } else if (requestDebug)
    alert('state: '+http_request.readyState);
}

function _doResponse(http_request,method) {
  if (!http_request) {
    alert('no request for url: '+url+'\n'+requests.toString());
    return false;
  }
  if (http_request.readyState >= 3) {
    if (requestDebug)
    alert('got response. status: '+http_request.status+'\nreadyState:'+http_request.readyState+'\nsuccessMethod:'+successMethod+'\nfailMethod:'+failMethod+'\n'+http_request.responseText);
    var http_response=this;
    if (http_request.status == 200 && successMethod!=NO_RESPONSE) {
      var doc=http_request.responseXML;
      if (!doc)
        alert("no doc in: "+http_request.responseText);
//    } else if (http_request.status == 500) {
//      eval(failMethod);
    }
    eval(method);
  } else if (requestDebug)
    alert('state: '+http_request.readyState);
//  else
//    setTimeout('_doResponse(http_request,\''+successMethod+'\',\''+failMethod+'\')',100);
}            

/** 
 * Makes a request to a given service, invoking a named method and calls given handler
 * 
 * @param svcclass service class to call
 * @param method method signature to invoke
 * @param method handler to invoke on return (ie 'onGet(url,http_request)') 
 */
function serviceRequest(svcclass,method,handler) {
	serviceRequest(svcclass,method,null,handler);
}

/** 
 * Makes a request to a given service, invoking a named method with given params and calls given handler
 * 
 * @param svcClass service class to call
 * @param method method signature to invoke
 * @param params parameters to use for invocation, in name1=val1|name2=val2|... format
 * @param method handler to invoke on return (ie 'onGet(url,http_request)') 
 */
function serviceRequest(svcClass,method,params,handler) {
	serviceRequest(svcClass,method,params,handler,false)
}
/**
 * Make a service request to given class method with params calling handler
 * on success and errhandler on fail.  If parseToJS is true, the 
 * XML result of the method call will be attempted to be parsed to 
 * a predefined js object.  In order for this to work, the result type 
 * must be included somewhere in the js, via a src from the service type 
 * interface (ie <script src="/svc/type/org.benow.some.result.Type.js">;</script>).
 */
function serviceRequest(svcClass,method,params,handler,parseToJS) {
	var paramStr='';
	if (params) {
		var tp=params;
		while (tp.indexOf('|')!=-1) {
			var pos=tp.indexOf('|')
			paramStr+=tp.substring(0,pos)+'&';
			tp=tp.substring(pos+1);
		}
		paramStr+=tp;
	}
	 
	var url='/svc/'+svcClass+'?exec='+method+'&xml=true';
//	alert("url: "+url);
	
  var hasMethod=_checkMethod(handler);
//  alert('errhandler: '+errHandler);

  Request_setLoading("Fetching "+loadingDesc+"...");  
  var http_request = new XMLHttpRequest();
  var http_response=http_request;
  http_request.onreadystatechange=function() {
    if (requestDebug)
    alert('state: '+http_request.readyState);

    if (this.readyState==4) {
    	if (typeof Request_clearLoading == 'function')
    		Request_clearLoading();
    	var result;
      document.getElementsByTagName("body")[0].style.cursor='default';
      if (requestDebug)
        alert('state: '+http_request.readyState+" status: "+http_request.status);     
      if (this.status == 200 && handler!=NO_RESPONSE) {
      if (requestDebug)
        alert('debug: \n'+this.responseText);
        // received, parse result object.
        if (parseToJS) {
          if (requestDebug)
          	alert("parsing: "+this.responseText);
        	// resolve result type
        	// <result type="org.some.Thing"> to Thing
        	var doc=this.responseXML;
        	if (doc) {
          	var result=doc.getElementsByTagName('result');
          	if (result) {
            	var elem=result[0];
            	var nodes=elem.childNodes;
            	var contentNode=null;
            	for (var i=0;i<nodes.length;i++) {
            		var node=nodes[i];
            		if (requestDebug)
              		alert('node['+i+"]: "+DOM_toString(node)+' node.type: '+node.nodeType);
            		if (node.nodeType==1) {
            			contentNode=node;
            			break;
            		}
            	}
            }
          }
        	/* broken, no js objects
        	if (contentNode) {
//              alert('node: '+DOM_toString(contentNode));
        		var type=contentNode.getAttribute('type');
        		if (type) {
//        			alert('type: '+type);
      		    var shouldHave='includes_'+_replaceAll(type,'.','_')
      			  if (typeof shouldHave != 'undefined') {
	              var pos=type.lastIndexOf('.');
	              var typeName=type.substring(pos+1,type.length);
	              
	              var createStmt='new '+typeName+'()';
	              result=eval(createStmt);
	              result.fromDOM(contentNode);
      			  }
//              alert('parsed '+typeName+'(): '+obj.toString()+'\nfrom:'+DOM_toString(contentNode));
        		}
        	}
        	*/
          
        } // if parseToJS

        if (hasMethod) {
        	// BUG handler is refering to the string 'onXXX(url, http_request)' and not
        	// the function.  Eval of handler throws not defined exception if it does
        	// not exist.  How to quietly find out if named method exists?  For now, a 
        	// onXXX is needed for each XXX.
          if (requestDebug)
          	alert("handler: "+handler);
        	eval(handler);
        } else
        	assertException(http_request);

      } else if (this.status == 500) {
        var doc=this.responseXML;
        var results=doc.getElementsByTagName('exception');
        if (results) {
          var type=results[0].getAttribute('type');
          if (type=='org.benow.web.RedirectException') {
            var msgE=results[0].getElementsByTagName('message');
            if (msgE) {
              var destURL=msgE[0].firstChild.nodeValue;
              if (requestDebug)
                alert('redirecting to: '+destURL);
              document.location.href=destURL;
            }
          }
        }
        var http_response=this;
      	if (requestDebug)
      	  alert("errhandler: "+handler);

        if (hasMethod) 
        	eval(handler);
        else
        	assertException(http_request);
        	
      } 
      
    }
  };
  
  var bodyElems=document.getElementsByTagName("body");
  if (bodyElems && bodyElems.length>0)
    document.getElementsByTagName("body")[0].style.cursor='wait';

  http_request.open('POST', url, true);
  http_request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
  http_request.setRequestHeader("Content-length", params.length);
  // add a header to indicate that we're coming from javascript. 
  // non-standard, but required for redirects.  FF doesn't add this,
  // perhaps Chrome, WebKit does.
  http_request.setRequestHeader('X-Requested-With','XMLHttpRequest');
  http_request.setRequestHeader("Connection", "close");
//  alert('sending request to: '+url+' params: '+params);
  http_request.send(params);
  return http_request;
}

function _replaceAll( instr, tofind, toreplace ) {
  var idx = instr.indexOf( tofind );
  while ( idx > -1 ) {
      instr = instr.replace( tofind, toreplace );
      idx = instr.indexOf( tofind );
  }
  return instr;
}

function Request_setLoading(msg) {
	if (showLoading) {
  var statusDiv=document.getElementById('loading');
  if (statusDiv) {
  	if (!msg)
  		msg='Loading...';
	  statusDiv.innerHTML=msg;
	  statusDiv.style.display='';
  }
	}
}

/* clear the loading message */
function Request_clearLoading() {
  var statusDiv=document.getElementById('loading');
  if (statusDiv)
    statusDiv.style.display='none';
}

/**
 * Get the exception message in the given request
 * @param request xmlhttprequest, as provided to the onX method for service call X
 * @return the exception message, or null if no message (ie not exception)
 */
function getExceptionMessage(request) {
  var xml=request.responseXML;
  if (xml) {
	  var msgs=xml.getElementsByTagName('message');
	  if (msgs.length>0)
	  	return msgs[0].firstChild.data;
  }
  return null;
}

/**
 * Was there an exception in the given response?  If so, the exception message
 * is returned.  Optionally, the full or partial exception class name can be given
 * which will match against the returned exception.  For example, if the following is 
 * is returned by the request:
 * <pre>
 * &lt;exception type="org.my.MyException"&gt;
 *   &lt;message&gt;Something bad happened&lt;/message&gt;
 * &lt;/exception&gt;
 * </pre>
 * <ul>
 * <li>isException(request) will be true</li>
 * <li>isException(request,'MyException') will be true</li>
 * <li>isException(request,'org.my.MyException') will be true</li>
 * <li>isException(request,'SomeOtherException') will be false</li>
 * </ul> 
 * 
 * @param request
 * @param exceptClass
 * @return exception message, if exception, null/false otherwise
 */
function isException(request,exceptClass) {
	if (!request) {
		if (typeof console != 'undefined')
			console.error('given null request to isException');
		return false;
	}
	
	var doc=request.responseXML;
	if (!doc)
		return false;
	
	var exNodes=doc.getElementsByTagName('exception');
	if (exNodes.length>0) {
		var exNode=exNodes[0];
		var msg=exNode.getElementsByTagName('message')[0].firstChild.nodeValue;
		if (exceptClass) {
			var type=exNode.getAttribute('type');
			if (type.indexOf(exceptClass)!=-1) 
				return msg;
		} else
			return msg;
	}
	return false;
}

function assertException(request) {
	var msg=isException(request);
	if (msg) {
	
    var doc=request.responseXML;
    if (!doc)
      return false;
    
    var exNodes=doc.getElementsByTagName('exception');
    if (exNodes.length>0) {
      var type=exNodes[0].getAttribute('type');
      if (type=='org.benow.web.RedirectException')
        return true;
    }
    
		alert('Exception occured during request:\n'+request.responseText);
		throw msg;
	}
	return true;
}

function appendHREF(toElem,text,href,onclick) {
	var a=toElem.ownerDocument.createElement('a');
	toElem.appendChild(a);
	if (href)
		a.setAttribute('href',href);
	if (onclick)
		a.setAttribute('onclick',onclick);
	a.appendChild(toElem.ownerDocument.createTextNode(text));
	return a;
}
