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

import java.util.*;
import javax.servlet.*;
import org.apache.log4j.*;
import com.silverwrist.venice.core.*;
import com.silverwrist.venice.except.*;
import com.silverwrist.venice.ui.*;
import com.silverwrist.venice.ui.helpers.*;
import com.silverwrist.venice.ui.servlet.RequestImpl;

public class CurrentConference implements AutoCleanup
{
  /*--------------------------------------------------------------------------------
   * Static data members
   *--------------------------------------------------------------------------------
   */

  private static Category logger = Category.getInstance(CurrentConference.class);

  private static final String ATTR_NAME = "HELPER.CurrentConference";
  private static final String TOPIC_SORT_HOLDER_ATTR = "conference.topic.TopicSortHolder";
  private static final String TOPIC_VISIT_ORDER_ATTR = "conference.topic.TopicVisitOrder";

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

  private RequestInput rinput;
  private ConferenceContext conf = null;
  private TopicContext topic = null;
  private TopicSortHolder topic_sort = null;

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

  public CurrentConference(RequestInput rinput)
  {
    this.rinput = rinput;
    rinput.setRequestAttribute(ATTR_NAME,this);
    rinput.registerCleanup(this);

  } // end constructor

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

  private final synchronized void getTopicSortHolder()
  {
    if (topic_sort==null)
    { // look in the session variables first
      topic_sort = (TopicSortHolder)(rinput.getSessionAttribute(TOPIC_SORT_HOLDER_ATTR));
      if (topic_sort==null)
      { // create a new one and save it off
	topic_sort = new TopicSortHolder();
	rinput.setSessionAttribute(TOPIC_SORT_HOLDER_ATTR,topic_sort);

      } // end if

    } // end if

  } // end getTopicSortHolder

  /*--------------------------------------------------------------------------------
   * Implementations from interface AutoCleanup
   *--------------------------------------------------------------------------------
   */

  public void cleanup()
  {
    rinput.setRequestAttribute(ATTR_NAME,null);
    rinput = null;
    conf = null;
    topic_sort = null;

  } // end cleanup

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

  public final ConferenceContext getConferenceParam(String param_name, boolean required, String on_error)
    throws ErrorBox, LogInOrCreate
  {
    CommunityContext comm = rinput.getCommunity(required,on_error);
    ConferenceContext rc = null;

    if (rinput.hasParameter(param_name))
    { // get the conference ID
      int conf_id = rinput.getParameterInt(param_name,-1);
      if (conf_id<0)
      { // the ID parameter is not valid!
	logger.error("Cannot convert conference parameter '" + rinput.getParameter(param_name) + "'!");
	throw new ErrorBox(null,"Invalid conference parameter.",on_error);

      } // end if

      if (comm==null)
        throw new ErrorBox(null,"Community context for conference not specified.",on_error);

      try
      { // get the conference context
	rc = comm.getConferenceContext(conf_id);
	if (rc==null)
	{ // couldn't find the conference
	  logger.error("community #" + comm.getCommunityID() + " does not contain conference #" + conf_id);
	  throw new ErrorBox(null,"The conference #" + conf_id + " could not be found in the '"
			          + comm.getName() + "' community.",on_error);

	} // end if

	if (logger.isDebugEnabled())
	  logger.debug("found conf #" + rc.getConfID());

      } // end try
      catch (AccessError ae)
      { // these all get handled in pretty much the same way
	if (rinput.getUser().isLoggedIn())
	  throw new ErrorBox("Access Error",ae.getMessage(),on_error);
	else
	  throw new LogInOrCreate();

      } // end catch
      catch (DataException de)
      { // error looking up the conference
	throw new ErrorBox("Database Error","Database error finding conference: " + de.getMessage(),on_error);

      } // end catch

    } // end if
    else
    { // there's no conference parameter
      if (required)
      { // no conference parameter - bail out now!
	logger.error("Conference parameter not specified!");
	throw new ErrorBox(null,"No conference specified.",on_error);

      } // end if

      if (logger.isDebugEnabled())
	logger.debug("no conference specified");

    } // end else

    return rc;

  } // end getConferenceParam

  public final ConferenceContext getConference()
  {
    return conf;

  } // end getConference

  public final ConferenceContext getConference(boolean required, String on_error)
      throws ErrorBox, LogInOrCreate
  {
    if (conf!=null)
      return conf;

    // call down to our sub-function
    conf = getConferenceParam("conf",required,on_error);
    return conf;

  } // end getConference

  public final TopicContext getTopicParam(String param_name, boolean required, String on_error)
      throws ErrorBox, LogInOrCreate
  {
    ConferenceContext conf = getConference(required,on_error);
    TopicContext rc = null;

    if (logger.isDebugEnabled())
      logger.debug("getTopicParam(\"" + param_name + "\"," + required + ",...) entry - param is "
		   + rinput.getParameter(param_name));

    if (rinput.hasParameter(param_name))
    { // get the topic ID
      short topic_id = rinput.getParameterShort(param_name,(short)(-1));
      if (topic_id<0)
      { // the ID parameter is not valid!
	logger.error("Cannot convert topic parameter '" + rinput.getParameter(param_name) + "'!");
	throw new ErrorBox(null,"Invalid topic parameter.",on_error);

      } // end if

      if (logger.isDebugEnabled())
	logger.debug("translated topic parameter: #" + topic_id);

      if (conf==null)
        throw new ErrorBox(null,"Conference context for topic not specified.",on_error);

      try
      { // get the topic context
	rc = conf.getTopic(topic_id);
	if (rc==null)
	{ // couldn't find the topic
	  logger.error("conference #" + conf.getConfID() + " does not contain topic #" + topic_id);
	  throw new ErrorBox(null,"The topic #" + topic_id + " could not be found in the '"
			          + conf.getName() + "' conference.",on_error);

	} // end if

	if (logger.isDebugEnabled())
	  logger.debug("found topic #" + rc.getTopicNumber() + " (" + rc.getTopicID() + ")");

      } // end try
      catch (AccessError ae)
      { // these all get handled in pretty much the same way
        logger.error("caught AccessError: " + ae.getMessage());
	if (rinput.getUser().isLoggedIn())
	  throw new ErrorBox("Access Error",ae.getMessage(),on_error);
	else
	  throw new LogInOrCreate();

      } // end catch
      catch (DataException de)
      { // error looking up the conference
	logger.error("caught DataException: " + de.getMessage());
	throw new ErrorBox("Database Error","Database error finding topic: " + de.getMessage(),on_error);

      } // end catch

    } // end if
    else
    { // there's no topic parameter
      if (required)
      { // no topic parameter - bail out now!
	logger.error("Topic parameter not specified!");
	throw new ErrorBox(null,"No topic specified.",on_error);

      } // end if

      if (logger.isDebugEnabled())
	logger.debug("no topic specified");

    } // end else

    return rc;

  } // end getTopicParam

  public final TopicContext getOptionalTopic(String param_name)
  {
    if (conf==null)
    { // no conference context
      logger.warn("getOptionalTopic(): no conference context");
      return null;

    } // end if

    short topic_id = rinput.getParameterShort(param_name,(short)0);
    if (topic_id<=0)
    { // the topic parameter is not real valid
      logger.warn("getOptionalTopic(): invalid topic number");
      return null;

    } // end if

    TopicContext rc = null;
    try
    { // get a topic
      rc = conf.getTopic(topic_id);
      if (rc==null)
	logger.warn("getOptionalTopic(): topic # " + topic_id + " not found");

    } // end try
    catch (DataException de)
    { // ignore DataException
      logger.warn("getOptionalTopic(): DataException getting topic",de);
      return null;
      
    } // end catch
    catch (AccessError ae)
    { // ignore AccessError
      logger.warn("getOptionalTopic(): AccessError getting topic",ae);
      return null;

    } // end catch

    return rc;

  } // end getOptionalTopic

  public final TopicContext getTopic()
  {
    return topic;

  } // end getTopic

  public final TopicContext getTopic(boolean required, String on_error) throws ErrorBox, LogInOrCreate
  {
    if (topic!=null)
      return topic;

    // call down to our sub-function
    topic = getTopicParam("top",required,on_error);
    return topic;

  } // end getTopic

  public final TopicMessageContext getMessageParam(String param_name, boolean required, String on_error)
      throws ErrorBox, LogInOrCreate
  {
    ConferenceContext conf = getConference(required,on_error);
    TopicMessageContext rc = null;

    if (rinput.hasParameter(param_name))
    { // get the message ID
      long message_id = rinput.getParameterLong(param_name,(long)(-1));
      if (message_id<0)
      { // the ID parameter is not valid!
	logger.error("Cannot convert message parameter '" + rinput.getParameter(param_name) + "'!");
	throw new ErrorBox(null,"Invalid message parameter.",on_error);

      } // end if

      if (conf==null)
        throw new ErrorBox(null,"Conference context for message not specified.",on_error);

      try
      { // get the message by its post ID
	rc = conf.getMessageByPostID(message_id);
	if (rc==null)
	{ // couldn't find the topic
	  logger.error("conference #" + conf.getConfID() + " does not contain message #" + message_id);
	  throw new ErrorBox(null,"The message #" + message_id + " could not be found in the '"
			          + conf.getName() + "' conference.",on_error);

	} // end if

	if (logger.isDebugEnabled())
	  logger.debug("found message #" + rc.getPostNumber() + " (" + rc.getPostID() + ")");

      } // end try
      catch (AccessError ae)
      { // these all get handled in pretty much the same way
	if (rinput.getUser().isLoggedIn())
	  throw new ErrorBox("Access Error",ae.getMessage(),on_error);
	else
	  throw new LogInOrCreate();

      } // end catch
      catch (DataException de)
      { // error looking up the conference
	throw new ErrorBox("Database Error","Database error finding message: " + de.getMessage(),on_error);

      } // end catch

    } // end if
    else
    { // there's no message parameter
      if (required)
      { // no message parameter - bail out now!
	logger.error("Message parameter not specified!");
	throw new ErrorBox(null,"No message specified.",on_error);

      } // end if

      if (logger.isDebugEnabled())
	logger.debug("no message specified");

    } // end else

    return rc;

  } // end getMessageParam

  public final TopicMessageContext getTopicMessageParam(String param_name, boolean required, String on_error)
      throws ErrorBox, LogInOrCreate
  {
    TopicContext topic = getTopic(required,on_error);
    TopicMessageContext rc = null;

    if (rinput.hasParameter(param_name))
    { // get the post number
      int post_num = rinput.getParameterInt(param_name,-1);
      if (post_num<0)
      { // the post number parameter is not valid!
	logger.error("Cannot convert message parameter '" + rinput.getParameter(param_name) + "'!");
	throw new ErrorBox(null,"Invalid message parameter.",on_error);

      } // end if

      if (topic==null)
        throw new ErrorBox(null,"Topic context for message not specified.",on_error);

      try
      { // get the appropriate message
	rc = topic.getMessage(post_num);
	if (rc==null)
	{ // couldn't find the topic
	  logger.error("topic #" + topic.getTopicID() + " does not contain message #" + post_num);
	  throw new ErrorBox(null,"The message #" + post_num + " could not be found in the '"
			          + topic.getName() + "' topic.",on_error);

	} // end if

	if (logger.isDebugEnabled())
	  logger.debug("found message #" + rc.getPostNumber() + " (" + rc.getPostID() + ")");

      } // end try
      catch (AccessError ae)
      { // these all get handled in pretty much the same way
	if (rinput.getUser().isLoggedIn())
	  throw new ErrorBox("Access Error",ae.getMessage(),on_error);
	else
	  throw new LogInOrCreate();

      } // end catch
      catch (DataException de)
      { // error looking up the conference
	throw new ErrorBox("Database Error","Database error finding message: " + de.getMessage(),on_error);

      } // end catch

    } // end if
    else
    { // there's no message parameter
      if (required)
      { // no message parameter - bail out now!
	logger.error("Message parameter not specified!");
	throw new ErrorBox(null,"No message specified.",on_error);

      } // end if

      if (logger.isDebugEnabled())
	logger.debug("no message specified");

    } // end else

    return rc;

  } // end getTopicMessageParam

  public final int getViewOption()
  {
    if (conf==null)
      return -1;
    getTopicSortHolder();
    return topic_sort.getViewOption(conf.getConfID());

  } // end getViewOption

  public final void setViewOption(int opt)
  {
    if (conf==null)
      return;
    getTopicSortHolder();
    topic_sort.setViewOption(conf.getConfID(),opt);

  } // end setViewOption

  public final int getSortOption()
  {
    if (conf==null)
      return -1;
    getTopicSortHolder();
    return topic_sort.getSortOption(conf.getConfID());

  } // end getSortOption

  public final void setSortOption(int opt)
  {
    if (conf==null)
      return;
    getTopicSortHolder();
    topic_sort.setSortOption(conf.getConfID(),opt);

  } // end setSortOption

  public final TopicVisitOrder getTopicVisitOrder()
  {
    if (conf==null)
      return null;
    Map map = (Map)(rinput.getSessionAttribute(TOPIC_VISIT_ORDER_ATTR));
    if (map==null)
      return null;
    return (TopicVisitOrder)(map.get(new Integer(conf.getConfID())));

  } // end getTopicVisitOrder

  public final TopicVisitOrder newTopicVisitOrder(List topiclist)
  {
    if (conf==null)
      return null;
    TopicVisitOrder tvo = new TopicVisitOrder(topiclist);
    Map map = null;
    synchronized (this)
    { // get the map in the session attribute
      map = (Map)(rinput.getSessionAttribute(TOPIC_VISIT_ORDER_ATTR));
      if (map==null)
      { // create new map and save it off
	map = Collections.synchronizedMap(new HashMap());
	rinput.setSessionAttribute(TOPIC_VISIT_ORDER_ATTR,map);

      } // end if

    } // end synchronized block

    map.put(new Integer(conf.getConfID()),tvo);
    return tvo;

  } // end newTopicVisitOrder

  public final PostInterval getPostInterval(String on_error) throws ErrorBox
  {
    if (topic==null)
      return null;

    int first, last;

    if (rinput.hasParameter("pxg"))
    { // we have a Go box parameter - decode it
      String foo = rinput.getParameter("pxg");
      int p = foo.indexOf('-');  // range specifier
      try
      { // attempt to parse out two post numbers from here
	if (p<0)
	{ // only one number specified - make them both equal
	  first = Integer.parseInt(foo.trim());
	  last = first;

	} // end if
	else if (p==0)
	{ // "-number" is the same as "0-number"
	  last = Integer.parseInt(foo.substring(1).trim());
	  first = 0;

	} // end else if
	else if (p==(foo.length()-1))
	{ // "number-" is the same as "number-<end of topic>"
	  first = Integer.parseInt(foo.substring(0,p).trim());
	  last = topic.getTotalMessages() - 1;

	} // end else if
	else
	{ // decode both numbers
	  first = Integer.parseInt(foo.substring(0,p).trim());
	  last = Integer.parseInt(foo.substring(p+1).trim());

	} // end else
     
	return new PostInterval(first,last);  // this will sort them out

      } // end try
      catch (NumberFormatException nfe)
      { // if numeric conversion fails, just fall out and try to redisplay the other way
      } // end catch

    } // end if

    if (rinput.hasParameter("p1"))
    { // convert the first message parameter and range-test it
      first = rinput.getParameterInt("p1",-1);
      if (first<0)
	first = 0;
      else if (first>=topic.getTotalMessages())
	first = topic.getTotalMessages() - 1;

      // get and range-check the second parameter (if it's there)
      last = rinput.getParameterInt("p2",first);
      if ((last<0) || (last>=topic.getTotalMessages()))
	last = topic.getTotalMessages() - 1;

    } // end if
    else
    { // no range specified - cook up a default
      last = topic.getTotalMessages();
      int unread = topic.getUnreadMessages();
      if ((unread==0) || (unread>=rinput.getEngine().getNumPostsPerPage()))
	first = last - rinput.getEngine().getNumPostsPerPage();
      else
	first = last - (unread + rinput.getEngine().getNumOldPostsBeforeNew());
      last--;

    } // end else

    return new PostInterval(first,last);  // all done!

  } // end getPostInterval

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

  public static final CurrentConference get(RequestInput ri)
  {
    CurrentConference cc = (CurrentConference)(ri.getRequestAttribute(ATTR_NAME));
    if (cc==null)
      cc = new CurrentConference(ri);
    return cc;

  } // end get

  public static final CurrentConference get(ServletRequest sreq) throws ServletException
  {
    Object obj = sreq.getAttribute(RequestImpl.REQUEST_ATTRIBUTE_STEM + ATTR_NAME);
    if (obj==null)
      throw new ServletException("CurrentConference.get: unable to get request content");
    if (obj instanceof CurrentConference)
      return (CurrentConference)obj;
    throw new ServletException("CurrentConference.get: request content is not a CurrentConference object");
 
  } // end get

} // end class CurrentConference
