/*
 * 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) 2003 Eric J. Bowersox/Silverwrist Design Studios.  All Rights Reserved.
 * 
 * Contributor(s): 
 */
package com.silverwrist.venice.sidebox;

import java.util.*;
import org.apache.commons.collections.*;
import org.apache.log4j.Logger;
import org.w3c.dom.*;
import com.silverwrist.util.xml.*;
import com.silverwrist.dynamo.db.NamespaceCache;
import com.silverwrist.dynamo.except.*;
import com.silverwrist.dynamo.iface.*;
import com.silverwrist.dynamo.util.*;
import com.silverwrist.venice.except.*;
import com.silverwrist.venice.iface.*;
import com.silverwrist.venice.session.SessionInfoParams;
import com.silverwrist.venice.sidebox.test.TestSideboxes;

public class SideboxManager implements NamedObject, ComponentInitialize, ComponentShutdown, SideboxService
{
  /*--------------------------------------------------------------------------------
   * Internal class implementing ObjectProvider to initialize SideboxFactory
   *--------------------------------------------------------------------------------
   */

  private class InitProps implements ObjectProvider
  {
    /*====================================================================
     * Attributes
     *====================================================================
     */

    private int m_sbid;                  // sidebox identifier
    private ReferenceMap m_properties;   // property cache

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

    InitProps(int sbid)
    {
      m_sbid = sbid;
      m_properties = new ReferenceMap(ReferenceMap.HARD,ReferenceMap.SOFT);

    } // end constructor

    /*====================================================================
     * Overrides from class Object
     *====================================================================
     */

    public String toString()
    {
      return "Sidebox Properties For Sidebox #" + m_sbid;

    } // end toString

    /*====================================================================
     * Implementations from interface ObjectProvider
     *====================================================================
     */
    
    public Object getObject(String namespace, String name)
    {
      try
      { // convert the namespace name to an ID here
	PropertyKey key = new PropertyKey(m_ns_cache.namespaceNameToId(namespace),name);
	Object rc = null;
	synchronized (this)
	{ // start by looking in the properties map
	  rc = m_properties.get(key);
	  if (rc==null)
	  { // no use - need to try the database
	    rc = m_ops.getSideboxProperty(m_sbid,key);
	    if (rc!=null)
	    { // found in the database
	      m_properties.put(key,rc);
	      logger.debug("value found in database");

	    } // end if

	  } // end if
	  else
	    logger.debug("value found in cache");

	} // end synchronized block

	if (rc==null)
	{ // the object was not found
	  logger.debug("value not found");
	  throw new NoSuchObjectException(this.toString(),namespace,name);

	} // end if

	return rc;

      } // end try
      catch (DatabaseException e)
      { // translate into our NoSuchObjectException but retain the DatabaseException
	logger.debug("Database exception while doing find",e);
	throw new NoSuchObjectException(this.toString(),namespace,name,e);

      } // end catch

    } // end getObject

  } // end class InitProps

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

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

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

  private String m_name;                       // this object's name
  private SideboxOps m_ops;                    // operations object
  private NamespaceCache m_ns_cache;           // namespace cache object
  private Hashtable m_qnk_to_tf;               // maps QualifiedNameKey to SideboxTypeFactory
  private Hashtable m_ns_to_tf;                // maps namespace to SideboxTypeFactory
  private ReferenceMap m_pk_to_fact;           // maps PropertyKey to SideboxFactory
  private ReferenceMap m_id_to_type;           // maps sidebox IDs to types
  private ReferenceMap m_id_to_name;           // maps sidebox IDs to names

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

  public SideboxManager()
  {
    m_qnk_to_tf = new Hashtable();
    m_ns_to_tf = new Hashtable();
    m_pk_to_fact = new ReferenceMap(ReferenceMap.HARD,ReferenceMap.SOFT);
    m_id_to_type = new ReferenceMap(ReferenceMap.HARD,ReferenceMap.SOFT);
    m_id_to_name = new ReferenceMap(ReferenceMap.HARD,ReferenceMap.SOFT);
    m_ns_to_tf.put(StandardSideboxes.NAMESPACE,new StandardSideboxes());
    m_ns_to_tf.put(TestSideboxes.NAMESPACE,new TestSideboxes());

  } // end constructor

  /*--------------------------------------------------------------------------------
   * 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("SideboxManager initializing");

    XMLLoader loader = XMLLoader.get();
    String name_pool = null, name_nscache = 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 database pool and namespace cache
      DOMElementHelper config_root_h = new DOMElementHelper(config_root);
      Element foo = loader.getSubElement(config_root_h,"database");
      name_pool = loader.getAttribute(foo,"connection");
      name_nscache = loader.getAttribute(foo,"namespaces");

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

    } // end catch

    // Get the database connection pool.
    DBConnectionPool pool = GetObjectUtils.getDatabaseConnection(services,name_pool);

    // Get the database operations object.
    m_ops = SideboxOps.get(pool);

    // Get the namespace cache.
    m_ns_cache = (NamespaceCache)(GetObjectUtils.getDynamoComponent(services,NamespaceCache.class,name_nscache));

  } // end initialize

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

  public void shutdown()
  {
    m_ns_cache = null;
    m_ops.dispose();
    m_ops = null;
    m_id_to_type.clear();
    m_id_to_name.clear();
    m_pk_to_fact.clear();
    m_qnk_to_tf.clear();
    m_ns_to_tf.clear();

  } // end shutdown

  /*--------------------------------------------------------------------------------
   * Implementations from interface SideboxService
   *--------------------------------------------------------------------------------
   */

  public List getSideboxes(Request req, String context_namespace, String context_name, Object context_param)
      throws DynamoException
  {
    // Start by getting the current user object.
    SessionInfoProvider sip = (SessionInfoProvider)(req.queryService(SessionInfoProvider.class));
    SessionInfo si = sip.getSessionInfo();
    DynamoUser user = (DynamoUser)(si.getObject(SessionInfoParams.NAMESPACE,SessionInfoParams.ATTR_USER));
    if (logger.isDebugEnabled())
      logger.debug("Retrieving sidebox list for user: " + user.getName());

    // Get the IDs of all the sideboxes.
    int[] ids = m_ops.getSideboxIDList(user.getUID(),m_ns_cache.namespaceNameToId(context_namespace),context_name,
				       context_param);
    if (logger.isDebugEnabled())
      logger.debug("Returned " + ids.length + " sideboxes");
    if (ids.length==0)
      return Collections.EMPTY_LIST;

    // Create the return list.
    ArrayList rc = new ArrayList(ids.length);
    for (int i=0; i<ids.length; i++)
    { // get the key to use for mapping
      if (logger.isDebugEnabled())
	logger.debug("Creating sidebox #" + ids[i]);
      Integer key = new Integer(ids[i]);
      SideboxFactory fact = null;

      synchronized (this)
      { // Look up the sidebox identity.
	PropertyKey name_key = null;

        // look up the type in our map first
	name_key = (PropertyKey)(m_id_to_name.get(key));
	if (name_key==null)
	{ // go down to the database and get it
	  name_key = m_ops.getSideboxIdentity(ids[i]);
	  m_id_to_name.put(key,name_key);

	} // end if

	if (logger.isDebugEnabled())
	  logger.debug("Sidebox name: " + name_key);

	// Now try and look up the factory.
	fact = (SideboxFactory)(m_pk_to_fact.get(name_key));
	if (fact==null)
	{ // need to go create the factory
	  PropertyKey type_key = null;

	  // look up the type in our map first
	  type_key = (PropertyKey)(m_id_to_type.get(key));
	  if (type_key==null)
	  { // go down to the database and get it
	    type_key = m_ops.getSideboxType(ids[i]);
	    m_id_to_type.put(key,type_key);

	  } // end if

	  if (logger.isDebugEnabled())
	    logger.debug("Sidebox type: " + type_key);

	  // Convert the type key to a QualifiedNameKey and look for a SideboxTypeFactory to use.
	  QualifiedNameKey type_qn = new QualifiedNameKey(m_ns_cache.namespaceIdToName(type_key.getNamespaceID()),
							  type_key.getName());
	  SideboxTypeFactory typefact = (SideboxTypeFactory)(m_qnk_to_tf.get(type_qn));
	  if (typefact==null)
	    typefact = (SideboxTypeFactory)(m_ns_to_tf.get(type_qn.getNamespace()));
	  if (typefact==null)
	  { // no type factory found for this sidebox type - throw an error
	    SideboxException se = new SideboxException(SideboxManager.class,"SideboxMessages","no.sbtype");
	    se.setParameter(0,type_qn.getNamespace());
	    se.setParameter(1,type_qn.getName());
	    throw se;

	  } // end if

	  if (logger.isDebugEnabled())
	    logger.debug("Type factory is of class: " + typefact.getClass().getName());

	  // Now create the sidebox factory.
	  fact = typefact.createFactory(type_qn.getNamespace(),type_qn.getName(),
					m_ns_cache.namespaceIdToName(name_key.getNamespaceID()),name_key.getName(),
					new InitProps(ids[i]));
	  m_pk_to_fact.put(name_key,fact);

	} // end if (no factory there)

      } // end synchronized block

      if (logger.isDebugEnabled())
	logger.debug("Factory is of class: " + fact.getClass().getName());

      // Use the factory to create the sidebox.
      Sidebox sbox = fact.createSidebox(req);
      if (sbox!=null)
	rc.add(sbox);

    } // end for

    if (logger.isDebugEnabled())
      logger.debug("Final sidebox list has " + rc.size() + " element(s)");

    if (rc.isEmpty())
      return Collections.EMPTY_LIST;
    rc.trimToSize();
    return Collections.unmodifiableList(rc);

  } // end getSideboxes

  public List getSideboxDescriptors(DynamoUser user, String context_namespace, String context_name,
				    Object context_param) throws DatabaseException
  {
    List rc = m_ops.getSideboxDescriptors(user.getUID(),m_ns_cache.namespaceNameToId(context_namespace),context_name,
					  context_param);
    if (rc.isEmpty())
      return Collections.EMPTY_LIST;
    return Collections.unmodifiableList(rc);

  } // end getSideboxDescriptors

  public List getAllSideboxDescriptors(String context_namespace, String context_name) throws DatabaseException
  {
    List rc = m_ops.getAllSideboxDescriptors(m_ns_cache.namespaceNameToId(context_namespace),context_name);
    if (rc.isEmpty())
      return Collections.EMPTY_LIST;
    return Collections.unmodifiableList(rc);

  } // end getAllSideboxDescriptors

  public void moveSideboxItemToPosition(DynamoUser user, String context_namespace, String context_name,
					Object context_param, int sbid, int new_pos) throws DatabaseException
  {
    m_ops.moveSideboxItemToPosition(user.getUID(),m_ns_cache.namespaceNameToId(context_namespace),context_name,
				    context_param,sbid,new_pos);

  } // end moveSideboxItemToPosition

  public void removeSidebox(DynamoUser user, String context_namespace, String context_name, Object context_param,
			    int sbid) throws DatabaseException
  {
    m_ops.removeSidebox(user.getUID(),m_ns_cache.namespaceNameToId(context_namespace),context_name,context_param,sbid);

  } // end removeSidebox

  public void addSidebox(DynamoUser user, String context_namespace, String context_name, Object context_param,
			 int sbid) throws DatabaseException
  {
    m_ops.addSidebox(user.getUID(),m_ns_cache.namespaceNameToId(context_namespace),context_name,context_param,sbid);

  } // end addSidebox

  public void copyUserConfig(DynamoUser from, DynamoUser to) throws DatabaseException
  {
    m_ops.copyUserConfig(from.getUID(),to.getUID());

  } // end copyUserConfig

} // end class SideboxManager
