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

import java.sql.*;
import java.util.*;
import org.apache.log4j.*;
import com.silverwrist.util.AnyCharMatcher;
import com.silverwrist.util.StringUtil;

/**
 * Utility functions commonly used for passing string values to SQL commands, and interpreting the results.
 *
 * @author Eric J. Bowersox &lt;erbo@silcom.com&gt;
 * @version X
 * @see com.silverwrist.util.StringUtil
 */
public class SQLUtil
{
  /*--------------------------------------------------------------------------------
   * Static data members
   *--------------------------------------------------------------------------------
   */

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

  private static final String SQL_WILDCARD_CHARS = "%_'";

  // used to convert dates and times to UTC for sending to SQL
  private static SimpleTimeZone utc = new SimpleTimeZone(0,"UTC");

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

  /**
   * Converts a date-and-time string (an SQL DATETIME value) to a standard Java date.
   *
   * @param dstr The date-and-time string to convert, presumed to be in UTC.
   * @return The converted date and time.
   * @exception java.sql.SQLException The date and time string was in an invalid format.
   */
  private static final java.util.Date convertDateTimeString(String dstr) throws SQLException
  {
    if (dstr==null)
      return null;  // null values are the same

    try
    { // do almost the reverse process of formatting it into a string
      GregorianCalendar cal = new GregorianCalendar(utc);
      cal.set(Calendar.YEAR,Integer.parseInt(dstr.substring(0,4)));
      cal.set(Calendar.MONTH,Integer.parseInt(dstr.substring(5,7)) - 1 + Calendar.JANUARY);
      cal.set(Calendar.DAY_OF_MONTH,Integer.parseInt(dstr.substring(8,10)));
      cal.set(Calendar.HOUR_OF_DAY,Integer.parseInt(dstr.substring(11,13)));
      cal.set(Calendar.MINUTE,Integer.parseInt(dstr.substring(14,16)));
      cal.set(Calendar.SECOND,Integer.parseInt(dstr.substring(17,19)));
      return cal.getTime();

    } // end try
    catch (NumberFormatException e)
    { // the NumberFormatException becomes an SQLException
      throw new SQLException("invalid DATETIME field format");

    } // end catch

  } // end convertDateString

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

  /**
   * Performs SQL encoding of an arbitrary string.  When a string is SQL-encoded, all single-quote
   * characters are doubled, and all other characters are left untouched.
   *
   * @param str The string to be SQL-encoded.  May be <CODE>null</CODE>.
   * @return The SQL-encoded equivalent of <CODE>str</CODE>.  If <CODE>str</CODE> is
   *         <CODE>null</CODE>, returns <CODE>null</CODE>.
   * @see com.silverwrist.util.StringUtil#encodeStringSQL(java.lang.String)
   */
  public static final String encodeString(String str)
  {
    return StringUtil.encodeStringSQL(str);

  } // end encodeString

  /**
   * Performs SQL encoding of an arbitrary string, enclosing it in quotes, so that it may be passed as an
   * argument in an SQL statement.  When a string is SQL-encoded, all single-quote characters are doubled,
   * and all other characters are left untouched.
   *
   * @param str The string to be SQL-encoded.  May be <CODE>null</CODE>.
   * @return The SQL-encoded equivalent of <CODE>str</CODE>, in quotes.  If <CODE>str</CODE> is
   *         <CODE>null</CODE>, returns "NULL".
   * @see com.silverwrist.util.StringUtil#encodeStringSQL(java.lang.String)
   */
  public static final String encodeStringArg(String str)
  {
    if (str==null)
      return "NULL";
    StringBuffer b = new StringBuffer("'");
    b.append(StringUtil.encodeStringSQL(str)).append('\'');
    return b.toString();

  } // end encodeStringArg

  /**
   * Performs SQL-encoding of a string which may contain the SQL wildcard characters, '%' and '_'.  The
   * wildcard characters are escaped with '\', and the single-quote characters are doubled, but all other
   * characters are left untouched.
   *
   * @param str The string to be SQL-encoded.  May be <CODE>null</CODE>.
   * @return The SQL-encoded equivalent of <CODE>str</CODE>.  If <CODE>str</CODE> is
   *         <CODE>null</CODE>, returns <CODE>null</CODE>.
   */
  public static final String encodeStringWildcards(String str)
  {
    if (str==null)
      return null;  // safety feature
    AnyCharMatcher nhc = new AnyCharMatcher(SQL_WILDCARD_CHARS);
    int ndx = nhc.get(str);
    if (ndx<0)
      return str;   // trivial short-circuit case
    StringBuffer buf = new StringBuffer();
    while (ndx>=0)
    { // append the matched "head" and then the encoded character
      if (ndx>0)
	buf.append(str.substring(0,ndx));
      switch (str.charAt(ndx++))
      {
	case '%':
	  buf.append("\\%");
	  break;

	case '_':
	  buf.append("\\_");
	  break;

	case '\'':
	  buf.append("\'\'");
	  break;

      } // end switch

      if (ndx==str.length())
	return buf.toString();  // munched the entire string - all done!
      str = str.substring(ndx);
      ndx = nhc.get(str);

    } // end while

    buf.append(str);  // append the unmatched tail
    return buf.toString();

  } // end encodeStringWildcards

  /**
   * Encodes a date as an SQL date/time string, expressed in UTC.
   *
   * @param d The date to be encoded.
   * @return The string equivalent of that date.
   */
  public static final String encodeDate(java.util.Date d)
  {
    // Break down the date as a UTC value.
    GregorianCalendar cal = new GregorianCalendar(utc);
    cal.setTime(d);

    // Create the two string buffers converting the date.
    StringBuffer rc = new StringBuffer();
    StringBuffer conv = new StringBuffer();
    String c;
    
    // Encode the year first.
    conv.append("0000").append(cal.get(Calendar.YEAR));
    c = conv.toString();
    rc.append(c.substring(c.length()-4)).append('-');

    // Now the month...
    conv.setLength(0);
    conv.append("00").append(cal.get(Calendar.MONTH) - Calendar.JANUARY + 1);
    c = conv.toString();
    rc.append(c.substring(c.length()-2)).append('-');

    // And the day...
    conv.setLength(0);
    conv.append("00").append(cal.get(Calendar.DAY_OF_MONTH));
    c = conv.toString();
    rc.append(c.substring(c.length()-2)).append(' ');

    // And the hour...
    conv.setLength(0);
    conv.append("00").append(cal.get(Calendar.HOUR_OF_DAY));
    c = conv.toString();
    rc.append(c.substring(c.length()-2)).append(':');

    // And the minute...
    conv.setLength(0);
    conv.append("00").append(cal.get(Calendar.MINUTE));
    c = conv.toString();
    rc.append(c.substring(c.length()-2)).append(':');

    // And the second...
    conv.setLength(0);
    conv.append("00").append(cal.get(Calendar.SECOND));
    c = conv.toString();
    rc.append(c.substring(c.length()-2));

    // This is the resulting date/time value.
    return rc.toString();

  } // end encodeDate

  /**
   * Returns the value of a DATETIME column in the current row of an SQL result set, formatted as a date.
   *
   * @param rs The result set to look in.
   * @param column The name of the column to be returned.
   * @return The value of the specified column, expressed as a date.
   * @exception java.sql.SQLException If the column could not be retrieved or converted.
   */
  public static final java.util.Date getFullDateTime(ResultSet rs, String column) throws SQLException
  {
    return convertDateTimeString(rs.getString(column));

  } // end getFullDateTime

  /**
   * Returns the value of a DATETIME column in the current row of an SQL result set, formatted as a date.
   *
   * @param rs The result set to look in.
   * @param column The 1-based index of the column to be returned.
   * @return The value of the specified column, expressed as a date.
   * @exception java.sql.SQLException If the column could not be retrieved or converted.
   */
  public static final java.util.Date getFullDateTime(ResultSet rs, int column) throws SQLException
  {
    return convertDateTimeString(rs.getString(column));

  } // end getFullDateTime

  public static final void shutdown(ResultSet rs)
  {
    try
    { // close the ResultSet
      if (rs!=null)
	rs.close();

    } // end try
    catch (SQLException e)
    { // ignore any SQL errors
    } // end catch

  } // end shutdown

  public static final void shutdown(Statement stmt)
  {
    try
    { // close the Statement
      if (stmt!=null)
	stmt.close();

    } // end try
    catch (SQLException e)
    { // ignore any SQL errors
    } // end catch

  } // end shutdown

  public static final void unlockTables(Connection conn)
  {
    try
    { // do the update
      Statement stmt = conn.createStatement();
      stmt.executeUpdate("UNLOCK TABLES;");
      shutdown(stmt);

    } // end try
    catch (SQLException e)
    { // warn if there was an error here
      logger.warn("DB error in unlockTables()",e);

    } // end catch

  } // end unlockTables

} // end class SQLUtil
