'use strict';
const MongoError = require('./error').MongoError;

let TxnState;
let stateMachine;

(() => {
  const NO_TRANSACTION = 'NO_TRANSACTION';
  const STARTING_TRANSACTION = 'STARTING_TRANSACTION';
  const TRANSACTION_IN_PROGRESS = 'TRANSACTION_IN_PROGRESS';
  const TRANSACTION_COMMITTED = 'TRANSACTION_COMMITTED';
  const TRANSACTION_COMMITTED_EMPTY = 'TRANSACTION_COMMITTED_EMPTY';
  const TRANSACTION_ABORTED = 'TRANSACTION_ABORTED';

  TxnState = {
    NO_TRANSACTION,
    STARTING_TRANSACTION,
    TRANSACTION_IN_PROGRESS,
    TRANSACTION_COMMITTED,
    TRANSACTION_COMMITTED_EMPTY,
    TRANSACTION_ABORTED
  };

  stateMachine = {
    [NO_TRANSACTION]: [NO_TRANSACTION, STARTING_TRANSACTION],
    [STARTING_TRANSACTION]: [
      TRANSACTION_IN_PROGRESS,
      TRANSACTION_COMMITTED,
      TRANSACTION_COMMITTED_EMPTY,
      TRANSACTION_ABORTED
    ],
    [TRANSACTION_IN_PROGRESS]: [
      TRANSACTION_IN_PROGRESS,
      TRANSACTION_COMMITTED,
      TRANSACTION_ABORTED
    ],
    [TRANSACTION_COMMITTED]: [
      TRANSACTION_COMMITTED,
      TRANSACTION_COMMITTED_EMPTY,
      STARTING_TRANSACTION,
      NO_TRANSACTION
    ],
    [TRANSACTION_ABORTED]: [STARTING_TRANSACTION, NO_TRANSACTION],
    [TRANSACTION_COMMITTED_EMPTY]: [TRANSACTION_COMMITTED_EMPTY, NO_TRANSACTION]
  };
})();

/**
 * The MongoDB ReadConcern, which allows for control of the consistency and isolation properties
 * of the data read from replica sets and replica set shards.
 * @typedef {Object} ReadConcern
 * @property {'local'|'available'|'majority'|'linearizable'|'snapshot'} level The readConcern Level
 * @see https://docs.mongodb.com/manual/reference/read-concern/
 */

/**
 * A MongoDB WriteConcern, which describes the level of acknowledgement
 * requested from MongoDB for write operations.
 * @typedef {Object} WriteConcern
 * @property {number|'majority'|string} [w=1] requests acknowledgement that the write operation has
 * propagated to a specified number of mongod hosts
 * @property {boolean} [j=false] requests acknowledgement from MongoDB that the write operation has
 * been written to the journal
 * @property {number} [wtimeout] a time limit, in milliseconds, for the write concern
 * @see https://docs.mongodb.com/manual/reference/write-concern/
 */

/**
 * Configuration options for a transaction.
 * @typedef {Object} TransactionOptions
 * @property {ReadConcern} [readConcern] A default read concern for commands in this transaction
 * @property {WriteConcern} [writeConcern] A default writeConcern for commands in this transaction
 * @property {ReadPreference} [readPreference] A default read preference for commands in this transaction
 */

/**
 * A class maintaining state related to a server transaction. Internal Only
 * @ignore
 */
class Transaction {
  /**
   * Create a transaction
   *
   * @ignore
   * @param {TransactionOptions} [options] Optional settings
   */
  constructor(options) {
    options = options || {};

    this.state = TxnState.NO_TRANSACTION;
    this.options = {};

    if (options.writeConcern || typeof options.w !== 'undefined') {
      const w = options.writeConcern ? options.writeConcern.w : options.w;
      if (w <= 0) {
        throw new MongoError('Transactions do not support unacknowledged write concern');
      }

      this.options.writeConcern = options.writeConcern ? options.writeConcern : { w: options.w };
    }

    if (options.readConcern) this.options.readConcern = options.readConcern;
    if (options.readPreference) this.options.readPreference = options.readPreference;
    if (options.maxCommitTimeMS) this.options.maxTimeMS = options.maxCommitTimeMS;

    // TODO: This isn't technically necessary
    this._pinnedServer = undefined;
    this._recoveryToken = undefined;
  }

  get server() {
    return this._pinnedServer;
  }

  get recoveryToken() {
    return this._recoveryToken;
  }

  get isPinned() {
    return !!this.server;
  }

  /**
   * @ignore
   * @return Whether this session is presently in a transaction
   */
  get isActive() {
    return (
      [TxnState.STARTING_TRANSACTION, TxnState.TRANSACTION_IN_PROGRESS].indexOf(this.state) !== -1
    );
  }

  /**
   * Transition the transaction in the state machine
   * @ignore
   * @param {TxnState} state The new state to transition to
   */
  transition(nextState) {
    const nextStates = stateMachine[this.state];
    if (nextStates && nextStates.indexOf(nextState) !== -1) {
      this.state = nextState;
      if (this.state === TxnState.NO_TRANSACTION || this.state === TxnState.STARTING_TRANSACTION) {
        this.unpinServer();
      }
      return;
    }

    throw new MongoError(
      `Attempted illegal state transition from [${this.state}] to [${nextState}]`
    );
  }

  pinServer(server) {
    if (this.isActive) {
      this._pinnedServer = server;
    }
  }

  unpinServer() {
    this._pinnedServer = undefined;
  }
}

function isTransactionCommand(command) {
  return !!(command.commitTransaction || command.abortTransaction);
}

module.exports = { TxnState, Transaction, isTransactionCommand };

@@ -56,16 +56,178 @@
 goError;
+%0Aconst ReadPreference = require('./topologies/read_preference');%0Aconst ReadConcern = require('../read_concern');%0Aconst WriteConcern = require('../write_concern');
 %0A%0Alet Tx
@@ -3171,28 +3171,22 @@
 %7D;%0A%0A    
-if (options.
+const 
 writeCon
@@ -3194,132 +3194,92 @@
 ern 
-%7C%7C typeof options.w !== 'undefined') %7B%0A      const w = options.writeConcern ? options.writeConcern.w : options.w;%0A      if (
+= WriteConcern.fromOptions(options);%0A    if (writeConcern) %7B%0A      if (writeConcern.
 w %3C=
@@ -3417,24 +3417,16 @@
 ncern = 
-options.
 writeCon
@@ -3433,50 +3433,8 @@
 cern
- ? options.writeConcern : %7B w: options.w %7D
 ;%0A  
@@ -3466,16 +3466,24 @@
 Concern)
+ %7B%0A     
  this.op
@@ -3502,25 +3502,17 @@
 ncern = 
-options.r
+R
 eadConce
@@ -3509,25 +3509,53 @@
  ReadConcern
-;
+.fromOptions(options);%0A    %7D%0A
 %0A    if (opt
@@ -3574,16 +3574,24 @@
 ference)
+ %7B%0A     
  this.op
@@ -3613,25 +3613,17 @@
 rence = 
-options.r
+R
 eadPrefe
@@ -3627,17 +3627,45 @@
 eference
-;
+.fromOptions(options);%0A    %7D%0A
 %0A    if 
@@ -3689,16 +3689,24 @@
 tTimeMS)
+ %7B%0A     
  this.op
@@ -3747,16 +3747,22 @@
 tTimeMS;
+%0A    %7D
 %0A%0A    //

use options helper to resolve read preference