/*
 * Created on 2004-6-13
 *
 * To change the template for this generated file go to
 * Window - Preferences - Java - Code Generation - Code and Comments
 */
package workflow.script.compiler;

import java.io.File;
import java.io.FileNotFoundException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import workflow.script.Constants;
import workflow.script.Options;
import workflow.script.ScriptCompilationContext;
import workflow.script.interpretor.ScriptWrapper;
import workflow.script.core.ScriptContext;
import workflow.script.util.StringManager;
import workflow.wfmc.xpdl.WorkflowTransition;

import com.zosatapo.commons.util.log.SystemLogHandler;

/**
 * @author reic
 *
 * To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Generation - Code and Comments
 */
public class ScriptRuntimeContext implements Runnable
{
	// Logger
	private static Log log = LogFactory.getLog(ScriptRuntimeContext.class);
	
	protected static StringManager sm = StringManager.getManager(Constants.RESOURCE_BUNDLE_BASE);
	
	// ----------------------------------------------------------- Constructors

	/**
	 * Create a ScriptRuntimeContext for a workflow application servletContext.
	 *
	 * Loads in any previously generated dependencies from file.
	 *
	 * @param ScriptContext for workflow application
	 */
	public ScriptRuntimeContext(ScriptContext servletContext, Options options)
	{

		System.setErr(new SystemLogHandler(System.err));
		System.setOut(new SystemLogHandler(System.out));
		
		this.servletContext = servletContext;
		this.options = options;

		// Get the parent class loader
		parentClassLoader = (URLClassLoader) servletContext.getAttribute(Constants.SERVLET_CLASS_LOADER);
		
		if (parentClassLoader == null)
		{
			parentClassLoader = (URLClassLoader) this.getClass().getClassLoader();
		}

		if (parentClassLoader != null)
		{
			log.debug(sm.getString("script.message.parent_class_loader_is", parentClassLoader.toString()));
		}
		else
		{
			log.debug(sm.getString("script.message.parent_class_loader_is", "<none>"));
		}
		
		initClassPath();

		// If this workflow application servletContext is running from a
		// directory, start the background compilation thread
		String appBase = servletContext.getRealPath("/");
		if (appBase != null && options.getReloading())
		{
			if (appBase.endsWith(File.separator))
			{
				appBase = appBase.substring(0, appBase.length() - 1);
			}
			String directory = appBase.substring(appBase.lastIndexOf(File.separator));
			threadName = threadName + "[" + directory + "]";
			threadStart();
		}
	}

	// ----------------------------------------------------- Instance Variables

	/**
	 * This workflow applications ScriptContext
	 */
	private ScriptContext servletContext;
	private Options options;
	private URLClassLoader parentClassLoader;
	private String classpath;

	/**
	 * Maps WFS pages to their ScriptBase's
	 */
	private Map wfses = Collections.synchronizedMap(new HashMap());

	/**
		* The background thread.
		*/
	private Thread thread = null;

	/**
		* The background thread completion semaphore.
		*/
	private boolean threadDone = false;

	/**
		* Name to register for the background thread.
		*/
	private String threadName = "ScriptRuntimeContext";
	// ------------------------------------------------------ Protected Methods

	/**
	 * Add a new ScriptWrapper.
	 *
	 * @param String uri of WFS
	 * @param Script className for WFS
	 */
	public void addScriptWrapper(
		WorkflowTransition transition,
		ScriptWrapper scriptWrapper)
	{
		wfses.remove(genScriptUUID(transition));
		wfses.put(genScriptUUID(transition), scriptWrapper);
	}

	/**
	 * Get an already existing ScriptWrapper.
	 *
	 */
	public ScriptWrapper getScriptWrapper(WorkflowTransition transition)
	{
		return (ScriptWrapper) wfses.get(genScriptUUID(transition));
	}

	/**
	 * Remove a  ScriptWrapper.
	 *
	 */
	public void removeScriptWrapper(WorkflowTransition transition)
	{
		wfses.remove(genScriptUUID(transition));
	}
	/**
	 * Get the parent URLClassLoader.
	 *
	 * @return URLClassLoader parent
	 */
	public URLClassLoader getParentClassLoader()
	{
		return parentClassLoader;
	}

	/**
	 * Process a "destory" event for this workflow application servletContext.
	 */
	public void destroy()
	{
    threadStop();
    
		if (System.err instanceof SystemLogHandler)
		{
			System.setErr(((SystemLogHandler) System.err).getWrapped());
		}
		
		Iterator servlets = wfses.values().iterator();
		while (servlets.hasNext())
		{
			((ScriptWrapper) servlets.next()).destroy();
		}
		
	}

	/**
	 * The classpath that is passed off to the Java compiler.
	 */
	public String getClassPath()
	{
		return classpath;
	}

	/**
	 * Method used to initialize classpath for compiles.
	 */
	private void initClassPath()
	{

		URL[] urls = parentClassLoader.getURLs();
		StringBuffer cpath = new StringBuffer();
		String sep = System.getProperty("path.separator");

		for (int i = 0; i < urls.length; i++)
		{
			// Willow can use URL's other than file URL's,
			// a protocol other than file: will generate a
			// bad file system path, so only add file:
			// protocol URL's to the classpath.

			if (urls[i].getProtocol().equals("file"))
			{
				cpath.append((String) urls[i].getFile() + sep);
			}
		}

		cpath.append(options.getScratchDir() + sep);
		classpath = cpath.toString();
		
		String cp = (String) servletContext.getAttribute(Constants.SERVLET_CLASS_PATH);
		
		if (cp == null || cp.equals(""))
		{
			cp = options.getClassPath();
		}
		
		if(cp!=null)
		{
			classpath +=cp;
		}
	}

	private String genScriptUUID(WorkflowTransition workflowTransition)
	{
		return workflowTransition.getUUID()+"."+workflowTransition.getScriptFileName();
	}
	
	// -------------------------------------------------------- Thread Support

	/**
	 * Start the background thread that will periodically check for
	 * changes to compile time script files in a WFS.
	 *
	 * @exception IllegalStateException if we should not be starting
	 *  a background thread now
	 */
	protected void threadStart()
	{

		// Has the background thread already been started?
		if (thread != null)
		{
			return;
		}

		// Start the background thread
		threadDone = false;
		thread = new Thread(this, threadName);
		thread.setDaemon(true);
		thread.start();

	}

	/**
	 * Stop the background thread that is periodically checking for
	 * changes to compile time included files in a JSP.
	 */
	protected void threadStop()
	{

		if (thread == null)
		{
			return;
		}

		threadDone = true;
		thread.interrupt();
		thread = null;

	}

	/**
	 * Sleep for the duration specified by the <code>checkInterval</code>
	 * property.
	 */
	protected void threadSleep()
	{

		try
		{
			Thread.sleep(options.getCheckInterval() * 1000L);
		}
		catch (InterruptedException e)
		{
			;
		}

	}

	// ------------------------------------------------------ Background Thread

	/**
	 * Method used by background thread to check the JSP dependencies
	 * registered with this class for JSP's.
	 */
	private void checkCompile()
	{
		Object[] scriptWrappers = wfses.values().toArray();
		for (int i = 0; i < scriptWrappers.length; i++)
		{
			ScriptWrapper scriptWrapper = (ScriptWrapper) scriptWrappers[i];
			ScriptCompilationContext ctxt = scriptWrapper.getServletEngineContext();
			
			// scriptWrapper also synchronizes on this when
			// it detects it has to do a reload
			synchronized (scriptWrapper)
			{
				try
				{
					ctxt.compile();
				}
				catch (FileNotFoundException ex)
				{
					ctxt.incrementRemoved();
				}
				catch (Throwable t)
				{
					scriptWrapper.getServletContext().log("Background compile failed", t);
				}
			}
		}
	}

	/**
	 * The background thread that checks for changes to files
	 * included by a JSP and flags that a recompile is required.
	 */
	public void run()
	{

		// Loop until the termination semaphore is set
		while (!threadDone)
		{

			// Wait for our check interval
			threadSleep();

			// Check for script files which are newer than the
			// WFS which uses them.
			try
			{
				checkCompile();
			}
			catch (Throwable t)
			{
				t.printStackTrace();
			}
		}
	}
}
