/*******************************************************************************
 * Copyright (c) 2007 java2script.org and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Zhou Renjian - initial API and implementation
 *******************************************************************************/

package net.sf.j2s.ajax;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

/**
 * @author zhou renjian
 *
 * 2006-10-10
 */
public class SimpleRPCRequest {
	
	public static final int MODE_AJAX = 1;
	public static final int MODE_LOCAL_JAVA_THREAD = 2;
	
	private static int runningMode = MODE_LOCAL_JAVA_THREAD;
	
	public static int getRequstMode() {
		return runningMode;
	}
	
	public static void switchToAJAXMode() {
		runningMode = MODE_AJAX;
	}
	
	/**
	 * This method only makes sense for Java client not for
	 * Java2Script client!
	 */
	public static void switchToLocalJavaThreadMode() {
		runningMode = MODE_LOCAL_JAVA_THREAD;
	}
	
	/**
	 * Java2Script client will always requests in AJAX mode. 
	 * @param runnable
	 * @j2sNative
	 * runnable.ajaxIn ();
	 * net.sf.j2s.ajax.SimpleRPCRequest.ajaxRequest (runnable);
	 */
	public static void request(final SimpleRPCRunnable runnable) {
		runnable.ajaxIn();
		if (runningMode == MODE_LOCAL_JAVA_THREAD) {
			new Thread(new Runnable() {
				public void run() {
					try {
						runnable.ajaxRun();
					} catch (RuntimeException e) {
						e.printStackTrace(); // should never fail in Java thread mode!
						runnable.ajaxFail();
						return;
					}
					runnable.ajaxOut();
				}
			}).start();
		} else {
			ajaxRequest(runnable);
		}
	}
	
	static String getClassNameURL(SimpleRPCRunnable runnable) {
		Class oClass = runnable.getClass();
		String name = oClass.getName();
		while (name.indexOf('$') != -1) {
			oClass = oClass.getSuperclass();
			if (oClass == null) {
				return null; // should never happen!
			}
			name = oClass.getName();
		}
		return name;
	}

	private static void ajaxRequest(final SimpleRPCRunnable runnable) {
		String url = runnable.getHttpURL();
		String method = runnable.getHttpMethod();
		String serialize = runnable.serialize();
		if (method == null) {
			method = "POST";
		}
		if (checkXSS(url, serialize, runnable)) {
			return;
		}
		String url2 = adjustRequestURL(method, url, serialize);
		if (url2 != url) {
			serialize = null;
		}
		
		final HttpRequest request = new HttpRequest();
		request.open(method, url, true);
		request.registerOnReadyStateChange(new XHRCallbackAdapter() {
			public void onLoaded() {
				String responseText = request.getResponseText();
				if (responseText == null || responseText.length() == 0) {
					runnable.ajaxFail(); // should seldom fail!
					return;
				}
				runnable.deserialize(responseText);
				runnable.ajaxOut();
			}
		});
		request.send(serialize);
	}

	protected static String adjustRequestURL(String method, String url, String serialize) {
		if ("GET".equals(method.toUpperCase())) {
			try {
				String query = URLEncoder.encode(serialize, "UTF-8");
				if (url.indexOf('?') != -1) {
					/* should not come to this branch! */
					url += "&jzz=" + query;
				} else {
					url += "?" + query;
				}
			} catch (UnsupportedEncodingException e) {
				// should never throws such exception!
				//e.printStackTrace();
			}
		}
		return url;
	}
	
	/**
	 * Check that whether it is in cross site script mode or not.
	 * @param url
	 * @return
	 * @j2sNative
if (url != null && (url.indexOf ("http://") == 0
		|| url.indexOf ("https://") == 0)) {
	var host = null;
	var idx = url.indexOf ('/', 9);
	if (idx != -1) {
		host = url.substring (url.indexOf ("//") + 2, idx); 
	} else {
		host = url.substring (url.indexOf ("//") + 2); 
	}
	return (window.location.host != host || window.location.protocol == "file:");
}
return false; // ftp ...
	 */
	protected static boolean isXSSMode(String url) {
		return false;
	}
	
	/**
	 * Check cross site script. Only make senses for JavaScript.
	 * 
	 * @param url
	 * @param serialize
	 * @param runnable
	 * @return
	 */
	protected static boolean checkXSS(String url, String serialize, SimpleRPCRunnable runnable) {
		/**
		 * @j2sNative
if (net.sf.j2s.ajax.SimpleRPCRequest.isXSSMode (url)) {
	var g = net.sf.j2s.ajax.SimpleRPCRequest;
	if (g.idSet == null) {
		g.idSet = new Object ();
	}
	var rnd = null;
	while (true) {
		var rnd = Math.random () + "0000000.*";
		rnd = rnd.substring (2, 8);
		if (g.idSet["o" + rnd] == null) {
			g.idSet["o" + rnd] = runnable;
			break;
		}
	}
	var limit = 7168; //8192;
	if (window["script.get.url.limit"] != null) {
		limit = window["script.get.url.limit"]; 
	}
	var ua = navigator.userAgent.toLowerCase ();
	if (ua.indexOf ("msie")!=-1 && ua.indexOf ("opera") == -1){
		limit = 2048;
		limit = 2048 - 44; // ;jsessionid=
	}
	limit -= url.length + 36; // 5 + 6 + 5 + 2 + 5 + 2 + 5;
	var contents = [];
	var content = encodeURIComponent(serialize);
	if (content.length > limit) {
		parts = Math.ceil (content.length / limit);
		var lastEnd = 0;
		for (var i = 0; i < parts; i++) {
			var end = (i + 1) * limit;
			if (end > content.length) {
				end = content.length;
			} else {
				for (var j = 0; j < 3; j++) {
					var ch = content.charAt (end - j);
					if (ch == '%') {
						end -= j;
						break;
					}
				}
			}
			contents[i] = content.substring (lastEnd, end);
			lastEnd = end; 
		}
	} else {
		contents[0] = content;
	}
	g.idSet["x" + rnd] = contents;
	// Only send the first request, later server return "continue", and client will get
	// the session id and continue later requests.
	net.sf.j2s.ajax.SimpleRPCRequest.callByScript(rnd, contents.length, 0, contents[0]);
	contents[0] = null;
	return true; // cross site script!
}
		 */ { }
		 return false;
	}
	
	static void callByScript(String rnd, String length, String i, String content) {
		/**
		 * @j2sNative
var g = net.sf.j2s.ajax.SimpleRPCRequest;
var runnable = g.idSet["o" + rnd];
if (runnable == null) return;
var url = runnable.getHttpURL();
var session = g.idSet["s" + rnd];
if (session != null && window["script.get.session.url"] != false) {
	url += ";jsessionid=" + session;
}
var script = document.createElement ("SCRIPT");
script.type = "text/javascript";
script.src = url + "?jzn=" + rnd + "&jzp=" + length 
		+ "&jzc=" + (i + 1) + "&jzz=" + content;
if (typeof (script.onreadystatechange) == "undefined") { // W3C
	script.onerror = function () {
		this.onerror = null;
		var idx = this.src.indexOf ("jzn=");
		var rid = this.src.substring (idx + 4, this.src.indexOf ("&", idx));
		net.sf.j2s.ajax.SimpleRPCRequest.xssNotify (rid, null);
		document.getElementsByTagName ("HEAD")[0].removeChild (this);
	};
	script.onload = function () {
		this.onload = null;
		if (navigator.userAgent.indexOf ("Opera") >= 0) {
			var idx = this.src.indexOf ("jzn=");
			var rid = this.src.substring (idx + 4, this.src.indexOf ("&", idx));
			net.sf.j2s.ajax.SimpleRPCRequest.xssNotify (rid, null);
		}
		document.getElementsByTagName ("HEAD")[0].removeChild (this);
	};
} else { // IE
	script.defer = true;
	script.onreadystatechange = function () {
		var state = "" + this.readyState;
		if (state == "loaded" || state == "complete") {
			this.onreadystatechange = null; 
			var idx = this.src.indexOf ("jzn=");
			var rid = this.src.substring (idx + 4, this.src.indexOf ("&", idx));
			net.sf.j2s.ajax.SimpleRPCRequest.xssNotify (rid, null);
			document.getElementsByTagName ("HEAD")[0].removeChild (this);
		}
	};
}
var head = document.getElementsByTagName ("HEAD")[0];
head.appendChild (script);
		 */ {}
	}
	
	/**
	 * Cross site script notify. Only make senses for JavaScript.
	 * 
	 * @param nameID
	 * @param response
	 * @param session
	 */
	static void xssNotify(String nameID, String response, String session) {
		/**
		 * @j2sNative
var ua = navigator.userAgent.toLowerCase ();
if (response != null && ua.indexOf ("msie") != -1 && ua.indexOf ("opera") == -1) {
	var ss = document.getElementsByTagName ("SCRIPT");
	for (var i = 0; i < ss.length; i++) {
		var s = ss[i];
		if (s.src != null && s.src.indexOf ("jzn=" + nameID) != -1
				&& s.readyState == "interactive") {
 			s.onreadystatechange =  function () {
				var state = "" + this.readyState;
				if (state == "loaded" || state == "complete") {
					this.onreadystatechange = null; 
 					document.getElementsByTagName ("HEAD")[0].removeChild (this);
				}
			};
	 	}
	}
}
		 */ { }
		if (response == "continue") {
			/**
			 * @j2sNative
var g = net.sf.j2s.ajax.SimpleRPCRequest;
if (session != null){
	g.idSet["s" + nameID] = session;
}
var xcontent = g.idSet["x" + nameID]; 
if (xcontent != null) {
	//The following codes may be modified to send out requests one by one.  
	if (xcontent != null) {
		for (var i = 0; i < xcontent.length; i++) {
			if (xcontent[i] != null) {
				g.callByScript(nameID, xcontent.length, i, xcontent[i]);
				xcontent[i] = null;
			}
		}  
		g.idSet["x" + nameID] = null;
	}
}
			 */ {}
			return;
		}
		SimpleRPCRunnable runnable = null;
		/**
		 * @j2sNative
var g = net.sf.j2s.ajax.SimpleRPCRequest;
runnable = g.idSet["o" + nameID];
g.idSet["o" + nameID] = null;
if (g.idSet["s" + nameID] != null) {
	g.idSet["s" + nameID] = null;
}
if (response == null && runnable != null) { // error!
	runnable.ajaxFail();
	return;
}
		 */ {}
		if (response == "unsupported" || response == "exceedrequestlimit") {
			/**
			 * @j2sNative
var existed = false;
var ss = document.getElementsByTagName ("SCRIPT");
for (var i = 0; i < ss.length; i++) {
	var s = ss[i];
	if (s.src != null && s.src.indexOf ("jzn=" + nameID) != -1) {
		existed = true;
		s.onreadystatechange = null;
		s.onerror = null;
		s.onload = null;
		document.getElementsByTagName ("HEAD")[0].removeChild (s);
 	}
}
if (!existed && runnable == null) {
	return; // already print out error message!
}
			 */ {}
			if (runnable != null) {
				runnable.ajaxFail();
			} else {
				if (response == "unsupported") {
					System.err.println("[Java2Script] Sever error: Cross site script is not supported!");
				} else {
					System.err.println("[Java2Script] Sever error: Exceed cross site script request limit!");
				}
			}
			return;
		}
		if (runnable != null) {
			runnable.deserialize(response);
			runnable.ajaxOut();
		}
	}
}
