package cn.bran.play;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
// available only with newer commons-lang
// import org.apache.commons.lang.text.StrSubstitutor;

import play.Play;
//import play.classloading.enhancers.LocalvariablesNamesEnhancer.LocalVariablesNamesTracer;
import play.exceptions.ActionNotFoundException;
import play.exceptions.NoRouteFoundException;
import play.exceptions.PlayException;
import play.exceptions.UnexpectedException;
import play.mvc.ActionInvoker;
import play.mvc.Router;
import play.mvc.Http.Request;
import play.mvc.Router.ActionDefinition;
import play.mvc.Router.Route;
import play.templates.GroovyTemplate;
import cn.bran.japid.util.StringUtils;
import cn.bran.japid.util.UrlMapper;

/**
 * the logic is modeled after the the Play!'s {@code Template.ActionBridge.}
 * 
 * 
 * @author bran
 * 
 */
public class RouteAdapter implements UrlMapper {
	// String controllerName;

	/**
	 * 
	 * @param controllerName
	 *            the current controller's name. If null is specified, the
	 *            Request.current().controller will be used
	 */
	public RouteAdapter() {
		super();
		// this.controllerName = controllerName;
	}

	/**
	 * Some sample form of action: CRUD.index(), blank(), attachment(_id, _name)
	 * this one has method call inside of controller param list
	 * 
	 * @{Application.show(post.previous().id)
	 * 
	 *                                        this is a super long one: *
	 * 
	 * @{list( ).remove('page').add('search', params.search).add('order',
	 *         (_caller.order == 'DESC' ? 'ASC' : 'DESC'))} in the table.html in
	 * 
	 *         the CRUD module
	 * 
	 *         this one has property access in the param list:
	 * @{show(_caller.object.id)
	 * 
	 * @param actionString
	 *            the leasing part of the whole expression, e.g.
	 *            Application.show in @{Application.show(post.previous().id)}.
	 *            it can also bear the form of a simple function call, such as:
	 *            show
	 * @param params
	 *            the runtime value of the parameters used to call the action,
	 *            e.g. 123 from evaluating "post.previous().id"
	 */
	@Override
	public String lookup(String actionString, Object[] params) {
		ActionBridge ab = new ActionBridge(false);
		ActionDefinition ad = ab.invokeMethod(actionString, params);
		return ad.toString();
	}

	@Override
	public String lookupAbs(String action, Object[] args) {
		return Request.current().getBase() + this.lookup(action, args);
	}

	public String lookupStatic(String resource, boolean isAbs) {
		return reverseStaticLookup(resource, isAbs);
		// return Router.reverseWithCheck(resource,
		// Play.getVirtualFile(resource));
	}

	@Override
	public String lookupStaticAbs(String resource) {
//		return Request.current().getBase() + this.lookupStatic(resource);
		return this.lookupStatic(resource, true);
	}

	/**
	 * @return
	 */
	private static HashMap<String, String> getActionCache() {
		HashMap<String, String> hash = (HashMap<String, String>) Request.current().args.get("actionReverseCache");
		if (hash == null) {
			hash = new HashMap<String, String>();
			Request.current().args.put("actionReverseCache", hash);
		}

		// put in the threadlocal is a problem because threads are reused in the
		// pool
		// HashMap<String, String> hash = actionReverseCache.get();
		// if (hash == null) {
		// hash = new HashMap<String, String>();
		// actionReverseCache.set(hash);
		// }
		return hash;
	}

	/**
	 * @return
	 */
	private static HashMap<String, String[]> getActionParamCache() {
		// the cache is bound to a request
		HashMap<String, String[]> hash = (HashMap<String, String[]>) Request.current().args.get("actionParamNamesCache");
		if (hash == null) {
			hash = new HashMap<String, String[]>();
			Request.current().args.put("actionParamNamesCache", hash);
		}

		// HashMap<String, String[]> hash = actionParamNamesCache.get();
		// if (hash == null) {
		// hash = new HashMap<String, String[]>();
		// actionParamNamesCache.set(hash);
		// }
		return hash;
	}

	/**
	 * reverse lookup a static resource
	 * 
	 * provide cache
	 * 
	 * @author Bing Ran<bing_ran@hotmail.com>
	 * @param resource
	 * @return
	 */
	public static String reverseStaticLookup(String resource, boolean isAbs) {
		try {
			HashMap<String, String> hash = getStaticCache();
			String url = hash.get(resource);
			if (url == null) {
				url = Router.reverseWithCheck(resource, Play.getVirtualFile(resource), isAbs);
				hash.put(resource, url);
			}
			return url;
		} catch (RuntimeException e) {
			throw new RuntimeException(e + ". No matching route in reverse lookup: " + resource);
		}
	}

	/**
	 * @return
	 */
	private static HashMap<String, String> getStaticCache() {
		HashMap<String, String> hash = staticCache.get();
		if (hash == null) {
			hash = new HashMap<String, String>();
			staticCache.set(hash);
		}
		return hash;
	}

	// cache lookups on the current thread.
	// ideally they can be done in the router for longer persistence and be
	// synched with route table reloading.

	// store quick reverse lookup
	// TODO should consider concurrent hashmap, mm but it's only thread local!
	// should this be shared among all thread(in the case concurrentThreadLocal
	// is required
	//
	static ThreadLocal<HashMap<String, String>> staticCache = new ThreadLocal<HashMap<String, String>>();
	// <action & param hash, url pattern>
	static ThreadLocal<HashMap<String, String>> actionReverseCache = new ThreadLocal<HashMap<String, String>>();
	// <action, paramNames>
	static ThreadLocal<HashMap<String, String[]>> actionParamNamesCache = new ThreadLocal<HashMap<String, String[]>>();

	@Override
	public String lookupStatic(String resource) {
		return lookupStatic(resource, false);
	}

}
