/*
 * 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@silcom.com>,
 * for Silverwrist Design Studios.  Portions created by Eric J. Bowersox are
 * Copyright (C) 2002 Eric J. Bowersox/Silverwrist Design Studios.  All Rights Reserved.
 * 
 * Contributor(s): 
 */
package com.silverwrist.venice.frame;

import java.util.*;
import java.util.regex.*;
import org.apache.log4j.Logger;
import org.w3c.dom.*;
import com.silverwrist.util.*;
import com.silverwrist.util.xml.*;
import com.silverwrist.dynamo.RequestType;
import com.silverwrist.dynamo.event.*;
import com.silverwrist.dynamo.except.*;
import com.silverwrist.dynamo.iface.*;
import com.silverwrist.dynamo.util.*;
import com.silverwrist.dynamo.velocity.VelocityParamSupplier;
import com.silverwrist.dynamo.velocity.VelocityRendererConfig;
import com.silverwrist.venice.VeniceNamespaces;
import com.silverwrist.venice.iface.*;
import com.silverwrist.venice.session.SessionInfoParams;

public class FrameAssembler
    implements NamedObject, ComponentInitialize, ComponentShutdown, OutputObjectFilter, DynamicUpdateListener,
               FrameParameters
{
  /*--------------------------------------------------------------------------------
   * Internal class that supplies rendering parameters for the frame
   *--------------------------------------------------------------------------------
   */

  private class BaseParams implements VelocityParamSupplier
  {
    /*====================================================================
     * Constructor
     *====================================================================
     */

    BaseParams()
    { // do nothing
    } // end constructor

    /*====================================================================
     * Implementations from interface VelocityParamSupplier
     *====================================================================
     */

    public Object getParameter(String key)
    {
      return m_globals.get(key);

    } // end getParameter

    public Collection getParameterNames()
    {
      return Collections.unmodifiableSet(m_globals.keySet());

    } // end getParameterNames

  } // end class BaseParams

  /*--------------------------------------------------------------------------------
   * Internal class that supplies additional operations for the Velocity engine
   *--------------------------------------------------------------------------------
   */

  public class VelocityOps
  {
    /*====================================================================
     * Constructor
     *====================================================================
     */

    VelocityOps()
    { // do nothing
    } // end constructor

    /*====================================================================
     * External operations
     *====================================================================
     */

    public boolean getShowComments()
    {
      return m_show_comments;

    } // end showComments

  } // end class VelocityOps

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

  private static Logger logger = Logger.getLogger(FrameAssembler.class);

  private static final Map PROP_TO_VELOCITY;
  private static final Map PROP_TO_TYPE;

  private static final Pattern PAT_LINEBREAK = Pattern.compile("\\r*\\n");

  private static final int[] NO_ACLS = new int[0];

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

  private String m_name;                       // this object's name
  private ObjectProvider m_props;              // global properties
  private ObjectProvider m_blocks;             // global blocks
  private MenuProvider m_menu_prov;            // menu provider
  private String m_frame_template;             // frame template parameter
  private HashMap m_globals = new HashMap();   // globals fed to Velocity
  private BaseParams m_base_params;            // base parameters object
  private boolean m_footer_breaks;             // add line breaks to footer?
  private boolean m_show_comments;             // show HTML comments?
  private ComponentShutdown m_shut_event;      // shutdown event handler
  private ComponentShutdown m_shut_vr;         // shutdown Velocity rendering component
  private ComponentShutdown m_shut_fasm;       // shutdown FrameAssembler

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

  public FrameAssembler()
  { // do nothing
  } // end constructor

  /*--------------------------------------------------------------------------------
   * Internal operations
   *--------------------------------------------------------------------------------
   */

  private final Object initLoadProperty(String name, Class expected_class) throws ConfigException
  {
    try
    { // call through to getObject
      Object rc = m_props.getObject(NAMESPACE,name);
      if (!(expected_class.isInstance(rc)))
      { // not the right class!
	ConfigException ce = new ConfigException(FrameAssembler.class,"FrameMessages","property.wrongClass");
	ce.setParameter(0,name);
	ce.setParameter(1,expected_class.getName());
	throw ce;

      } // end if

      return rc;

    } // end try
    catch (NoSuchObjectException e)
    { // translate into ConfigException and reflect it back
      ConfigException ce = new ConfigException(FrameAssembler.class,"FrameMessages","property.notFound",e);
      ce.setParameter(0,name);
      throw ce;

    } // end catch

  } // end initLoadProperty

  private final void setupOptions(OptionSet set)
  {
    m_show_comments = set.get(OPT_COMMENTS);
    m_globals.put("ms_copyright_violations",Boolean.valueOf(set.get(OPT_SMARTTAG)));
    m_footer_breaks = set.get(OPT_FOOTER_BREAKS);

  } // end setupOptions

  /*--------------------------------------------------------------------------------
   * Overrides from class Object
   *--------------------------------------------------------------------------------
   */

  public String toString()
  {
    return "the FrameAssembler";

  } // end toString

  /*--------------------------------------------------------------------------------
   * Implementations from interface NamedObject
   *--------------------------------------------------------------------------------
   */

  public String getName()
  {
    return m_name;

  } // end getName

  /*--------------------------------------------------------------------------------
   * Implementations from interface ComponentInitialize
   *--------------------------------------------------------------------------------
   */

  /**
   * Initialize the component.
   *
   * @param config_root Pointer to the section of the Dynamo XML configuration file that configures this
   *                    particular component.  This is to be considered "read-only" by the component.
   * @param services An implementation of {@link com.silverwrist.dynamo.iface.ServiceProvider ServiceProvider}
   *                 which provides initialization services to the component.  This will include an implementation
   *                 of {@link com.silverwrist.dynamo.iface.ObjectProvider ObjectProvider} which may be used to
   *                 get information about other objects previously initialized by the application.
   * @exception com.silverwrist.dynamo.except.ConfigException If an error is encountered in the component
   *            configuration.
   */
  public void initialize(Element config_root, ServiceProvider services) throws ConfigException
  {
    logger.info("FrameAssembler initializing");

    XMLLoader loader = XMLLoader.get();
    String gprops = null, name_menu = null;
    try
    { // verify the right node name
      loader.verifyNodeName(config_root,"object");

      // get the object's name
      m_name = loader.getAttribute(config_root,"name");

      // get the name of the global properties object
      DOMElementHelper config_root_h = new DOMElementHelper(config_root);
      Element foo = loader.getSubElement(config_root_h,"global-properties");
      gprops = loader.getAttribute(foo,"object");

      // get the name of the menu manager
      foo = loader.getSubElement(config_root_h,"providers");
      name_menu = loader.getAttribute(foo,"menu");

    } // end try
    catch (XMLLoadException e)
    { // error loading XML config data
      logger.fatal("XML loader exception in FrameAssembler",e);
      throw new ConfigException(e);

    } // end catch

    // Get the global data manager object.
    ServiceProvider gdm_sp =
        (ServiceProvider)(GetObjectUtils.getDynamoComponent(services,ServiceProvider.class,gprops));
    try
    { // get the "properties" and "blocks" services from that object
      m_props = (ObjectProvider)(gdm_sp.queryService(ObjectProvider.class,"properties"));
      m_blocks = (ObjectProvider)(gdm_sp.queryService(ObjectProvider.class,"blocks"));

    } // end try
    catch (NoSuchServiceException e)
    { // this shouldn't happen, but...
      throw new ConfigException(FrameAssembler.class,"FrameMessages","init.noGlobals",e);

    } // end catch

    // Set up the initial values of the global properties.
    Iterator it = PROP_TO_VELOCITY.entrySet().iterator();
    while (it.hasNext())
    { // scan through maps and load globals
      Map.Entry ntry = (Map.Entry)(it.next());
      String key = ntry.getKey().toString();
      m_globals.put(ntry.getValue().toString(),initLoadProperty(key,(Class)(PROP_TO_TYPE.get(key))));

    } // end while

    // Set up the frame template.
    m_frame_template = (String)initLoadProperty(PARAM_FRAME_TEMPLATE_NAME,String.class);

    // Set up the initial values for the option set.
    setupOptions((OptionSet)initLoadProperty(PARAM_OPTIONSET,OptionSet.class));

    m_base_params = new BaseParams();  // create base parameter object

    // Load the menu provider.
    m_menu_prov = (MenuProvider)(GetObjectUtils.getDynamoComponent(services,MenuProvider.class,name_menu));

    // Register to receive global property update events.
    EventListenerRegistration reg =
        (EventListenerRegistration)(services.queryService(EventListenerRegistration.class));
    m_shut_event = reg.registerDynamicUpdateListener(GlobalPropertyUpdateEvent.class,this);

    // Add an object to the standard objects available to Velocity.
    VelocityRendererConfig vrcon =
        (VelocityRendererConfig)(services.queryService(VelocityRendererConfig.class));
    m_shut_vr = vrcon.addStandardComponentInstance("frame",new VelocityOps());

    // Register this object as a filter for output.
    OutputObjectFilterRegistration freg =
        (OutputObjectFilterRegistration)(services.queryService(OutputObjectFilterRegistration.class));
    m_shut_fasm = freg.registerOutputObjectFilter(this);

    logger.info("FrameAssembler init complete");

  } // end initialize

  /*--------------------------------------------------------------------------------
   * Implementations from interface ComponentShutdown
   *--------------------------------------------------------------------------------
   */

  public void shutdown()
  {
    m_shut_fasm.shutdown();
    m_shut_fasm = null;
    m_shut_vr.shutdown();
    m_shut_vr = null;
    m_shut_event.shutdown();
    m_shut_event = null;
    m_base_params = null;
    m_menu_prov = null;
    m_props = null;
    m_blocks = null;
    m_globals.clear();

  } // end shutdown

  /*--------------------------------------------------------------------------------
   * Implementations from interface OutputObjectFilter
   *--------------------------------------------------------------------------------
   */

  public Object filterObject(Object input, Request r) throws RenderingException
  {
    if (RequestType.HTTP.equals(r.getType()) && (input instanceof FramedContent))
    { // we need to retrieve the user from the session
      try
      { // get the user
	RequestHelper rh = new RequestHelper(r);
	SessionInfo session = rh.getSession();
	DynamoUser user = (DynamoUser)(session.getObject(SessionInfoParams.NAMESPACE,
							 SessionInfoParams.ATTR_USER));

	// get the request-borne data
	String location = (String)(r.getObject(SessionInfoParams.REQ_NAMESPACE,
					       SessionInfoParams.RATTR_LOCATION));
	Boolean display_login = (Boolean)(r.getObject(SessionInfoParams.REQ_NAMESPACE,
						      SessionInfoParams.RATTR_DISPLAY_LOGIN));

	// get the menus
	MenuRenderObject fixed_menu = m_menu_prov.getLeftMenu(user,VeniceNamespaces.FRAME_LAF_NAMESPACE,
							      "fixed.menu",NO_ACLS);

	// get the footer and optionally insert line breaks
	String footer = (String)(m_blocks.getObject(NAMESPACE,BLOCK_FOOTER));
	if (m_footer_breaks)
	  // set up the regular expression system to replace carriage returns to insert a <br />
	  footer = PAT_LINEBREAK.matcher(footer).replaceAll("<br />\r\n");

	return new FrameRendering((FramedContent)input,m_frame_template,m_base_params,user,location,
				  display_login,fixed_menu,footer);

      } // end try
      catch (DatabaseException e)
      { // rethrow it as a RenderingException
	throw new RenderingException(e);

      } // end catch

    } // end if

    return null;

  } // end filterObject

  /*--------------------------------------------------------------------------------
   * Implementations from interface DynamicUpdateListener
   *--------------------------------------------------------------------------------
   */

  public void updateReceived(DynamicUpdateEvent evt)
  {
    GlobalPropertyUpdateEvent event = (GlobalPropertyUpdateEvent)evt;
    if (NAMESPACE.equals(event.getPropertyNamespace()))
    { // see if this is one of the properties we monitor
      String key = (String)(PROP_TO_VELOCITY.get(event.getPropertyName()));
      if (key!=null)
	m_globals.put(key,m_props.getObject(NAMESPACE,event.getPropertyName()));

      if (PARAM_FRAME_TEMPLATE_NAME.equals(event.getPropertyName()))
	m_frame_template = (String)(m_props.getObject(NAMESPACE,event.getPropertyName()));

      if (PARAM_OPTIONSET.equals(event.getPropertyName()))
	setupOptions((OptionSet)(m_props.getObject(NAMESPACE,event.getPropertyName())));

    } // end if

  } // end updateReceived

  /*--------------------------------------------------------------------------------
   * Static initializer
   *--------------------------------------------------------------------------------
   */

  static
  { // Initialize the mapping from property names to Velocity parameter names.
    HashMap tmp = new HashMap();
    HashMap tmp2 = new HashMap();
    tmp.put(PARAM_SITE_TITLE,"sitetitle");
    tmp2.put(PARAM_SITE_TITLE,String.class);
    tmp.put(PARAM_SITE_URL,"siteurl");
    tmp2.put(PARAM_SITE_URL,String.class);
    tmp.put(PARAM_SITE_LOGO_URL,"logourl");
    tmp2.put(PARAM_SITE_LOGO_URL,String.class);
    tmp.put(PARAM_SITE_LOGO_URL_TYPE,"logourltype");
    tmp2.put(PARAM_SITE_LOGO_URL_TYPE,String.class);
    tmp.put(PARAM_SITE_LOGO_WIDTH,"logowidth");
    tmp2.put(PARAM_SITE_LOGO_WIDTH,Integer.class);
    tmp.put(PARAM_SITE_LOGO_HEIGHT,"logoheight");
    tmp2.put(PARAM_SITE_LOGO_HEIGHT,Integer.class);
    tmp.put(PARAM_FOOTER_LOGO_SCALE,"flogoscale");
    tmp2.put(PARAM_FOOTER_LOGO_SCALE,Integer.class);
    tmp.put(PARAM_PAGEICON_URL,"pgicon_url");
    tmp2.put(PARAM_PAGEICON_URL,String.class);
    tmp.put(PARAM_PAGEICON_URL_TYPE,"pgicon_ltyp");
    tmp2.put(PARAM_PAGEICON_URL_TYPE,String.class);
    tmp.put(PARAM_PAGEICON_TYPE,"pgicon_mime");
    tmp2.put(PARAM_PAGEICON_TYPE,String.class);
    tmp.put(PARAM_FAVICON_URL,"favicon_url");
    tmp2.put(PARAM_FAVICON_URL,String.class);
    tmp.put(PARAM_FAVICON_URL_TYPE,"favicon_ltyp");
    tmp2.put(PARAM_FAVICON_URL_TYPE,String.class);
    PROP_TO_VELOCITY = Collections.unmodifiableMap(tmp);
    PROP_TO_TYPE = Collections.unmodifiableMap(tmp2);

  } // end static initializer

} // end class FrameAssembler
