/**
 *  '$RCSfile$'
 *    Purpose: A class that gets Accession Number, check for uniqueness
 *             and register it into db
 *  Copyright: 2000 Regents of the University of California and the
 *             National Center for Ecological Analysis and Synthesis
 *    Authors: Jivka Bojilova, Matt Jones
 *
 *   '$Author$'
 *     '$Date$'
 * '$Revision$'
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package edu.ucsb.nceas.metacat;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import edu.ucsb.nceas.metacat.database.DBConnection;
import edu.ucsb.nceas.metacat.database.DBConnectionPool;
import edu.ucsb.nceas.metacat.properties.PropertyService;
import edu.ucsb.nceas.utilities.PropertyNotFoundException;

/**
 * (on insert of XML document)
 * Generates a unique Accession Number or if provided check it
 * for uniqueness and register it into the db connection
 * (on update or delete of XML document)
 * Check for existance of provided Accession Number
 *
 */
public class AccessionNumber  {

  private String sitecode = null;
  private String sep = null;
  private String docid = null;
  private String rev = null;

    /**
	 * Construct an AccessionNumber
	 */
	private AccessionNumber() throws AccessionNumberException {
		try {
			this.sitecode = PropertyService.getProperty("document.sitecode");
			this.sep = PropertyService.getProperty("document.accNumSeparator");
		} catch (PropertyNotFoundException pnfe) {
			throw new AccessionNumberException("Could not retrieve property "
					+ "in constructor: " + pnfe.getMessage());
		}
	}

  /**
	 * NEW - WHEN CLIENT ALWAYS PROVIDE ACCESSION NUMBER INCLUDING REV IN IT
	 * Construct an AccessionNumber
	 * 
	 * @param conn
	 *            the db connection to read Accession number from
	 * @param accnum
	 *            the accession number to be checked for validness
	 */
  public AccessionNumber (String accnum, String action)
           throws AccessionNumberException, SQLException, NumberFormatException
 {
    this();

    this.rev = null;
    this.docid = accnum;
    if ( accnum != null ) {
      int firstIndex = accnum.indexOf(this.sep);
      int lastIndex = accnum.lastIndexOf(this.sep);
      if ( firstIndex != lastIndex ) {
        //this docid contains a revision number
        this.rev = accnum.substring(lastIndex + 1);
        this.docid = accnum.substring(0, lastIndex);
      }
    }

    // INSERT
    if ( action.equals("INSERT")) {

        if(rev != null){
            try {
                Integer.parseInt(rev);
            }
            catch (java.lang.NumberFormatException e) {
                throw new AccessionNumberException(
                    "Revision number is required");
            }
        }

      // check accession number for validness
      if ( docid == null ) {
        throw new AccessionNumberException("Accession number is required");

      // rev is not provided; throw an exception to prevent the insertion
      } else if ( rev == null ) {
        throw new AccessionNumberException
                  ("Revision number is required");

      // docid is used; throw an exception to prevent the insertion
      } else if ( accNumberUsed(docid) ) {
        throw new AccessionNumberException
                  ("Accession number " + docid + " is already in use");

      // rev is <> 1; throw an exception to prevent the insertion
      } /*else if ( !rev.equals("1")) {
        throw new AccessionNumberException("Revision number must be 1");
      }*/

    // UPDATE or DELETE
    } else if ( action.equals("UPDATE") || action.equals("DELETE")) {
      String l_rev = "";

      int reversionNumber = 1;

      if(rev != null){
          try{
              reversionNumber = Integer.parseInt(rev);
          } catch (java.lang.NumberFormatException e){
              throw new AccessionNumberException(
                      "Revision number is required");
          }
      }

      // Accession# is not provided; throw an exception to prevent the action
      if ( docid == null ) {
        throw new AccessionNumberException("Accession number is required");

      // rev is not provided; throw an exception to prevent the action
      } else if ( rev == null ) {
        throw new AccessionNumberException
                  ("Revision number is required");

      // Accession# is not current (not in xml_documents or xml_revisions); throw an exception
      } else if ( !accNumberIsCurrent(docid) ) {
        throw new AccessionNumberException
                  ("Document not found for Accession number " + docid);

      //Revision number is less than or equal the recent one; throw a exception
      } else if ( action.equals("UPDATE") &&
                  reversionNumber <= getLastRevisionNumber(docid) ) {
        throw new AccessionNumberException
                 ("Next revision number can't be less than or equal to "
                                              + getLastRevisionNumber(docid));

      // Revision number is not the recent one; throw an exception
      } else if ( action.equals("DELETE") &&
                  !rev.equals(l_rev = getLastRevision(docid)) ) {
        throw new AccessionNumberException
                  ("Last revision number is "+ l_rev);
      }
    }
  }

  /** check for existence of Accesssion Number xml_acc_numbers table */
  public static boolean accNumberUsed ( String accNumber )
                  throws SQLException {

    boolean hasAccNumber = false;
    DBConnection conn = null;
    int serialNumber = -1;
    PreparedStatement pstmt = null;
    ResultSet rs = null;

    try {
      
      //check out DBConnection
      conn=DBConnectionPool.getDBConnection("AccessionNumber.accNumberUsed");
      serialNumber=conn.getCheckOutSerialNumber();
      pstmt = conn.prepareStatement(
                "SELECT 'x' FROM xml_documents " +
                "WHERE docid = ? " +
                "UNION " +
                "SELECT 'x' FROM xml_revisions " +
                "WHERE docid = ?");
      pstmt.setString(1,accNumber);
      pstmt.setString(2,accNumber);
      pstmt.execute();
      rs = pstmt.getResultSet();
      hasAccNumber = rs.next();
      //pstmt.close();

    } catch (SQLException e) {
      throw new SQLException
      ("Error on AccessionNumber.accNumberUsed(accNumber): " + e.getMessage());
    }
    finally
    {
       try {
           if(rs != null) {
               rs.close();
           }
           if(pstmt != null) {
               pstmt.close();
           }
       } finally {
           DBConnectionPool.returnDBConnection(conn, serialNumber);
       }
      
    }

    return hasAccNumber;
  }

  // Check for existence of Accesssion Number in xml_documents or xml_revisions
  // table.  We check xml_revisions because document may have been deleted, which 
  // will remove it from xml_documents, but a revision still exists.
  private boolean accNumberIsCurrent(String accNumber) throws SQLException {

    boolean hasCurrentAccNumber = false;
    DBConnection conn = null;
    int serialNumber = -1;
    ResultSet rs = null;
    PreparedStatement pstmt = null;
    try {
     
      //check out DBConnection
      conn=DBConnectionPool.getDBConnection("AccessionNumber.accNumberIsCurre");
      serialNumber=conn.getCheckOutSerialNumber();
      pstmt = conn.prepareStatement(
                "SELECT 'x' FROM xml_documents " +
                "WHERE docid = ?");
      pstmt.setString(1, accNumber);
      pstmt.execute();
      rs = pstmt.getResultSet();
      hasCurrentAccNumber = rs.next();
      if(!hasCurrentAccNumber)
      {
        //need to look xml_revision table;
        pstmt = conn.prepareStatement(
            "SELECT 'x' FROM xml_revisions " +
            "WHERE docid = ?");
        pstmt.setString(1, accNumber);
        pstmt.execute();
        rs = pstmt.getResultSet();
        hasCurrentAccNumber = rs.next();
      }
      //pstmt.close();

    } catch (SQLException e) {
      throw new SQLException(
          "Error on AccessionNumber.accNumberIsCurrent(String accNumber): " +
          e.getMessage());
    }
    finally
    {
       
       try {
           if(rs != null) {
               rs.close();
           }
           if(pstmt != null) {
               pstmt.close();
           }
       } finally {
           DBConnectionPool.returnDBConnection(conn, serialNumber);
       }
      
    }

    return hasCurrentAccNumber;
  }

  // get the recent revision number for docid
  private String getLastRevision(String docid) throws SQLException
  {
    String rev = "";
    DBConnection conn = null;
    int serialNumber = -1;
    PreparedStatement pstmt = null;
    ResultSet rs = null;
    try {
      
      //check out DBConnection
      conn=DBConnectionPool.getDBConnection("AccessionNumber.getLastRevision");
      serialNumber=conn.getCheckOutSerialNumber();
      pstmt = conn.prepareStatement
              ("SELECT rev FROM xml_documents WHERE docid = ?");
      pstmt.setString(1, docid);
      pstmt.execute();

      rs = pstmt.getResultSet();
      boolean hasRow = rs.next();
      rev = rs.getString(1);
      //pstmt.close();

    } catch (SQLException e) {
      throw new SQLException(
      "Error on AccessionNumber.getLastRevision(): " + e.getMessage());
    }
    finally
    {
        try {
            if(rs != null) {
                rs.close();
            }
            if(pstmt != null) {
                pstmt.close();
            }
        } finally {
            DBConnectionPool.returnDBConnection(conn,serialNumber);
        }
      
    }

    return rev;
  }

  /**
    * Get last revision number from database for a docid
    * The return value is integer because we want compare it to there new one
    * @param docid <sitecode>.<uniqueid> part of Accession Number
    */
  private int getLastRevisionNumber(String docId) throws SQLException
  {
    int rev = 1;
    DBConnection conn =null;
    int serialNumber = -1;
    PreparedStatement pStmt = null;
    ResultSet rs = null;
    try {
      
      //check out DBConnection
      conn=DBConnectionPool.getDBConnection("AccessionNumber.getLastRevisionN");
      serialNumber=conn.getCheckOutSerialNumber();

      pStmt = conn.prepareStatement
              ("SELECT rev FROM xml_documents WHERE docid = ? ");
      pStmt.setString(1, docId);
      pStmt.execute();

      rs = pStmt.getResultSet();
      boolean hasRow = rs.next();
      if(hasRow)
      {
        rev = rs.getInt(1);
      }     
      //pStmt.close();

    } catch (SQLException e) {
      throw new SQLException(
      "Error on AccessionNumber.getLastRevision(): " + e.getMessage());
    }
    finally
    {
        try {
            if(rs != null) {
                rs.close();
            }
            if(pStmt != null) {
                pStmt.close();
            }
        } finally {
            DBConnectionPool.returnDBConnection(conn,serialNumber);
        }
      
    }
    return rev;
  }

  /**
   * returns the docid encoded in this accession number
   */
  public String getDocid() {

    return this.docid;
  }

  /**
   * returns the revision number encoded in this accession number
   */
  public String getRev()
  {
    return this.rev;
  }

}
