/*
 * 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 {PromiseAdapter} = require('./promise-adapter');
const {DatabasePool} = require('./database-pool');
const {PreparedStatement, ParameterizedQuery} = require('./types');
const {QueryFile} = require('./query-file');
const {queryResult} = require('./query-result');
const {parsePromise} = require('./promise-parser');
const {assert} = require('./assert');

const npm = {
    path: require('path'),
    pg: require('pg'),
    minify: require('pg-minify'),
    formatting: require('./formatting'),
    helpers: require('./helpers'),
    errors: require('./errors'),
    utils: require('./utils'),
    pubUtils: require('./utils/public'),
    mode: require('./tx-mode'),
    package: require('../package.json'),
    text: require('./text')
};

let originalClientConnect;

/**
 * @author Vitaly Tomilov
 * @module pg-promise
 *
 * @description
 * ## pg-promise v11.9
 * All documentation here is for the latest official release only.
 *
 * ### Initialization Options
 *
 * Below is the complete list of _Initialization Options_ for the library that can be passed in during
 * the library's initialization:
 *
 * ```js
 * const initOptions = {&#47;* options as documented below *&#47;};
 *
 * const pgp = require('pg-promise')(initOptions);
 * ```
 *
 * @property {{}} [options]
 * Library Initialization Options.
 *
 * @property {boolean} [options.pgFormatting=false]
 * Redirects all query formatting to the $[pg] driver.
 *
 * By default (`false`), the library uses its own advanced query-formatting engine.
 * If you set this option to a truthy value, query formatting will be done entirely by the
 * $[pg] driver, which means you won't be able to use any of the feature-rich query formatting
 * that this library implements, restricting yourself to the very basic `$1, $2,...` syntax.
 *
 * This option is dynamic (can be set before or after initialization).
 *
 * @property {boolean} [options.pgNative=false]
 * Use $[Native Bindings]. Library $[pg-native] must be included and installed independently, or else there will
 * be an error thrown: {@link external:Error Error} = `Failed to initialize Native Bindings.`
 *
 * This is a static option (can only be set prior to initialization).
 *
 * @property {object|function} [options.promiseLib=Promise]
 * Overrides the default (ES6 Promise) promise library for its internal use.
 *
 * Example below sets to use $[Bluebird] - the best and recommended promise library. It is the fastest one,
 * and supports $[Long Stack Traces], essential for debugging promises.
 *
 * ```js
 * const Promise = require('bluebird');
 * const initOptions = {
 *     promiseLib: Promise
 * };
 * const pgp = require('pg-promise')(initOptions);
 * ```
 *
 * All existing promise libraries are supported. The ones with recognizable signature are used automatically,
 * while the rest can be configured via the $[Promise Adapter].
 *
 * This is a static option (can only be set prior to initialization).
 *
 * @property {boolean} [options.capSQL=false]
 * Capitalizes any SQL generated by the library.
 *
 * By default, all internal SQL within the library is generated using the low case.
 * If, however, you want all SQL to be capitalized instead, set `capSQL` = `true`.
 *
 * It is purely a cosmetic feature.
 *
 * This option is dynamic (can be set before or after initialization).
 *
 * @property {string|Array<string>|null|undefined|function} [options.schema]
 * Forces change of the default database schema(s) for every fresh connection, i.e.
 * the library will execute `SET search_path TO schema_1, schema_2, ...` in the background
 * whenever a fresh physical connection is allocated.
 *
 * Normally, one changes the default schema(s) by $[changing the database or the role], but sometimes you
 * may want to switch the default schema(s) without persisting the change, and then use this option.
 *
 * It can be a string, an array of strings, or a callback function that takes `dc` (database context)
 * as the only parameter (and as `this`), and returns schema(s) according to the database context. A callback function
 * can also return nothing (`undefined` or `null`), if no schema change needed for the specified database context.
 *
 * The order of schema names matters, so if a table name exists in more than one schema, its default access resolves
 * to the table from the first such schema on the list.
 *
 * This option is dynamic (can be set before or after initialization).
 *
 * @property {boolean} [options.noWarnings=false]
 * Disables all diagnostic warnings in the library (it is ill-advised).
 *
 * This option is dynamic (can be set before or after initialization).
 *
 * @property {function} [options.connect]
 * Global event {@link event:connect connect} handler.
 *
 * This option is dynamic (can be set before or after initialization).
 *
 * @property {function} [options.disconnect]
 * Global event {@link event:disconnect disconnect} handler.
 *
 * This option is dynamic (can be set before or after initialization).
 *
 * @property {function} [options.query]
 * Global event {@link event:query query} handler.
 *
 * This option is dynamic (can be set before or after initialization).
 *
 * @property {function} [options.receive]
 * Global event {@link event:receive receive} handler.
 *
 * This option is dynamic (can be set before or after initialization).
 *
 * @property {function} [options.task]
 * Global event {@link event:task task} handler.
 *
 * This option is dynamic (can be set before or after initialization).
 *
 * @property {function} [options.transact]
 * Global event {@link event:transact transact} handler.
 *
 * This option is dynamic (can be set before or after initialization).
 *
 * @property {function} [options.error]
 * Global event {@link event:error error} handler.
 *
 * This option is dynamic (can be set before or after initialization).
 *
 * @property {function} [options.extend]
 * Global event {@link event:extend extend} handler.
 *
 * This option is dynamic (can be set before or after initialization).
 *
 * @see
 * {@link module:pg-promise~end end},
 * {@link module:pg-promise~as as},
 * {@link module:pg-promise~errors errors},
 * {@link module:pg-promise~helpers helpers},
 * {@link module:pg-promise~minify minify},
 * {@link module:pg-promise~ParameterizedQuery ParameterizedQuery},
 * {@link module:pg-promise~PreparedStatement PreparedStatement},
 * {@link module:pg-promise~pg pg},
 * {@link module:pg-promise~QueryFile QueryFile},
 * {@link module:pg-promise~queryResult queryResult},
 * {@link module:pg-promise~spex spex},
 * {@link module:pg-promise~txMode txMode},
 * {@link module:pg-promise~utils utils}
 *
 */
function $main(options) {

    options = assert(options, ['pgFormatting', 'pgNative', 'promiseLib', 'capSQL', 'noWarnings',
        'connect', 'disconnect', 'query', 'receive', 'task', 'transact', 'error', 'extend', 'schema']);

    let pg = npm.pg;
    const p = parsePromise(options.promiseLib);

    const config = {
        version: npm.package.version,
        promiseLib: p.promiseLib,
        promise: p.promise
    };

    npm.utils.addReadProp(config, '$npm', {}, true);

    // Locking properties that cannot be changed later:
    npm.utils.addReadProp(options, 'promiseLib', options.promiseLib);
    npm.utils.addReadProp(options, 'pgNative', !!options.pgNative);

    config.options = options;

    // istanbul ignore next:
    // we do not cover code specific to Native Bindings
    if (options.pgNative) {
        pg = npm.pg.native;
        if (npm.utils.isNull(pg)) {
            throw new Error(npm.text.nativeError);
        }
    } else {
        if (!originalClientConnect) {
            originalClientConnect = pg.Client.prototype.connect;
            pg.Client.prototype.connect = function () {
                const handler = msg => {
                    if (msg.parameterName === 'server_version') {
                        this.serverVersion = msg.parameterValue;
                        this.connection.removeListener('parameterStatus', handler);
                    }
                };
                this.connection.on('parameterStatus', handler);
                return originalClientConnect.call(this, ...arguments);
            };
        }
    }

    const Database = require('./database')(config);

    const inst = (cn, dc) => {
        if (npm.utils.isText(cn) || (cn && typeof cn === 'object')) {
            return new Database(cn, dc, config);
        }
        throw new TypeError('Invalid connection details: ' + npm.utils.toJson(cn));
    };

    npm.utils.addReadProperties(inst, rootNameSpace);

    /**
     * @member {external:PG} pg
     * @description
     * Instance of the $[pg] library that's being used, depending on initialization option `pgNative`:
     *  - regular `pg` module instance, without option `pgNative`, or equal to `false` (default)
     *  - `pg` module instance with $[Native Bindings], if option `pgNative` was set.
     *
     * Available as `pgp.pg`, after initializing the library.
     */
    inst.pg = pg; // keep it modifiable, so the protocol can be mocked

    /**
     * @member {function} end
     * @readonly
     * @description
     * Shuts down all connection pools created in the process, so it can terminate without delay.
     * It is available as `pgp.end`, after initializing the library.
     *
     * All {@link Database} objects created previously can no longer be used, and their query methods will be rejecting
     * with {@link external:Error Error} = `Connection pool of the database object has been destroyed.`
     *
     * And if you want to shut down only a specific connection pool, you do so via the {@link Database}
     * object that owns the pool: `db.$pool.end()` (see {@link Database#$pool Database.$pool}).
     *
     * For more details see $[Library de-initialization].
     */
    npm.utils.addReadProp(inst, 'end', () => {
        DatabasePool.shutDown();
    });

    /**
     * @member {helpers} helpers
     * @readonly
     * @description
     * Namespace for {@link helpers all query-formatting helper functions}.
     *
     * Available as `pgp.helpers`, after initializing the library.
     *
     * @see {@link helpers}.
     */
    npm.utils.addReadProp(inst, 'helpers', npm.helpers(config));

    /**
     * @member {external:spex} spex
     * @readonly
     * @description
     * Initialized instance of the $[spex] module, used by the library within tasks and transactions.
     *
     * Available as `pgp.spex`, after initializing the library.
     *
     * @see
     * {@link Task#batch},
     * {@link Task#page},
     * {@link Task#sequence}
     */
    npm.utils.addReadProp(inst, 'spex', config.$npm.spex);

    config.pgp = inst;

    return inst;
}

const rootNameSpace = {

    /**
     * @member {formatting} as
     * @readonly
     * @description
     * Namespace for {@link formatting all query-formatting functions}.
     *
     * Available as `pgp.as`, before and after initializing the library.
     *
     * @see {@link formatting}.
     */
    as: npm.formatting.as,

    /**
     * @member {external:pg-minify} minify
     * @readonly
     * @description
     * Instance of the $[pg-minify] library used internally to minify SQL scripts.
     *
     * Available as `pgp.minify`, before and after initializing the library.
     */
    minify: npm.minify,

    /**
     * @member {queryResult} queryResult
     * @readonly
     * @description
     * Query Result Mask enumerator.
     *
     * Available as `pgp.queryResult`, before and after initializing the library.
     */
    queryResult,

    /**
     * @member {PromiseAdapter} PromiseAdapter
     * @readonly
     * @description
     * {@link PromiseAdapter} class.
     *
     * Available as `pgp.PromiseAdapter`, before and after initializing the library.
     */
    PromiseAdapter,

    /**
     * @member {ParameterizedQuery} ParameterizedQuery
     * @readonly
     * @description
     * {@link ParameterizedQuery} class.
     *
     * Available as `pgp.ParameterizedQuery`, before and after initializing the library.
     */
    ParameterizedQuery,

    /**
     * @member {PreparedStatement} PreparedStatement
     * @readonly
     * @description
     * {@link PreparedStatement} class.
     *
     * Available as `pgp.PreparedStatement`, before and after initializing the library.
     */
    PreparedStatement,

    /**
     * @member {QueryFile} QueryFile
     * @readonly
     * @description
     * {@link QueryFile} class.
     *
     * Available as `pgp.QueryFile`, before and after initializing the library.
     */
    QueryFile,

    /**
     * @member {errors} errors
     * @readonly
     * @description
     * {@link errors} - namespace for all error types.
     *
     * Available as `pgp.errors`, before and after initializing the library.
     */
    errors: npm.errors,

    /**
     * @member {utils} utils
     * @readonly
     * @description
     * {@link utils} - namespace for utility functions.
     *
     * Available as `pgp.utils`, before and after initializing the library.
     */
    utils: npm.pubUtils,

    /**
     * @member {txMode} txMode
     * @readonly
     * @description
     * {@link txMode Transaction Mode} namespace.
     *
     * Available as `pgp.txMode`, before and after initializing the library.
     */
    txMode: npm.mode
};

npm.utils.addReadProperties($main, rootNameSpace);

module.exports = $main;

/**
 * @external Promise
 * @see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise
 */

/**
 * @external PG
 * @see https://node-postgres.com
 */

/**
 * @external Client
 * @see https://node-postgres.com/apis/client
 */

/**
 * @external pg-minify
 * @see https://github.com/vitaly-t/pg-minify
 */

/**
 * @external spex
 * @see https://github.com/vitaly-t/spex
 */
