package com.udapsoft.waf.system.controller;

import java.io.IOException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.transaction.UserTransaction;

import kr.co.hsnc.common.base.WAFFactory;
import kr.co.hsnc.common.base.WAFLogger;
import kr.co.hsnc.common.loader.FileChangeListener;
import kr.co.hsnc.common.loader.FileMonitor;
import kr.co.hsnc.j2ee.waf.controller.web.action.ActionHandler;
import kr.co.hsnc.j2ee.waf.controller.web.util.WebKeys;
import kr.co.hsnc.j2ee.waf.view.template.Screen;
import kr.co.hsnc.j2ee.waf.view.template.ScreenDefinitionDAO;
import kr.co.hsnc.j2ee.waf.view.template.Screens;

import com.udapsoft.waf.system.HandlerStorage;

public class FrontController extends HttpServlet implements FileChangeListener {

	private static final long serialVersionUID = 4556105374329449270L;
	private HashMap<String, Screens> allScreens;
	private ServletConfig config;
	private ServletContext context;
	private String defaultLocale;
	private boolean cachePreviousScreenAttributes = false;
	private boolean cachePreviousScreenParameters = false;
	private static final String PREVIOUS_SCREEN = "PREVIOUS";

	private boolean isDebug = false;

	// FileChangeListener Variables
	private long interval = 2000L;
	private FileMonitor monitor;
	private SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");

	/**
	 * Called by the servlet container to indicate to a servlet that the servlet
	 * is being placed into service.
	 * 
	 * @param config
	 *            ServletConfig
	 * @exception ServletException
	 *                - if an exception occurs that interrupts the servlet's
	 *                normal operation.
	 */
	public void init(ServletConfig config) throws ServletException {
		super.init(config);
		this.config = config;
		this.context = config.getServletContext();
		this.monitor = FileMonitor.getInstance();
		
		// enable the caching of previous screen attributes
		String cachePreviousScreenAttributesString = config.getInitParameter("cache_previous_screen_attributes");
		if (cachePreviousScreenAttributesString != null) {
			if (cachePreviousScreenAttributesString.toLowerCase().equals("true")) {
				WAFLogger.sys("FrontController: Enabled caching of previous screen attributes.");
				cachePreviousScreenAttributes = true;
			}
		}
		
		// enable the caching of previous screen parameters
		String cachePreviousScreenParametersString = config.getInitParameter("cache_previous_screen_parameters");
		if (cachePreviousScreenParametersString != null) {
			if (cachePreviousScreenParametersString.toLowerCase().equals("true")) {
				WAFLogger.sys("FrontController: Enabled caching of previous screen parameters.");
				cachePreviousScreenParameters = true;
			}
		}

		allScreens = new HashMap<String, Screens>();
		defaultLocale = config.getInitParameter("default_locale");
		if (defaultLocale == null) {
			defaultLocale = (Locale.getDefault()).toString();
		}
		String locales = config.getInitParameter("locales");
		if (locales != null) {
			StringTokenizer strTok = new StringTokenizer(locales, ",");
			while (strTok.hasMoreTokens()) {
				initScreens(config.getServletContext(), strTok.nextToken());
			}
		}
	}

	/**
	 * FileChangeListener.fileChanged() Implementation....
	 * 
	 * @param fileName
	 *            screendefinitions file name.
	 */
	public void fileChanged(String fileName) {
		WAFLogger.sys("["
				+ this.formatter.format(Calendar.getInstance().getTime())
				+ "] FrontController.fileChanged()... -> " + fileName);
		initScreens(context, defaultLocale);
	}

	/**
	 * initialize screens.
	 * 
	 * @param context
	 *            ServletContext.
	 * @param language
	 *            locale language.
	 */
	protected synchronized void initScreens(ServletContext context,
			String language) {
		URL screenDefinitionURL = null;
		String screenDefinitionRealPath = null;
		try {
			screenDefinitionURL = context.getResource("/WEB-INF/screendefinitions_" + language + ".xml");
			screenDefinitionRealPath = context.getRealPath("/WEB-INF/screendefinitions_" + language + ".xml");

			this.monitor.addFileChangeListener(this, screenDefinitionRealPath, this.interval);
		} catch (java.net.MalformedURLException ex) {
			WAFLogger.sys("FrontController.initScreens(): malformed URL exception: " + ex);
		} catch (java.io.IOException ex) {
			WAFLogger.sys("FrontController.initScreens(): IOException: " + ex);
		}
		
		if (screenDefinitionURL != null) {
			Screens screenDefinitions = ScreenDefinitionDAO.loadScreenDefinitions(screenDefinitionURL);
			if (screenDefinitions != null) {
				allScreens.put(language, screenDefinitions);
			} else {
				WAFLogger.sys("Template Servlet Error Loading Screen Definitions: Confirm that file at URL /WEB-INF/screendefinitions_"
								+ language
								+ ".xml contains the screen definitions");
			}
		} else {
			WAFLogger.sys("Template Servlet Error Loading Screen Definitions: URL /WEB-INF/screendefinitions_" + language + ".xml not found");
		}
	}

	/**
	 * Called by the servlet container to indicate to a servlet that the servlet
	 * is being placed into service.
	 * 
	 * @param request
	 *            HttpServletRequest
	 * @param response
	 *            HttpServletResponse
	 * @exception ServletException
	 *                if an exception occurs that interferes with the servlet's
	 *                normal operation occurred
	 * @exception IOException
	 *                if an input or output exception occurs
	 */
	public void service(HttpServletRequest request, HttpServletResponse response)
			throws IOException, ServletException {

		request.setAttribute("WAF.TIMESTAMP", new Long(System.currentTimeMillis()));
		/**
		 * 브라우저 Cache Expire 시키기
		 */
		if (request.getProtocol().compareTo("HTTP/1.0") == 0) {
			response.setHeader("Pragma", "no-cache");
		} else if (request.getProtocol().compareTo("HTTP/1.1") == 0) {
			response.setHeader("Cache-Control", "no-cache");
		}
		response.setDateHeader("Expires", 0);

		String screenName = null;
		String localeString = null;
		String currentURI = request.getRequestURI();
		if (request.getSession().getAttribute(WebKeys.CURRENT_URI) != null) {
			request.getSession().removeAttribute(WebKeys.CURRENT_URI);
		}
		request.getSession().setAttribute(WebKeys.CURRENT_URI, currentURI);

		debug(" currentURI=" + currentURI);

		String languageParam = request.getParameter("locale");
		// The language when specified as a parameter overrides the language set
		// in the session

		if (request.getSession().getAttribute(WebKeys.LOCALE) == null) {
			Locale locale = new Locale(defaultLocale.substring(0, 2), defaultLocale.substring(3, 5));
			request.getSession().setAttribute(WebKeys.LOCALE, locale);
		}

		if (languageParam != null) {
			localeString = languageParam;
		} else if (request.getSession().getAttribute(WebKeys.LOCALE) != null) {
			localeString = ((Locale) request.getSession().getAttribute(
					WebKeys.LOCALE)).toString();
		}

		if (allScreens.get(localeString) == null) {
			localeString = defaultLocale;
		}

		// get the screen name
		// int lastPathSeparator = currentURI.lastIndexOf("/") + 1;
		// int lastDot = currentURI.lastIndexOf(".");
		String currentScreen = currentURI.substring(request.getContextPath().length());
		int lastDot = currentScreen.lastIndexOf(".");
		// if (lastPathSeparator != -1 && lastDot != -1 && lastDot >
		// lastPathSeparator) {
		if (lastDot != -1) {
			// screenName = currentURI.substring(lastPathSeparator, lastDot);
			screenName = currentScreen.substring(0, lastDot);
		}
		debug(" screenName=" + screenName);
		// check if request is for the previous screen
		if (screenName.equals(PREVIOUS_SCREEN)) {
			String longScreenName = (String) request.getSession().getAttribute(
					WebKeys.PREVIOUS_SCREEN);
			int lastDot2 = longScreenName.lastIndexOf(".");
			if (lastDot2 != -1 && lastDot2 > 0) {
				screenName = longScreenName.substring(0, lastDot2);
			}
			// put the request attributes stored in the session in the request
			if (cachePreviousScreenParameters) {
				Map<?, ?> previousParams = (Map<?, ?>) request.getSession().getAttribute(WebKeys.PREVIOUS_REQUEST_PARAMETERS);
				Map<String, String[]> params = (Map<String, String[]>) request.getParameterMap();
				Iterator<?> it = previousParams.keySet().iterator();
				while (it.hasNext()) {
					String key = (String) it.next();
					Object value = previousParams.get(key);
					params.put(key, (String[]) value);
				}
			}
			// put in the previous request attributes
			if (cachePreviousScreenAttributes) {
				Map<?, ?> previousAttributes = (Map<?, ?>) request.getSession().getAttribute(WebKeys.PREVIOUS_REQUEST_ATTRIBUTES);
				Iterator<?> it2 = previousAttributes.keySet().iterator();
				// put the request attributes stored in the session in the
				// request
				while (it2.hasNext()) {
					String key = (String) it2.next();
					Object value = previousAttributes.get(key);
					request.setAttribute(key, value);
				}
			}
		} else {
			String extension = currentURI.substring(lastDot, currentURI.length());
			request.getSession().setAttribute(WebKeys.PREVIOUS_SCREEN, screenName + extension);
			if (cachePreviousScreenParameters) {
				// copy all the parameters into a new map
				HashMap<String, Object> newParams = new HashMap<String, Object>();
				Map<?, ?> params = (Map<?, ?>) request.getParameterMap();
				Iterator<?> it = params.keySet().iterator();
				// put the request attributes stored in the session in the
				// request
				while (it.hasNext()) {
					String key = (String) it.next();
					Object value = params.get(key);
					newParams.put(key, value);
				}
				request.getSession().setAttribute(WebKeys.PREVIOUS_REQUEST_PARAMETERS, newParams);
			}
			if (cachePreviousScreenAttributes) {
				// put the request attributes into a map
				HashMap<String, Object> attributes = new HashMap<String, Object>();
				Enumeration<?> enums = request.getAttributeNames();
				while (enums.hasMoreElements()) {
					String key = (String) enums.nextElement();
					Object value = request.getAttribute(key);
					attributes.put(key, value);
				}
				request.getSession().setAttribute(WebKeys.PREVIOUS_REQUEST_ATTRIBUTES, attributes);
			}
		}
		// get the screen information for the coresponding language
		Screen screen = null;
		if (screenName != null) {
			Screens localeScreens = (Screens) allScreens.get(localeString);
			if (localeScreens != null) {
				// **********************************************************************
				if (request.getSession().getAttribute(WebKeys.SCREENS) == null) {
					request.getSession().setAttribute(WebKeys.SCREENS, localeScreens);
				}
				// **********************************************************************
				screen = (Screen) localeScreens.getScreen(screenName);
			}
			// default to the default locale screen if it was not defined in the
			// locale specific definitions
			if (screen == null) {
				WAFLogger.debug("Screen not Found loading default from " + defaultLocale);
				localeScreens = (Screens) allScreens.get(defaultLocale);
				screen = (Screen) localeScreens.getScreen(screenName);
			}
			
			if (screen != null) {
				String handler = screen.getHandler();
				debug("##### -> handler = " + (handler == "" ? "NULL" : handler));
				if (handler != "") {

					// **********************************************************************
					request.setAttribute(HandlerStorage.KEY, currentURI + "_" + request.getParameter("event"));
					String screenname = processRequest(request, response, handler);
					if (screenname != null) {
						screenName = screenname;
						screen = (Screen) localeScreens.getScreen(screenName);
						debug(" 1 screenName = " + screenName);
					}
				}
				debug(" 2 screenName = " + screenName);
				request.setAttribute(WebKeys.CURRENT_SCREEN, screen);
				String templateName = localeScreens.getTemplate(screenName);
				debug(" 3 templateName = " + templateName);
				if (templateName != null) {
					insertTemplate(request, response, templateName);
				}
				
			} else {
				WAFLogger.debug("Error: Definition for screen " + screenName + " not found");

				response.setStatus(javax.servlet.http.HttpServletResponse.SC_MOVED_TEMPORARILY);
				response.setHeader("Location", request.getContextPath() + "/error.screen?code=WAF-400&param1=" + screenName);
			}
		}
	}

	/**
	 * process request.
	 * 
	 * @param request
	 *            HttpServletRequest
	 * @param response
	 *            HttpServletResponse
	 * @param handlerName
	 *            handler name
	 */
	protected String processRequest(HttpServletRequest request, HttpServletResponse response, String handlerName) {
		String screen = null;
		// FlowHandler handler = null;
		ActionHandler handler = null;
		try {
			debug("processRequest()...1 " + handlerName);

			// handler =
			// (FlowHandler)getClass().getClassLoader().loadClass(handlerName).newInstance();
			/*
			 * if ( request.getSession().getAttribute(handlerName) == null) {
			 * debug("processRequest()...1-1 ");
			 * //request.getSession().setAttribute(handlerName,
			 * getClass().getClassLoader
			 * ().loadClass(handlerName).newInstance());
			 * request.getSession().setAttribute(handlerName,
			 * WAFFactory.getInstance(handlerName)); } handler =
			 * (ActionHandler)request.getSession().getAttribute(handlerName);
			 */
			handler = (ActionHandler) WAFFactory.getInstance(handlerName);

			// invoke the processFlow(HttpServletRequest)
			handler.doStart(request);
			debug("processRequest()...2 " + handler);
			// flowResult = handler.processFlow(request);
			screen = handler.doHandle(request);
			debug("processRequest()...3 screen=" + screen);
			handler.doEnd(request);
			
		} catch (Exception e) {
			WAFLogger.debug("[Exception]FrontController.processRequest()... "
					+ e);
			WAFLogger.errorStackTraceeElement(e);
			
		} finally {
			handler = null;
		}
		
		return screen;
	}

	/**
	 * insert template.
	 * 
	 * @param request
	 *            HttpServletRequest
	 * @param response
	 *            HttpServletResponse
	 * @param templateName
	 *            template name
	 * @exception ServletException
	 *                if an exception occurs that interferes with the servlet's
	 *                normal operation occurred
	 * @exception IOException
	 *                if an input or output exception occurs
	 */
	protected void insertTemplate(HttpServletRequest request,
			HttpServletResponse response, String templateName)
			throws IOException, ServletException {

		// This method tries to wrap the request dispatcher call with-in
		// a transaction for making the local EJB access efficient. However,
		// it is not a fatal error, if such a transaction can not be started
		// for some reason, so it handles them gracefully.
		boolean tx_started = false;
		UserTransaction ut = null;

		try {
			// Lookup the UserTransaction object
			InitialContext ic = new InitialContext();
			ut = (UserTransaction) ic.lookup("java:comp/UserTransaction");
			ut.begin(); // start the transaction.
			tx_started = true;
			/*************************************************************/
			// RollbackException
			ut.setTransactionTimeout(300); // 5 minutes
			/*************************************************************/
		} catch (NamingException ne) {
			// it should not have happened, but it is a recoverable error.
			// Just dont start the transaction.
			// ne.printStackTrace();
		} catch (NotSupportedException nse) {
			// Again this is a recoverable error.
			nse.printStackTrace();
		} catch (SystemException se) {
			// Again this is a recoverable error.
			se.printStackTrace();
		}

		try {
			context.getRequestDispatcher(templateName).forward(request, response);
		} finally {
			// commit the transaction if it was started earlier successfully.
			try {
				if (tx_started && ut != null) {
					ut.commit();
				}
			} catch (IllegalStateException re) {
				re.printStackTrace();
			} catch (RollbackException re) {
				re.printStackTrace();
			} catch (HeuristicMixedException hme) {
				hme.printStackTrace();
			} catch (HeuristicRollbackException hre) {
				hre.printStackTrace();
			} catch (SystemException se) {
				se.printStackTrace();
			}
		}
	}

	private void debug(Object obj) {
		if (isDebug)
			WAFLogger.debug("[FrontController]" + obj);
	}
}