/*
 * The contents of this file are subject to the Mozilla Public License Version 1.1
 * (the "License"); you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at <http://www.mozilla.org/MPL/>.
 * 
 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
 * WARRANTY OF ANY KIND, either express or implied. See the License for the specific
 * language governing rights and limitations under the License.
 * 
 * The Original Code is the Venice Web Communities System.
 * 
 * The Initial Developer of the Original Code is Eric J. Bowersox <erbo@ricochet.com>,
 * for Silverwrist Design Studios.  Portions created by Eric J. Bowersox are
 * Copyright (C) 2004 Eric J. Bowersox/Silverwrist Design Studios.  All Rights Reserved.
 * 
 * Contributor(s): 
 */
package com.silverwrist.venice.ui.velocity;

import java.io.*;
import java.util.*;
import javax.servlet.*;
import org.apache.log4j.*;
import org.apache.velocity.*;
import org.apache.velocity.app.*;
import org.apache.velocity.context.AbstractContext;
import org.apache.velocity.context.Context;
import org.apache.velocity.exception.*;
import org.apache.velocity.runtime.RuntimeConstants;
import com.silverwrist.venice.ui.*;
import com.silverwrist.venice.ui.config.RootConfig;

public class VelocityRenderer
{
  /*--------------------------------------------------------------------------------
   * Internal "tool" context.
   *--------------------------------------------------------------------------------
   */

  private class ToolContext extends AbstractContext
  {
    /*====================================================================
     * Attributes
     *====================================================================
     */

    private StdObject m_std;

    /*====================================================================
     * Constructor
     *====================================================================
     */

    ToolContext(Context inner, RequestOutput out)
    {
      super(inner);
      m_std = new StdObject(out);

    } // end constructor

    /*====================================================================
     * Abstract implementations from class AbstractContext
     *====================================================================
     */

    public Object internalGet(String key)
    {
      if (key.equals("std"))
	return m_std;
      return null;

    } // end internalGet

    public Object internalPut(String key, Object value)
    {
      Context ctxt = getChainedContext();
      if (ctxt==null)
	return null;
      else
	return ctxt.put(key,value);

    } // end internalPut

    public boolean internalContainsKey(Object key)
    {
      String keystr = key.toString();
      if (keystr.equals("std"))
	return true;
      return false;

    } // end internalContainsKey

    public Object[] internalGetKeys()
    {
      ArrayList rc = new ArrayList();
      rc.add("std");
      return rc.toArray();

    } // end internalGetKeys

    public Object internalRemove(Object key)
    {
      Context ctxt = getChainedContext();
      if (ctxt==null)
	return null;
      else
	return ctxt.remove(key);

    } // end internalRemove

  } // end class ToolContext

  /*--------------------------------------------------------------------------------
   * Static data members
   *--------------------------------------------------------------------------------
   */

  private static final String CONTEXT_KEY = VelocityRenderer.class.getName();
  private static final String LOGGING_CATEGORY_NAME = "velocity.engine";

  private static Logger vel_logger = Logger.getLogger(LOGGING_CATEGORY_NAME);
  private static Logger logger = Logger.getLogger(VelocityRenderer.class);

  /*--------------------------------------------------------------------------------
   * Attributes
   *--------------------------------------------------------------------------------
   */

  private VelocityEngine m_ve;           // our instance of the Velocity engine

  /*--------------------------------------------------------------------------------
   * Constructor
   *--------------------------------------------------------------------------------
   */

  private VelocityRenderer(String template_dir) throws Exception
  {
    m_ve = new VelocityEngine();
    m_ve.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS,
		     "org.apache.velocity.runtime.log.SimpleLog4JLogSystem");
    m_ve.setProperty("runtime.log.logsystem.log4j.category",LOGGING_CATEGORY_NAME);
    m_ve.setProperty("resource.loader","file");
    m_ve.setProperty("file.loader.description","Velocity File Resource Loader");
    m_ve.setProperty("file.resource.loader.class","org.apache.velocity.runtime.resource.loader.FileResourceLoader");
    m_ve.setProperty("file.resource.loader.path",template_dir);
    m_ve.setProperty("velocimacro.permissions.allow.inline","true");
    m_ve.setProperty("velocimacro.permissions.allow.inline.to.replace.global","false");
    m_ve.setProperty("velocimacro.permissions.allow.inline.local.scope","true");
    m_ve.init();

  } // end constructor

  /*--------------------------------------------------------------------------------
   * External operations
   *--------------------------------------------------------------------------------
   */

  public void formatTemplate(String template_name, VelocitySupplyParams params, RequestOutput out, Writer wr)
      throws IOException, ServletException
  {
    if (wr==null)
      wr = out.getWriter();
    try
    { // Load the template.
      Template templ = m_ve.getTemplate(template_name);

      // Create a context containing the arguments.
      VelocityContext ctxt = new VelocityContext(new ContextWrapper(params));

      // Merge the template onto the output.
      templ.merge(new ToolContext(ctxt,out),wr);
      wr.flush();

    } // end try
    catch (ResourceNotFoundException e)
    { // this is not good
      throw new ServletException("unable to load Velocity resource: " + e.getMessage(),e);

    } // end catch
    catch (ParseErrorException e)
    { // parse error
      throw new ServletException("Velocity parse error: " + e.getMessage(),e);

    } // end catch
    catch (MethodInvocationException e)
    { // some method failed
      throw new ServletException("Velocity method invocation error: " + e.getMessage(),e);

    } // end catch
    catch (IOException e)
    { // pass IOExceptions straight through
      throw e;

    } // end catch
    catch (Exception e)
    { // all other garbage goes here
      throw new ServletException("Velocity rendering failure: " + e.getMessage(),e);

    } // end catch

  } // end formatTemplate

  public void render(ContentVelocity content, RequestOutput out, Writer wr) throws IOException, ServletException
  {
    formatTemplate(content.getTemplateName(),content,out,wr);

  } // end render

  public void render(ContentVelocityOwnTemplate content, RequestOutput out, Writer wr)
      throws IOException, ServletException
  {
    if (wr==null)
      wr = out.getWriter();
    try
    { // Get the identifier.
      String ident = content.getIdentifier();
      if (ident==null)
	ident = "ContentVelocityOwnTemplate";

      // Create a context containing the arguments.
      VelocityContext ctxt = new VelocityContext(new ContextWrapper(content));

      if (!(m_ve.evaluate(new ToolContext(ctxt,out),wr,ident,content.getTemplate())))
	throw new ServletException("Velocity template rendering failure");
      wr.flush();

    } // end try
    catch (ResourceNotFoundException e)
    { // this is not good
      throw new ServletException("unable to load Velocity resource: " + e.getMessage(),e);

    } // end catch
    catch (ParseErrorException e)
    { // parse error
      throw new ServletException("Velocity parse error: " + e.getMessage(),e);

    } // end catch
    catch (MethodInvocationException e)
    { // some method failed
      throw new ServletException("Velocity method invocation error: " + e.getMessage(),e);

    } // end catch
    catch (IOException e)
    { // pass IOExceptions straight through
      throw e;

    } // end catch
    catch (Exception e)
    { // all other garbage goes here
      throw new ServletException("Velocity rendering failure: " + e.getMessage(),e);

    } // end catch

  } // end render

  /*--------------------------------------------------------------------------------
   * External static operations
   *--------------------------------------------------------------------------------
   */

  public static void init(ServletContext ctxt, RootConfig config)
  {
    if (ctxt.getAttribute(CONTEXT_KEY)!=null)
      return;  // already initialized

    try
    { // initialize the renderer object
      VelocityRenderer tmp = new VelocityRenderer(config.getVelocityTemplateDirectory());
      ctxt.setAttribute(CONTEXT_KEY,tmp);

    } // end try
    catch (Exception e)
    { // the initialization failed!
      logger.fatal("VelocityRenderer initialization failed!",e);

    } // end catch

  } // end init

  public static VelocityRenderer get(ServletContext ctxt) throws ServletException
  {
    VelocityRenderer rc = (VelocityRenderer)(ctxt.getAttribute(CONTEXT_KEY));
    if (rc==null)
      throw new ServletException("VelocityRenderer not initialized");
    return rc;

  } // end get

} // end class VelocityRenderer
