package cn.bran.japid.template;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import cn.bran.japid.util.RenderInvokerUtils;

/**
 * a helper class. for hiding the template API from user eyes. not really needed
 * since the template invocation API is simple enough.
 * 
 * @author Bing Ran<bing_ran@hotmail.com>
 * @deprecated don't use the play way of subclassing a controller. Use
 *             JapidRender directly anywhere.
 */
class JapidPlainController {
	private static final char DOT = '.';
	private static final String HTML = ".html";
	private static final String JAPIDVIEWS_ROOT = "japidviews";

	/**
	 * render an array of objects to a template defined by a Template class.
	 * 
	 * @param <T>
	 *            a sub-class type of JapidTemplateBaseWithoutPlay
	 * @param c
	 *            a sub-class of JapidTemplateBase
	 * @param args
	 *            arguments
	 */
	public static <T extends JapidTemplateBaseWithoutPlay> String renderWith(Class<T> c, Object... args) {
		checkJapidInit();

		if (JapidRenderer.isDevMode())
			return renderJapidWith(c.getName(), args);
		else
			try {
				return invokeRender(c, args);
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
	}

	/**
	 * 
	 */
	private static void checkJapidInit() {
		if (!JapidRenderer.isInited()) {
			throw new RuntimeException("The Japid is not initialized. Please use JapidRender.init(...) to set it up.");
		}
	}

	/**
	 * @param <T>
	 * @param c
	 *            the Japid renderer class, to be used with reflection.
	 * @param args
	 * @return
	 * @throws NoSuchMethodException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 */
	private static <T extends JapidTemplateBaseWithoutPlay> String invokeRender(Class<T> c, Object... args) {
		int modifiers = c.getModifiers();
		if (Modifier.isAbstract(modifiers)) {
			throw new RuntimeException("Cannot init the template class since it's an abstract class: " + c.getName());
		}
		try {
			Constructor<T> ctor = c.getConstructor(StringBuilder.class);
			StringBuilder sb = new StringBuilder(8000);
			T t = ctor.newInstance(sb);
			String rr = (String) RenderInvokerUtils.render(t, args);
			return rr;
		} catch (NoSuchMethodException e) {
			throw new RuntimeException("Could not match the arguments with the template args.");
		} catch (InstantiationException e) {
			// e.printStackTrace();
			throw new RuntimeException("Could not instantiate the template object. Abstract?");
		} catch (Exception e) {
			if (e instanceof RuntimeException)
				throw (RuntimeException) e;
			else
				throw new RuntimeException("Could not invoke the template object: " + e);
			// throw new RuntimeException(e);
		}
	}

	/**
	 * pickup the Japid renderer in the conventional location and render it.
	 * Positional match is used to assign values to parameters
	 * 
	 * @param objects
	 */
	protected static String render(Object... objects) {
		checkJapidInit();
		String action = template();
		return renderJapidWith(action, objects);
	}

	public static String renderJapidWith(String template, Object... args) {
		checkJapidInit();
		return getRenderResultWith(template, args);
	}

	protected static String template() {
		// the super.template() class uses current request object to determine
		// the caller and method to find the matching template
		// this won't work if the current method is called from another action.
		// let's fall back to use the stack trace to deduce the template.
		// String caller2 = StackTraceUtils.getCaller2();

		final StackTraceElement[] stes = new Throwable().getStackTrace();

		for (StackTraceElement st : stes) {
			String controller = st.getClassName();
			String action = st.getMethodName();
			Class<?> controllerClass;
			try {
				controllerClass = JapidPlainController.class.getClassLoader().loadClass(controller);
				if (controllerClass != null) {
					Class<?> superclass = controllerClass.getSuperclass();
					if (JapidPlainController.class.isAssignableFrom(superclass)) {
						String expr = controller + "." + action;
						return expr;
					}
				}
			} catch (ClassNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return null;
	}

	/**
	 * copies from the same method in the Java class. Removed the public
	 * requirement for easier chaining.
	 * 
	 * @param name
	 * @param clazz
	 * @return
	 */
	static Method findActionMethod(String name, Class clazz) {
		while (!clazz.getName().equals("java.lang.Object")) {
			for (Method m : clazz.getDeclaredMethods()) {
				if (m.getName().equalsIgnoreCase(name) /*
														 * &&
														 * Modifier.isPublic(m
														 * .getModifiers())
														 */) {
					return m;
				}
			}
			clazz = clazz.getSuperclass();
		}
		return null;
	}

	/**
	 * render parameters to the prescribed template and return the RenderResult
	 * 
	 * @param template
	 *            relative path from japidviews folder. if empty, use implicit
	 *            naming pattern to match the template
	 * @param args
	 */
	public static String getRenderResultWith(String template, Object... args) {
		checkJapidInit();

		if (template == null || template.length() == 0) {
			template = template();
		}

		if (template.endsWith(HTML)) {
			template = template.substring(0, template.length() - HTML.length());
		}

		String templateClassName = template.startsWith(JAPIDVIEWS_ROOT) ?
					template :
					JAPIDVIEWS_ROOT + File.separator + template;

		templateClassName = templateClassName.replace('/', DOT).replace('\\', DOT);

		Class<? extends JapidTemplateBaseWithoutPlay> tClass = null;

		if (JapidRenderer.isDevMode())
			tClass = JapidRenderer.getClass(templateClassName);
		else
			try {
				tClass = (Class<? extends JapidTemplateBaseWithoutPlay>)
						JapidPlainController.class.getClassLoader().loadClass(templateClassName);
			} catch (ClassNotFoundException e) {
				throw new RuntimeException(e);
			}

		if (tClass == null) {
			String templateFileName = templateClassName.replace(DOT, '/') + HTML;
			throw new RuntimeException("Could not find a Japid template with the name of: " + templateFileName);
		} else {
			// render(tClass, args);
			String rr = invokeRender(tClass, args);
			return rr;
		}
	}

}
