/*
 Copyright (c) 2012, Oracle and/or its affiliates. All rights
 reserved.
 
 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; version 2 of
 the License.
 
 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., 51 Franklin St, Fifth Floor, Boston, MA
 02110-1301  USA
 */

/*global unified_debug */

"use strict";

var     udebug     = unified_debug.getLogger("Transaction.js");
var userContext    = require("./UserContext.js");

/** Transaction is implemented as a state machine. 
 * States are:
 * idle: user has not called begin
 * active user has called begin
 * rollback_only user has called begin and setRollbackOnly
 * 
 * Functions that cause state transitions are:
 * begin: begin a transaction if not already begun
 * rollback: rollback the current transaction
 * setRollbackOnly: allow rollback but not commit
 * commit: commit the current transaction
 * rollback: roll back the current transaction
 * 
 * Functions that return status are:
 * getRollbackOnly returns whether the state is rollback only
 * isActive returns whether the state is active
 * 
 * There are only three valid transitions:
 * 
 * (Idle) begin -> (Active) commit -> (Idle)
 * (Idle) begin -> (Active) rollback -> (Idle)
 * (Idle) begin -> (Active) setRollbackOnly -> (RollbackOnly) rollback -> (Idle)
 */

function Idle() {
  this.name = 'Idle';
}
var idle = new Idle();

function Active() {
  this.name = 'Active';
}
var active = new Active();

function RollbackOnly() {
  this.name = 'RollbackOnly';
}
var rollbackOnly = new RollbackOnly();

/** An error may have occurred. If there is a callback defined, signal the error via the callback,
 * and return the new transaction state. The state should remain the same (the current state
 * is passed in the function).
 * If no callback is defined with an error, throw the error (and remain in the current state).
 */
var callbackErrOrThrow = function(err, user_arguments) {
  var promise = new userContext.Promise();
  // signal the error via the promise
  if (err) {
    promise.reject(err);
  } else {
    promise.fulfill();
  }
   if (typeof(user_arguments[0]) === 'function') {
    var return_arguments = [];
    var i;
    for (i = 1; i < user_arguments.length; ++i) {
      return_arguments[i] = user_arguments[i];
    }
    return_arguments[0] = err;
    user_arguments[0].apply(null, return_arguments);
  }
 return promise;
};

Idle.prototype.begin = function(session, user_arguments) {
  udebug.log('Idle begin');
  // notify dbSession if they are interested
  if (typeof(session.dbSession.begin) === 'function') {
    session.dbSession.begin();
  }
  session.tx.setState(active);
  // no error
  return callbackErrOrThrow(null, user_arguments);
};

Idle.prototype.commit = function(session, user_arguments) {
  udebug.log('Idle commit');
  var err = new Error('Illegal state: Idle cannot commit.');
  err.sqlstate = '25000';
  return callbackErrOrThrow(err, user_arguments);
};

Idle.prototype.rollback = function(session, user_arguments) {
  udebug.log('Idle rollback');
  var err = new Error('Illegal state: Idle cannot rollback.');
  err.sqlstate = '25000';
  return callbackErrOrThrow(err, user_arguments);
};

Idle.prototype.isActive = function() {
  udebug.log_detail('Idle isActive');
  return false;
};

Idle.prototype.setRollbackOnly = function(session, user_arguments) {
  udebug.log('Idle setRollbackOnly');
};

Idle.prototype.getRollbackOnly = function() {
  udebug.log('Idle getRollbackOnly');
  return false;
};

Active.prototype.begin = function(session, user_arguments) {
  udebug.log('Active begin');
  var err = new Error('Illegal state: Active cannot begin.');
  err.sqlstate = '25000';
  return callbackErrOrThrow(err, user_arguments);
};

Active.prototype.commit = function(session, user_arguments) {
  udebug.log('Active commit');
  var context = new userContext.UserContext(user_arguments, 1, 1, session, session.sessionFactory);
  // delegate to context's commit for execution which will change the state to idle
  return context.commit();
};

Active.prototype.rollback = function(session, user_arguments) {
  udebug.log('Active rollback');
  var context = new userContext.UserContext(user_arguments, 1, 1, session, session.sessionFactory);
  // delegate to context's rollback for execution which will change the state to idle
  return context.rollback();
};

Active.prototype.isActive = function() {
  udebug.log_detail('Active isActive');
  return true;
};

Active.prototype.setRollbackOnly = function(session) {
  udebug.log('Active setRollbackOnly');
  session.tx.setState(rollbackOnly);
};

Active.prototype.getRollbackOnly = function() {
  udebug.log('Active getRollbackOnly');
  return false;
};

RollbackOnly.prototype.begin = function(session, user_arguments) {
  udebug.log('RollbackOnly begin');
  var err = new Error('Illegal state: RollbackOnly cannot begin.');
  err.sqlstate = '25000';
  return callbackErrOrThrow(err, user_arguments);
};

RollbackOnly.prototype.commit = function(session, user_arguments) {
  udebug.log('RollbackOnly commit');
  var err = new Error('Illegal state: RollbackOnly cannot commit.');
  err.sqlstate = '25000';
  return callbackErrOrThrow(err, user_arguments);
};

RollbackOnly.prototype.rollback = function(session, user_arguments) {
  udebug.log('RollbackOnly rollback');
  var context = new userContext.UserContext(user_arguments, 1, 1, session, session.sessionFactory);
  // delegate to context's rollback for execution which will set the state to idle
  return context.rollback();
};

RollbackOnly.prototype.isActive = function() {
  udebug.log_detail('RollbackOnly isActive');
  return true;
};

RollbackOnly.prototype.setRollbackOnly = function() {
  udebug.log('RollbackOnly setRollbackOnly');
};

RollbackOnly.prototype.getRollbackOnly = function() {
  udebug.log('RollbackOnly getRollbackOnly');
  return true;
};

function Transaction(session) {
  this.session = session;
  this.state = idle;
}

// States can be addressed by name in the Transaction object for setState(newState)
Transaction.prototype.idle = idle;
Transaction.prototype.active = active;
Transaction.prototype.rollbackOnly = rollbackOnly;

Transaction.prototype.begin = function() {
  udebug.log('Transaction.begin');
  return this.state.begin(this.session, arguments);
};

Transaction.prototype.commit = function() {
  udebug.log('Transaction.commit');
  return this.state.commit(this.session, arguments);
};

Transaction.prototype.rollback = function() {
  udebug.log('Transaction.rollback');
  return this.state.rollback(this.session, arguments);
};

Transaction.prototype.isActive = function() {
  udebug.log_detail('Transaction.isActive');
  return this.state.isActive();
};

Transaction.prototype.setRollbackOnly = function() {
  udebug.log('Transaction.setRollbackOnly');
  this.state.setRollbackOnly(this.session, arguments);
};

Transaction.prototype.getRollbackOnly = function() {
  udebug.log('Transaction.getRollbackOnly');
  return this.state.getRollbackOnly();
};

/** This function is used by each state to change the state of the transaction.
 * @param newState one of: Idle, Active, RollbackOnly
 */
Transaction.prototype.setState = function(newState) {
  udebug.log('Transaction.setState to', newState.name);
  this.state = newState;
};

exports.Transaction = Transaction;

