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

import java.sql.*;
import java.util.*;
import org.apache.log4j.*;
import com.silverwrist.venice.core.*;
import com.silverwrist.venice.core.internals.*;
import com.silverwrist.venice.db.*;
import com.silverwrist.venice.except.*;
import com.silverwrist.venice.htmlcheck.*;
import com.silverwrist.venice.security.AuditRecord;

class ConferenceCommunityContextImpl implements ConferenceCommunityContext
{
  /*--------------------------------------------------------------------------------
   * Inner class used to cache locally-important data
   *--------------------------------------------------------------------------------
   */

  static class ConfCache
  {
    String name;
    String description;
    java.util.Date created;

    public ConfCache(String name, String description, java.util.Date created)
    {
      this.name = name;
      this.description = description;
      this.created = created;

    } // end constructor

    public String getName()
    {
      return name;

    } // end getName

    public String getDescription()
    {
      return description;

    } // end getDescription

    public java.util.Date getCreated()
    {
      return created;

    } // end getCreated

  } // end class ConfCache

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

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

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

  private EnvConferenceCommunity env; // the environment
  private int confid;              // ID of this conference
  private int level;               // level granted in conference to members of the community
  private short sequence;          // sequence number this conference appears in list
  private boolean hide_list;       // hide the conference in the list?
  private ConfCache cache;         // cache of locally-important information
  private ConferenceData confdata = null;  // the inner conference data object
  private boolean deleted = false; // has this conference been deleted?

  /*--------------------------------------------------------------------------------
   * Constructors
   *--------------------------------------------------------------------------------
   */

  ConferenceCommunityContextImpl(EnvCommunityData env, int confid) throws DataException
  {
    if (logger.isDebugEnabled())
      logger.debug("new ConferenceCommunityContextImpl(#" + confid + ") for community # "
		   + env.getCommunityID());

    this.env = new EnvConferenceCommunity(env);
    this.confid = confid;

    Connection conn = null;      // pooled database connection
    Statement stmt = null;

    try
    { // get a database connection
      conn = env.getConnection();
      stmt = conn.createStatement();

      // Build a monster query!  This is like the query for retrieving the ConferenceUserContextImpl
      // data, except without the left outer join to the user conference membership table.
      StringBuffer sql =
	  new StringBuffer("SELECT c.createdate, c.name, c.descr, s.granted_lvl, s.sequence, s.hide_list "
			   + "FROM sigtoconf s, confs c WHERE s.confid = c.confid AND s.sigid = ");
      sql.append(env.getCommunityID()).append(" AND c.confid = ").append(confid).append(';');
      if (logger.isDebugEnabled())
	logger.debug("SQL: " + sql.toString());

      // Run that monster query!
      ResultSet rs = stmt.executeQuery(sql.toString());
      if (!(rs.next()))
	throw new DataException("conference ID#" + confid + " not found in community#"
				+ env.getCommunityID());

      // fill in the "cache" and "level" indicators
      this.cache = new ConfCache(rs.getString(2),rs.getString(3),SQLUtil.getFullDateTime(rs,1));
      this.level = rs.getInt(4);
      this.sequence = rs.getShort(5);
      this.hide_list = rs.getBoolean(6);

    } // end try
    catch (SQLException e)
    { // turn SQLException into data exception
      logger.error("DB error reading conference entry: " + e.getMessage(),e);
      throw new DataException("unable to retrieve conference information: " + e.getMessage(),e);

    } // end catch
    finally
    { // make sure we release the connection before we go
      SQLUtil.shutdown(stmt);
      env.releaseConnection(conn);

    } // end finally

  } // end constructor

  ConferenceCommunityContextImpl(EnvCommunityData env, short sequence, boolean hide_list, ConferenceData cdata)
  {
    if (logger.isDebugEnabled())
      logger.debug("new ConferenceCommunityContextImpl(NEW#" + confid + ") for community # "
		   + env.getCommunityID());

    this.env = new EnvConferenceCommunity(env);
    this.confid = cdata.getID();
    this.level = 0;
    this.sequence = sequence;
    this.hide_list = hide_list;
    this.cache = null;
    this.confdata = cdata;

  } // end constructor

  /*--------------------------------------------------------------------------------
   * Internal functions
   *--------------------------------------------------------------------------------
   */

  private final synchronized ConferenceData getConferenceData() throws DataException
  {
    if (confdata==null)
    { // attempt to load the ConferenceCommunityContext
      if (deleted)
	throw new DataException("This conference has been deleted.");
      confdata = env.getEngine().getConferenceDataObject(confid);

      // clear cache when we get the real confdata
      cache = null;

    } // end if

    return confdata;

  } // end getConferenceData

  private final synchronized ConferenceData getConferenceDataNE()
  {
    if (confdata==null)
    { // we need to load the ConferenceCommunityContext...
      if (deleted)
	return null;  // no return on deletion

      try
      { // attempt to load the ConferenceCommunityContext
	confdata = env.getEngine().getConferenceDataObject(confid);

      } // end try
      catch (DataException e)
      { // there was a data failure loading the conference
	logger.error("** Got DataException in getConferenceDataNE: " + e.getMessage(),e);
	return null;

      } // end catch

      // clear cache when we get the real confdata
      cache = null;

    } // end if

    return confdata;

  } // end getConferenceDataNE

  /*--------------------------------------------------------------------------------
   * Implementations from interface ConferenceCommunityContext
   *--------------------------------------------------------------------------------
   */

  public int getConfID()
  {
    return confid;

  } // end getConfID

  public String getName()
  {
    if (cache!=null)
      return cache.getName();
    ConferenceData c = getConferenceDataNE();
    if (c!=null)
      return c.getName();
    else
      return null;

  } // end getName
 
  public String getDescription()
  {
    if (cache!=null)
      return cache.getDescription();
    ConferenceData c = getConferenceDataNE();
    if (c!=null)
      return c.getDescription();
    else
      return null;

  } // end getDescription

  public java.util.Date getCreationDate()
  {
    if (cache!=null)
      return cache.getCreated();
    ConferenceData c = getConferenceDataNE();
    if (c!=null)
      return c.getCreationDate();
    else
      return null;

  } // end getCreationDate

  public java.util.Date getLastUpdateDate()
  {
    ConferenceData c = getConferenceDataNE();
    if (c!=null)
      return c.getLastUpdateDate();
    else
      return null;

  } // end getLastUpdateDate

  public int getCommunityGrantedLevel()
  {
    return level;

  } // end getCommunityGrantedLevel

  public List getAliases() throws DataException
  {
    return getConferenceData().getAliases();

  } // end getAliases
  
  public List getHosts() throws DataException
  {
    return getConferenceData().getHosts();

  } // end getHosts
  
  public boolean canReadConference(int level)
  {
    ConferenceData c = getConferenceDataNE();
    if (c==null)
      return false;
    if (level<this.level)
      return c.canReadConference(this.level);
    else
      return c.canReadConference(level);

  } // end canReadConference

  public boolean canPostToConference(int level)
  {
    ConferenceData c = getConferenceDataNE();
    if (c==null)
      return false;
    if (level<this.level)
      return c.canPostToConference(this.level);
    else
      return c.canPostToConference(level);

  } // end canPostToConference

  public boolean canCreateTopic(int level)
  {
    ConferenceData c = getConferenceDataNE();
    if (c==null)
      return false;
    if (level<this.level)
      return c.canCreateTopic(this.level);
    else
      return c.canCreateTopic(level);

  } // end canCreateTopic

  public boolean canChangeConference(int level)
  {
    ConferenceData c = getConferenceDataNE();
    if (c==null)
      return false;
    if (level<this.level)
      return c.canChangeConference(this.level);
    else
      return c.canChangeConference(level);

  } // end canChangeConference

  public int getReadLevel() throws DataException
  {
    return getConferenceData().getReadLevel();

  } // end getReadLevel

  public int getPostLevel() throws DataException
  {
    return getConferenceData().getPostLevel();

  } // end getPostLevel

  public int getCreateLevel() throws DataException
  {
    return getConferenceData().getCreateLevel();

  } // end getCreateLevel

  public int getHideLevel() throws DataException
  {
    return getConferenceData().getHideLevel();

  } // end getHideLevel

  public int getNukeLevel() throws DataException
  {
    return getConferenceData().getNukeLevel();

  } // end getNukeLevel

  public int getChangeLevel() throws DataException
  {
    return getConferenceData().getChangeLevel();

  } // end getChangeLevel

  public int getDeleteLevel() throws DataException
  {
    return getConferenceData().getDeleteLevel();

  } // end getDeleteLevel

  public synchronized void setSecurityLevels(EnvCommunity outer, int read, int post, int create, int hide,
					     int nuke, int change, int delete) throws DataException
  {
    getConferenceData().setSecurityLevels(outer,read,post,create,hide,nuke,change,delete);

  } // end setSecurityLevels

  public synchronized void setName(EnvCommunity outer, String val) throws DataException
  {
    getConferenceData().setName(outer,val);

  } // end setName

  public synchronized void setDescription(String val) throws DataException
  {
    getConferenceData().setDescription(val);

  } // end setDescription

  public synchronized void addAlias(EnvCommunity outer, String alias) throws DataException
  {
    getConferenceData().addAlias(outer,alias);

  } // end addAlias

  public synchronized void removeAlias(EnvCommunity outer, String alias) throws DataException
  {
    getConferenceData().removeAlias(outer,alias);

  } // end removeAlias

  public synchronized void setMembership(EnvCommunity outer, int uid, int grant_level) throws DataException
  {
    getConferenceData().setMembership(outer,uid,grant_level);

  } // end setMembership

  public synchronized void setCommunityGrantedLevel(EnvCommunity outer, int new_level) throws DataException
  {
    if (deleted)
      throw new DataException("This conference has been deleted.");

    Connection conn = null;  // database connection
    Statement stmt = null;
    AuditRecord ar = null;   // audit record
      
    try
    { // get a database connection
      conn = env.getConnection();

      // create the SQL statement
      stmt = conn.createStatement();
      StringBuffer sql = new StringBuffer("UPDATE sigtoconf SET granted_lvl = ");
      sql.append(new_level).append(" WHERE sigid = ").append(this.env.getCommunityID());
      sql.append(" AND confid = ").append(confid).append(';');

      // execute the update
      stmt.executeUpdate(sql.toString());

      // save copies of the data in our local variables
      level = new_level;

      // create an audit record reflecting the change
      ar = outer.newAudit(AuditRecord.CONF_SECURITY,"conf=" + confid);

    } // end try
    catch (SQLException e)
    { // database error - this is a DataException
      logger.error("DB error storing conference data: " + e.getMessage(),e);
      throw new DataException("unable to save conference data: " + e.getMessage(),e);

    } // end catch
    finally
    { // make sure the connection is released before we go
      SQLUtil.shutdown(stmt);
      AuditRecord.store(conn,ar);
      env.releaseConnection(conn);

    } // end finally

  } // end setCommunityGrantedLevel

  public short getSequence()
  {
    return sequence;

  } // end getSequence

  public synchronized void setSequence(short seq) throws DataException
  {
    if (deleted)
      throw new DataException("This conference has been deleted.");

    Connection conn = null;  // database connection
    Statement stmt = null;

    try
    { // get a database connection
      conn = env.getConnection();

      // create the SQL statement
      stmt = conn.createStatement();
      StringBuffer sql = new StringBuffer("UPDATE sigtoconf SET sequence = ");
      sql.append(seq).append(" WHERE sigid = ").append(env.getCommunityID()).append(" AND confid = ");
      sql.append(confid).append(';');

      // execute the update
      stmt.executeUpdate(sql.toString());

      // save copies of the data in our local variables
      sequence = seq;

    } // end try
    catch (SQLException e)
    { // database error - this is a DataException
      logger.error("DB error storing conference data: " + e.getMessage(),e);
      throw new DataException("unable to save conference data: " + e.getMessage(),e);

    } // end catch
    finally
    { // make sure the connection is released before we go
      SQLUtil.shutdown(stmt);
      env.releaseConnection(conn);

    } // end finally

  } // end setSequence

  public boolean getHideList()
  {
    return hide_list;

  } // end getHideList

  public void setHideList(EnvCommunity outer, boolean flag) throws DataException
  {
    if (logger.isDebugEnabled())
      logger.debug("setHideList(conf #" + confid + ", " + flag + ")");

    if (deleted)
    { // aiyaiyai - we're deleted
      logger.error("conference deleted, can't change a thing");
      throw new DataException("This conference has been deleted.");

    } // end if

    if (flag==hide_list)
    { // this is a no-op
      logger.warn("setHideList for this conference is a no-op");
      return;

    } // end if

    Connection conn = null;  // database connection
    Statement stmt = null;
    AuditRecord ar = null;   // audit record
      
    try
    { // get a database connection
      conn = env.getConnection();

      // create the SQL statement
      stmt = conn.createStatement();
      StringBuffer sql = new StringBuffer("UPDATE sigtoconf SET hide_list = ");
      sql.append(flag ? '1' : '0').append(" WHERE sigid = ").append(env.getCommunityID());
      sql.append(" AND confid = ").append(confid).append(';');
      if (logger.isDebugEnabled())
	logger.debug("SQL: " + sql.toString());

      // execute the update
      stmt.executeUpdate(sql.toString());

      // save copies of the data in our local variables
      hide_list = flag;

      // create an audit record reflecting the change
      ar = outer.newAudit(AuditRecord.CONF_SECURITY,"conf=" + confid);

    } // end try
    catch (SQLException e)
    { // database error - this is a DataException
      logger.error("DB error storing conference data: " + e.getMessage(),e);
      throw new DataException("unable to save conference data: " + e.getMessage(),e);

    } // end catch
    finally
    { // make sure the connection is released before we go
      SQLUtil.shutdown(stmt);
      AuditRecord.store(conn,ar);
      env.releaseConnection(conn);

    } // end finally

  } // end setHideList

  public boolean canHideTopics(int level)
  {
    ConferenceData c = getConferenceDataNE();
    if (c==null)
      return false;
    if (level<this.level)
      return c.canHideTopics(this.level);
    else
      return c.canHideTopics(level);

  } // end canHideTopics

  public String getAnAlias() throws DataException
  {
    return getConferenceData().getAnAlias();

  } // end getAnAlias

  public ReturnTopicInfo createNewTopic(EnvCommunity outer, String title, String pseud, String body)
      throws DataException
  {
    ConferenceData d = getConferenceData();
    String conf_alias = d.getAnAlias();
    short new_topic = d.enterCreateTopic();

    try
    { // preprocess the body argument through the HTML checker
      HTMLChecker text_ch = env.getEngine().createCheckerObject(EngineBackend.HTMLC_POST_BODY);
      text_ch.setContextValue("PostLinkDecoderContext",
			      new PostLinkDecoderContext(outer.getCommunity().realCommunityAlias(),conf_alias,
							 new_topic));

      try
      { // run through the HTML checker
	text_ch.append(body);
	text_ch.finish();

      } // end try
      catch (AlreadyFinishedException e)
      { // this isn't right...
	throw new InternalStateError("HTMLChecker erroneously throwing AlreadyFinishedException",e);

      } // end catch

      try
      { // call down to create the new topic!
	return d.createNewTopic(outer,title,pseud,text_ch.getValue(),text_ch.getLines());

      } // end try
      catch (NotYetFinishedException e)
      { // this isn't right either!
	throw new InternalStateError("HTMLChecker erroneously throwing NotYetFinishedException",e);

      } // end catch

    } // end try
    finally
    { // make sure we relinquish the topic creation no matter what happens
      d.exitCreateTopic();

    } // end finally

  } // end createNewTopic

  public boolean canScribblePosts(int level)
  {
    ConferenceData c = getConferenceDataNE();
    if (c==null)
      return false;
    if (level<this.level)
      return c.canScribblePosts(this.level);
    else
      return c.canScribblePosts(level);

  } // end canScribblePosts

  public boolean canNukePosts(int level)
  {
    ConferenceData c = getConferenceDataNE();
    if (c==null)
      return false;
    if (level<this.level)
      return c.canNukePosts(this.level);
    else
      return c.canNukePosts(level);

  } // end canNukePosts

  public void touchUpdate(Connection conn, java.util.Date date) throws DataException
  {
    getConferenceData().touchUpdate(conn,date);

  } // end touchUpdate

  public short getTopTopic()
  {
    ConferenceData c = getConferenceDataNE();
    if (c==null)
      return -1;
    else
      return c.getTopTopic();

  } // end getTopTopic

  public List getMemberList() throws DataException
  {
    return getConferenceData().getMemberList();

  } // end getMemberList

  public int getMemberLevel(int uid) throws DataException
  {
    return getConferenceData().getMemberLevel(uid);

  } // end getMemberLevel

  public boolean canDeleteConference(int level)
  {
    ConferenceData c = getConferenceDataNE();
    if (c==null)
      return false;
    if (level<this.level)
      return c.canDeleteConference(this.level);
    else
      return c.canDeleteConference(level);

  } // end canDeleteConference

  public void delete(EnvCommunity outer) throws DataException
  {
    ConferenceData c = getConferenceData();
    Connection conn = null;
    Statement stmt = null;
    boolean delete_core = true;

    try
    { // get a database connection
      conn = env.getConnection();
      stmt = conn.createStatement();

      // see if we have to delete the core object as well
      StringBuffer sql = new StringBuffer("SELECT sigid FROM sigtoconf WHERE confid = ");
      sql.append(confid).append(" AND sigid <> ").append(env.getCommunityID()).append(" LIMIT 1;");
      ResultSet rs = stmt.executeQuery(sql.toString());
      if (rs.next())
	delete_core = false;  // we don't delete the core yet
      SQLUtil.shutdown(rs);

      // remove the row that links this conference to this community
      sql.setLength(0);
      sql.append("DELETE FROM sigtoconf WHERE confid = ").append(confid).append(" AND sigid = ");
      sql.append(env.getCommunityID()).append(';');
      stmt.executeUpdate(sql.toString());

      // record that we've been deleted
      level = 0;
      sequence = -1;
      hide_list = true;
      cache = null;
      deleted = true;

    } // end try
    catch (SQLException e)
    { // turn SQLException into data exception
      logger.error("DB error deleting conference entry: " + e.getMessage(),e);
      throw new DataException("unable to delete conference information: " + e.getMessage(),e);

    } // end catch
    finally
    { // make sure we release the connection before we go
      SQLUtil.shutdown(stmt);
      env.releaseConnection(conn);

    } // end finally

    if (delete_core)
    { // the conference is not linked to any other communities - we need to delete the core as well
      c.delete(outer);
      env.getEngine().detachConferenceDataObject(confid);

    } // end if

    // detach our own reference from the lower-level object
    confdata = null;

  } // end delete

  public boolean displayPostPictures()
  {
    ConferenceData c = getConferenceDataNE();
    if (c!=null)
      return c.displayPostPictures();
    else
      return false;

  } // end displayPostPictures

  public ConferenceProperties getProperties() throws DataException
  {
    return getConferenceData().getProperties();

  } // end getProperties

  public void setProperties(ConferenceProperties props) throws DataException
  {
    getConferenceData().setProperties(props);

  } // end setProperties

  public String getCustomBlock(int selector) throws DataException
  {
    return getConferenceData().getCustomBlock(selector);

  } // end getCustomBlock

  public void setCustomBlock(int selector, String data) throws DataException
  {
    getConferenceData().setCustomBlock(selector,data);

  } // end setCustomBlock

  public void removeCustomBlocks() throws DataException
  {
    getConferenceData().removeCustomBlocks();

  } // end removeCustomBlocks

} // end class ConferenceCommunityContextImpl
