/** 
 * Project    : KANT (LMT)
 * Source Name: Session.java
 * Copyright  : Copyright (c) LTI, Carnegie Mellon University
 * Description: Class for user
 * @version     1.0 (created by jko - 10/01/01)
 **/

package lmt;

import java.sql.*;
import java.util.*;
import java.text.SimpleDateFormat;

public class Session {
  public static int EXTRA_TIME = 15*1000; // 15 sec extra time for session checking 
								  
  String	session_id;
  String	lockType;
  String	timestamp;
  int		timeout;
  boolean	hasLock;
  SQLConnection con;
  //	Term	term;
	
  /** constructor 
   **/
  public Session(String s_id, SQLConnection con) {
    this.con = con;
    session_id = s_id;
    lockType = "X";
    timestamp = "";
    hasLock = false;
    timeout = 0;
    //		term = new Term();
  }

  // setters & getters
  public String getSessionID() {
    return session_id;
  }	
  public void setSessionID(String s_id) {
    session_id = s_id;
  }	
  public String getLockType() {
    return lockType;
  }	
  public void setLockType(String lockType) {
    if ( lockType == null )
      lockType = "";
    this.lockType = lockType;
  }	
  public String getTimestamp() {
    return timestamp;
  }	
  public void setTimestamp(String timestamp) {
    if ( timestamp == null )
      timestamp = "";
    this.timestamp = timestamp;
  }	
  public void setTimeout(int timeout) {
    this.timeout = timeout;
  }
  /*	public void setTerm(Term t) {
    this.term = t;
    }
  */	
  /** check whether the term has lock for another session 
   * @return integer representing the result of operation
   **/	
  public int checkLock(Term term) {
    int ret = 0;

    if ( timeout == 0 )
      return Constants.NO_LOCK;	
		
    // start transaction
    con.setAutoCommit(false);	
    con.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
		
    // check the lock 
    String sql = "SELECT LOCK_TYPE, TIMESTAMP FROM LMT_LOCKS ";
    sql += "WHERE TERM_ID='" + term.getTermID() + "' for update nowait";
    Debug.debug("-- checkLock: " + sql);		
			
    try {						
      SQLConnection.Result r = con.query(sql);
      // if the term does not have a lock
      if (r == null || !r.hasData()){
	// remove the lock caused by "for update nowait"
	con.setAutoCommit(true);
	if (r!=null) r.close();
	return Constants.NO_LOCK;	
      }
      // if the term has a lock
      setLockType(r.rs.getString(1));				 
      setTimestamp(r.rs.getString(2));	
      // change the timestamp field because its type is like this 
      // 2002-01-10 13:46:55.0
      timestamp = timestamp.substring(0, timestamp.length()-2);
      //Debug.debug("-- timestamp: " + getTimestamp());			
      if (r!=null) r.close();
    } catch (SQLException e) {
      System.err.println("ERROR: " + e);
      con.setAutoCommit(true);
      return Constants.FAIL;
    }	
				
    // check timestamp of the lock
    if ( validTimestamp() ) {
      Debug.debug("++ checkLock : term locked");
      ret = Constants.TABLELOCKED;	
    } else {
      // the lock is old. replace it with the new lock.
      sql = "UPDATE LMT_LOCKS SET ";
      sql += "session_id='" + session_id +"',";
      sql += "timestamp = SYSDATE ";
      sql += "WHERE TERM_ID='" + term.getTermID() + "'";
      Debug.debug("-- checkLock update the previous lock: " + sql);	
      if ( con.update(sql) == Constants.SUCCESS)
	ret = Constants.LOCK_EXPIRED;
      else 
	ret = Constants.FAIL;	
    }
    // remove the lock caused by "for update nowait" and end transaction
    con.commit();	
    con.setAutoCommit(true);	
    return ret;
  }
	
  public int getLock(Term term) {
    if ( timeout == 0 )
      return Constants.SUCCESS;	

    String sql = "INSERT INTO LMT_LOCKS VALUES (";
    sql += "'" + session_id + "',";
    sql += "'" + term.getTermID() + "',";
    sql += "SYSDATE,";
    sql += "'" + lockType + "')";
    Debug.debug("-- getLock: " + sql);		
    int ret = con.update(sql);

    if ( ret == Constants.SUCCESS ) 
      hasLock = true;

    return ret;
  }

  // release lock of this session
  public boolean releaseLock() {
    if ( timeout == 0 )
      return true;	
		
    if ( !hasLock || session_id.length() == 0 ) 
      return false;
    String sql = "DELETE FROM LMT_LOCKS WHERE session_id = '" + session_id + "'";
    Debug.debug("-- releaseLock: " + sql);		
    int ret = con.updateQuery(sql);
    if ( ret <= 1 )
      hasLock = false;
    return (!hasLock);
  }
	
  boolean validTimestamp() {
    String strCurTime = "";

    // get the current time from the Oracle 
    // we don't use the local time because it could be wrong
    String sql = "SELECT to_char(sysdate, 'YYYY-MM-DD HH24:MI:SS') NOW from dual";
    try {						
      SQLConnection.Result r = con.query(sql);
      if (r == null || !r.hasData()){
	// we cannot get the time, so we assume timestamp is valid
	if ( r !=null) r.close();
	return true;
      }
      strCurTime = r.rs.getString(1);				 
      if (r!=null) r.close();
      Debug.debug("-- strCurTime: " + strCurTime);
    } catch (SQLException e) {
      Debug.debug("++ validTimestamp error");
      System.err.println("ERROR: " + e);
      return true;
    }			
		
    // check the time
    SimpleDateFormat df = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss");
    java.util.Date dtSavedTime;
    java.util.Date dtCurTime;
    try {
      dtSavedTime = df.parse(timestamp);
      dtCurTime = df.parse(strCurTime);
    } catch (Exception e) {
      Debug.debug("++ validTimestamp: parse error");
      //System.err.println("ERROR: " + e);			
      return true;
    }
    // convert time into the number, which means milliseconds 
    long lSavedTime = dtSavedTime.getTime(); 
    long lCurTime = dtCurTime.getTime();
 
    Debug.debug("-- savedTime : " + lSavedTime);
    Debug.debug("-- curTime   : " + lCurTime);
    Debug.debug("-- timeout   : " + timeout*60*1000);
    Debug.debug("-- extra time: " + EXTRA_TIME);
		
    // two numbers are compared based on the timeout set by the user in the config file
    // extra time is used to give a little more time to the session
    // because if the term is chaned, the user will have message dialog
    // we need time for user to handle the dialog
    return ( lSavedTime + timeout*60*1000 + EXTRA_TIME >= lCurTime );
  }

  public int renewTimestamp(Term term) {
    if ( timeout == 0 )
      return Constants.SUCCESS;	
		
    String sql = "UPDATE LMT_LOCKS SET TIMESTAMP=SYSDATE WHERE SESSION_ID='" + session_id +"'";
    int ret = con.update(sql);
    if (ret == Constants.FAIL) {
      // if current session info is deleted by admin, insert a new info
      ret = getLock(term);
    }
    return ret;	
  }
	
  public void makeQueryForUpdateTimestamp(Vector queryVector) {
    String sql = "UPDATE LMT_LOCKS SET TIMESTAMP=SYSDATE WHERE SESSION_ID='" + session_id +"'";
    queryVector.addElement(sql);
  }
}	
