/*
 * 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.security;

import java.sql.*;
import java.util.*;
import org.apache.log4j.*;
import com.silverwrist.venice.db.SQLUtil;
import com.silverwrist.venice.core.AuditData;
import com.silverwrist.venice.except.DataException;
import com.silverwrist.venice.except.InternalStateError;

public class AuditRecord implements AuditData
{
  /*--------------------------------------------------------------------------------
   * Internal class for caching description strings on load
   *--------------------------------------------------------------------------------
   */

  static class DescrStringCache
  {
    private HashMap descr_cache = new HashMap();
    private HashMap uname_cache = new HashMap();
    private HashMap commname_cache = new HashMap();
    private Statement stmt;

    DescrStringCache(Connection conn) throws SQLException
    {
      stmt = conn.createStatement();

    } // end constructor

    String getDescription(int code) throws SQLException, DataException
    {
      Integer code_x = new Integer(code);
      String rc = (String)(descr_cache.get(code_x));
      if (rc==null)
      { // OK, get it from the database...
	ResultSet rs = stmt.executeQuery("SELECT descr FROM refaudit WHERE type = " + code + ";");
	if (!(rs.next()))
	  throw new DataException("description string not found for code " + code);
	rc = rs.getString(1);
	SQLUtil.shutdown(rs);
	descr_cache.put(code_x,rc);

      } // end if

      return rc;

    } // end getDescription

    String getUserName(int uid) throws SQLException, DataException
    {
      if (uid==0)
	return "";  // UID 0 = no user
      Integer uid_x = new Integer(uid);
      String rc = (String)(uname_cache.get(uid_x));
      if (rc==null)
      { // OK, get it from the database
	ResultSet rs = stmt.executeQuery("SELECT username FROM users WHERE uid = " + uid + ";");
	if (rs.next())
          rc = rs.getString(1);
	else
	  rc = "(UID #" + uid + ")";
	SQLUtil.shutdown(rs);
	uname_cache.put(uid_x,rc);

      } // end if

      return rc;

    } // end getUserName

    String getCommunityName(int cid) throws SQLException, DataException
    {
      if (cid<=0)
	return "";  // no community
      Integer cid_x = new Integer(cid);
      String rc = (String)(commname_cache.get(cid_x));
      if (rc==null)
      { // OK, get it from the database
	ResultSet rs = stmt.executeQuery("SELECT signame FROM sigs WHERE sigid = " + cid + ";");
	if (rs.next())
	  rc = rs.getString(1);
	else
	  rc = "(CID #" + cid + ")";
	SQLUtil.shutdown(rs);
	commname_cache.put(cid_x,rc);

      } // end if

      return rc;

    } // end getCommunityName

    void shutdown()
    {
      SQLUtil.shutdown(stmt);

    } // end shutdown

  } // end class DescrStringCache

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

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

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

  private long record;             // audit record identifier
  private java.util.Date when;     // the date/time of this audit event
  private int type;                // the audit event type
  private int uid;                 // the user ID
  private int cid;                 // the community ID
  private String ip;               // the IP address of the user
  private String[] data;           // the data values associated with the record
  private String descr = null;     // audit record description
  private String uname = null;     // user name of user
  private String commname = null;  // name of community

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

  public AuditRecord(int type, int uid, String ip, int cid, String data1, String data2, String data3,
		     String data4)
  {
    setBaseData(type,uid,ip);
    this.cid = cid;
    setData(data1,data2,data3,data4);

  } // end constructor

  public AuditRecord(int type, int uid, String ip, int cid, String data1, String data2, String data3)
  {
    setBaseData(type,uid,ip);
    this.cid = cid;
    setData(data1,data2,data3,null);

  } // end constructor

  public AuditRecord(int type, int uid, String ip, int cid, String data1, String data2)
  {
    setBaseData(type,uid,ip);
    this.cid = cid;
    setData(data1,data2,null,null);

  } // end constructor

  public AuditRecord(int type, int uid, String ip, int cid, String data1)
  {
    setBaseData(type,uid,ip);
    this.cid = cid;
    setData(data1,null,null,null);

  } // end constructor

  public AuditRecord(int type, int uid, String ip, int cid)
  {
    setBaseData(type,uid,ip);
    this.cid = cid;
    setData(null,null,null,null);

  } // end constructor

  public AuditRecord(int type, int uid, String ip, String data1, String data2, String data3, String data4)
  {
    setBaseData(type,uid,ip);
    this.cid = 0;
    setData(data1,data2,data3,data4);

  } // end constructor

  public AuditRecord(int type, int uid, String ip, String data1, String data2, String data3)
  {
    setBaseData(type,uid,ip);
    this.cid = 0;
    setData(data1,data2,data3,null);

  } // end constructor

  public AuditRecord(int type, int uid, String ip, String data1, String data2)
  {
    setBaseData(type,uid,ip);
    this.cid = 0;
    setData(data1,data2,null,null);

  } // end constructor

  public AuditRecord(int type, int uid, String ip, String data1)
  {
    setBaseData(type,uid,ip);
    this.cid = 0;
    setData(data1,null,null,null);

  } // end constructor

  public AuditRecord(int type, int uid, String ip)
  {
    setBaseData(type,uid,ip);
    this.cid = 0;
    setData(null,null,null,null);

  } // end constructor

  protected AuditRecord(ResultSet rs, DescrStringCache cache) throws SQLException, DataException
  {
    record = rs.getLong("record");
    when = SQLUtil.getFullDateTime(rs,"on_date");
    type = rs.getInt("event");
    uid = rs.getInt("uid");
    cid = rs.getInt("sigid");
    ip = rs.getString("ip");
    data = new String[DATA_COUNT];
    data[0] = rs.getString("data1");
    data[1] = rs.getString("data2");
    data[2] = rs.getString("data3");
    data[3] = rs.getString("data4");
    descr = cache.getDescription(type);
    uname = cache.getUserName(uid);
    commname = cache.getCommunityName(cid);

  } // end constructor

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

  private final void setBaseData(int type, int uid, String ip)
  {
    this.record = 0;
    this.when = null;
    this.type = type;
    this.uid = uid;
    this.ip = ip;

  } // end setBaseData

  private final void setData(String data1, String data2, String data3, String data4)
  {
    data = new String[DATA_COUNT];
    data[0] = data1;
    data[1] = data2;
    data[2] = data3;
    data[3] = data4;

  } // end setData

  /*--------------------------------------------------------------------------------
   * Implementations from interface AuditData
   *--------------------------------------------------------------------------------
   */

  public final long getRecord()
  {
    return record;

  } // end getRecord

  public final java.util.Date getDateTime()
  {
    return when;

  } // end getDateTime

  public final int getType()
  {
    return type;

  } // end getType

  public final int getUID()
  {
    return uid;

  } // end getType

  public final int getCommunityID()
  {
    return cid;

  } // end getCommunityID

  public final String getIPAddress()
  {
    return ip;

  } // end getIPAddress

  public final String getData(int ndx)
  {
    return data[ndx];

  } // end getData

  public final String getDescription()
  {
    return descr;

  } // end getDescription

  public final String getUserName()
  {
    return uname;

  } // end getUserName

  public final String getCommunityName()
  {
    return commname;

  } // end getCommunityName

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

  public void store(Connection conn) throws SQLException
  {
    if (record!=0)
      throw new InternalStateError("audit record " + String.valueOf(record) + " already stored!");

    Statement stmt = null;

    try
    { // begin updating the database
      stmt = conn.createStatement();
      stmt.executeUpdate("LOCK TABLES audit WRITE;");
      try
      { // attempt to insert a record into the audit table
	StringBuffer sql = new StringBuffer("INSERT INTO audit (on_date, event, uid, sigid, ip, data1, data2, "
					    + "data3, data4) VALUES ('");
	java.util.Date now = new java.util.Date();
	sql.append(SQLUtil.encodeDate(now)).append("', ").append(type).append(", ").append(uid).append(", ");
	sql.append(cid).append(", '").append(SQLUtil.encodeString(ip)).append("', ");
	sql.append(SQLUtil.encodeStringArg(data[0])).append(", ").append(SQLUtil.encodeStringArg(data[1]));
	sql.append(", ").append(SQLUtil.encodeStringArg(data[2])).append(", ");
	sql.append(SQLUtil.encodeStringArg(data[3])).append(");");

	stmt.executeUpdate(sql.toString());

	ResultSet rs = stmt.executeQuery("SELECT LAST_INSERT_ID();");
	if (!(rs.next()))
	  throw new InternalStateError("AuditRecord.store bogus query - must return at least 1 row!");
	record = rs.getLong(1);
	when = now;

      } // end try
      finally
      { // make sure we unlock the tables before we go
	SQLUtil.unlockTables(conn);

      } // end finally

    } // end try
    finally
    { // close down to conserve resources
      SQLUtil.shutdown(stmt);

    } // end finally

  } // end store

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

  public static List getAuditRecords(Connection conn, int cid, int offset, int count)
      throws SQLException, DataException
  {
    ArrayList rc = new ArrayList();
    DescrStringCache cache = new DescrStringCache(conn);
    Statement stmt = null;

    try
    { // query for audit records
      stmt = conn.createStatement();
      StringBuffer sql = new StringBuffer("SELECT * FROM audit");
      if (cid>0)
	sql.append(" WHERE sigid = ").append(cid);
      sql.append(" ORDER BY on_date DESC LIMIT ").append(offset).append(", ").append(count).append(';');
      ResultSet rs = stmt.executeQuery(sql.toString());

      while (rs.next())
      { // load the results
	AuditData dta = new AuditRecord(rs,cache);
	rc.add(dta);

      } // end while

    } // end try
    finally
    { // shut down things
      SQLUtil.shutdown(stmt);
      cache.shutdown();

    } // end finally

    return Collections.unmodifiableList(rc);

  } // end getAuditRecords

  public static int getAuditRecordCount(Connection conn, int cid) throws SQLException
  {
    Statement stmt = null;

    try
    { // look up the count
      stmt = conn.createStatement();
      StringBuffer sql = new StringBuffer("SELECT COUNT(*) FROM audit");
      if (cid>0)
	sql.append(" WHERE sigid = ").append(cid);
      sql.append(';');
      ResultSet rs = stmt.executeQuery(sql.toString());
      if (!(rs.next()))
	throw new InternalStateError("query failure on getAuditRecordCount");
      return rs.getInt(1);

    } // end try
    finally
    { // close statement to conserve resources
      SQLUtil.shutdown(stmt);

    } // end finally

  } // end getAuditRecordCount

  public static void store(Connection conn, AuditRecord ar)
  {
    try
    { // save off the audit record
      if ((ar!=null) && (conn!=null))
	ar.store(conn);

    } // end try
    catch (SQLException e)
    { // we couldn't store the audit record!
      logger.error("DB error saving audit record: " + e.getMessage(),e);

    } // end catch

  } // end store

} // end class AuditRecord
