/*
 * Copyright (c) 2015-present, Vitaly Tomilov
 *
 * See the LICENSE file at the top-level directory of this distribution
 * for licensing information.
 *
 * Removal or modification of this copyright notice is prohibited.
 */

const {InnerState} = require(`./inner-state`);
const {addInspection} = require(`./utils`);
const {assertOptions} = require(`assert-options`);

/**
 * @enum {number}
 * @alias txMode.isolationLevel
 * @readonly
 * @summary Transaction Isolation Level.
 * @description
 * The type is available from the {@link txMode} namespace.
 *
 * @see $[Transaction Isolation]
 */
const isolationLevel = {
    /** Isolation level not specified. */
    none: 0,

    /** ISOLATION LEVEL SERIALIZABLE */
    serializable: 1,

    /** ISOLATION LEVEL REPEATABLE READ */
    repeatableRead: 2,

    /** ISOLATION LEVEL READ COMMITTED */
    readCommitted: 3

    // From the official documentation: http://www.postgresql.org/docs/9.5/static/sql-set-transaction.html
    // The SQL standard defines one additional level, READ UNCOMMITTED. In PostgreSQL READ UNCOMMITTED is treated as READ COMMITTED.
    // => skipping `READ UNCOMMITTED`.
};

/**
 * @class txMode.TransactionMode
 * @description
 * Constructs a complete transaction-opening `BEGIN` command, from these options:
 *  - isolation level
 *  - access mode
 *  - deferrable mode
 *
 * The type is available from the {@link txMode} namespace.
 *
 * @param {} [options]
 * Transaction Mode options.
 *
 * @param {txMode.isolationLevel} [options.tiLevel]
 * Transaction Isolation Level.
 *
 * @param {boolean} [options.readOnly]
 * Sets transaction access mode based on the read-only flag:
 *  - `undefined` - access mode not specified (default)
 *  - `true` - access mode is set to `READ ONLY`
 *  - `false` - access mode is set to `READ WRITE`
 *
 * @param {boolean} [options.deferrable]
 * Sets transaction deferrable mode based on the boolean value:
 *  - `undefined` - deferrable mode not specified (default)
 *  - `true` - mode is set to `DEFERRABLE`
 *  - `false` - mode is set to `NOT DEFERRABLE`
 *
 * It is used only when `tiLevel`=`isolationLevel.serializable`
 * and `readOnly`=`true`, or else it is ignored.
 *
 * @returns {txMode.TransactionMode}
 *
 * @see $[BEGIN], {@link txMode.isolationLevel}
 *
 * @example
 *
 * const {TransactionMode, isolationLevel} = pgp.txMode;
 *
 * // Create a reusable transaction mode (serializable + read-only + deferrable):
 * const mode = new TransactionMode({
 *     tiLevel: isolationLevel.serializable,
 *     readOnly: true,
 *     deferrable: true
 * });
 *
 * db.tx({mode}, t => {
 *     return t.any('SELECT * FROM table');
 * })
 *     .then(data => {
 *         // success;
 *     })
 *     .catch(error => {
 *         // error
 *     });
 *
 * // Instead of the default BEGIN, such transaction will start with:
 *
 * // BEGIN ISOLATION LEVEL SERIALIZABLE READ ONLY DEFERRABLE
 *
 */
class TransactionMode extends InnerState {

    constructor(options) {
        options = assertOptions(options, [`tiLevel`, `deferrable`, `readOnly`]);
        const {readOnly, deferrable} = options;
        let {tiLevel} = options;
        let level, accessMode, deferrableMode, begin = `begin`;
        tiLevel = (tiLevel > 0) ? parseInt(tiLevel) : 0;

        if (tiLevel > 0 && tiLevel < 4) {
            const values = [`serializable`, `repeatable read`, `read committed`];
            level = `isolation level ` + values[tiLevel - 1];
        }
        if (readOnly) {
            accessMode = `read only`;
        } else {
            if (readOnly !== undefined) {
                accessMode = `read write`;
            }
        }
        // From the official documentation: http://www.postgresql.org/docs/9.5/static/sql-set-transaction.html
        // The DEFERRABLE transaction property has no effect unless the transaction is also SERIALIZABLE and READ ONLY
        if (tiLevel === isolationLevel.serializable && readOnly) {
            if (deferrable) {
                deferrableMode = `deferrable`;
            } else {
                if (deferrable !== undefined) {
                    deferrableMode = `not deferrable`;
                }
            }
        }
        if (level) {
            begin += ` ` + level;
        }
        if (accessMode) {
            begin += ` ` + accessMode;
        }
        if (deferrableMode) {
            begin += ` ` + deferrableMode;
        }

        super({begin, capBegin: begin.toUpperCase()});
    }

    /**
     * @method txMode.TransactionMode#begin
     * @description
     * Returns a complete BEGIN statement, according to all the parameters passed into the class.
     *
     * This method is primarily for internal use by the library.
     *
     * @param {boolean} [cap=false]
     * Indicates whether the returned SQL must be capitalized.
     *
     * @returns {string}
     */
    begin(cap) {
        return cap ? this._inner.capBegin : this._inner.begin;
    }
}

addInspection(TransactionMode, function () {
    return this.begin(true);
});

/**
 * @namespace txMode
 * @description
 * Transaction Mode namespace, available as `pgp.txMode`, before and after initializing the library.
 *
 * Extends the default `BEGIN` with Transaction Mode parameters:
 *  - isolation level
 *  - access mode
 *  - deferrable mode
 *
 * @property {function} TransactionMode
 * {@link txMode.TransactionMode TransactionMode} class constructor.
 *
 * @property {txMode.isolationLevel} isolationLevel
 * Transaction Isolation Level enumerator
 *
 * @see $[BEGIN]
 */
module.exports = {
    isolationLevel,
    TransactionMode
};