'use strict';

const npm = {
    con: require('manakin').local,
    path: require('path'),
    pg: require('pg'),
    minify: require('pg-minify'),
    adapter: require('./adapter'),
    result: require('./result'),
    promise: require('./promise'),
    formatting: require('./formatting'),
    helpers: require('./helpers'),
    queryFile: require('./queryFile'),
    errors: require('./errors'),
    utils: require('./utils'),
    pubUtils: require('./utils/public'),
    mode: require('./txMode'),
    types: require('./types'),
    dbPool: require('./dbPool'),
    package: require('../package.json')
};

/**
 * @author Vitaly Tomilov
 * @module pg-promise
 *
 * @description
 * ### Initialization Options
 *
 * Below is the complete list of _Initialization Options_ for the library that can be passed during
 * the library's initialization:
 *
 * ```js
 * const initOptions = {/* options as documented below */};
 *
 * const pgp = require('pg-promise')(initOptions);
 * ```
 *
 * @param {object} [options]
 * Library Initialization Options.
 *
 * @param {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 pass in this option as `true`, query formatting will be done entirely by the
 * $[pg] driver, and you won't be able to use any of the feature-rich query formatting
 * that this library implements.
 *
 * This option is dynamic (can be set before or after initialization).
 *
 * @param {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).
 *
 * @param {object|function} [options.promiseLib=Promise]
 * Overrides the default promise library (ES6 Promise).
 *
 * **example:**
 * ```js
 * const Promise = require('bluebird');
 * const initOptions = {
 *     promiseLib: Promise
 * };
 * const pgp = require('pg-promise')(initOptions);
 * ```
 *
 * This is a static option (can only be set prior to initialization).
 *
 * @param {boolean} [options.noLocking=false]
 * Prevents protocol locking.
 *
 * By default, the library locks much of its protocol to read-only access, as a fool-proof mechanism.
 * Specifically for the {@link event:extend extend} event this serves as a protection against overriding existing
 * properties or trying to set them at the wrong time.
 *
 * If this provision gets in the way of using a mock-up framework for your tests, you can force
 * the library to deactivate most of the locks by setting `noLocking` = `true` within the options.
 *
 * This option is dynamic (can be set before or after initialization).
 *
 * @param {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).
 *
 * @param {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).
 *
 * @param {function} [options.connect]
 * Global event {@link event:connect connect} handler.
 *
 * This option is dynamic (can be set before or after initialization).
 *
 * @param {function} [options.disconnect]
 * Global event {@link event:disconnect disconnect} handler.
 *
 * This option is dynamic (can be set before or after initialization).
 *
 * @param {function} [options.query]
 * Global event {@link event:query query} handler.
 *
 * This option is dynamic (can be set before or after initialization).
 *
 * @param {function} [options.receive]
 * Global event {@link event:receive receive} handler.
 *
 * @param {function} [options.task]
 * Global event {@link event:task task} handler.
 *
 * This option is dynamic (can be set before or after initialization).
 *
 * @param {function} [options.transact]
 * Global event {@link event:transact transact} handler.
 *
 * This option is dynamic (can be set before or after initialization).
 *
 * @param {function} [options.error]
 * Global event {@link event:error error} handler.
 *
 * This option is dynamic (can be set before or after initialization).
 *
 * @param {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) {

    if (npm.utils.isNull(options)) {
        options = {};
    } else {
        if (typeof options !== 'object') {
            throw new TypeError('Invalid initialization options: ' + JSON.stringify(options));
        }

        // list of supported initialization options:
        const validOptions = ['pgFormatting', 'pgNative', 'promiseLib', 'noLocking', 'capSQL', 'noWarnings',
            'connect', 'disconnect', 'query', 'receive', 'task', 'transact', 'error', 'extend'];

        if (!options.noWarnings) {
            for (let prop in options) {
                if (validOptions.indexOf(prop) === -1) {
                    npm.con.warn('WARNING: Invalid property \'%s\' in initialization options.\n%s\n', prop, npm.utils.getLocalStack(3));
                    break;
                }
            }
        }
    }

    let pg = npm.pg;
    const p = npm.promise(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('Failed to initialize Native Bindings.');
        }
    }

    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: ' + JSON.stringify(cn));
    };

    npm.utils.addReadProperties(inst, rootNameSpace);

    /**
     * @member {external:PG} pg
     * @readonly
     * @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.
     */
    npm.utils.addReadProp(inst, 'pg', pg);

    /**
     * @member {function} end
     * @readonly
     * @description
     * Shuts down all connection pools currently allocated, so the process 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, throwing
     * {@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}).
     *
     */
    npm.utils.addReadProp(inst, 'end', () => {
        npm.dbPool.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;
    Object.freeze(config);

    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: npm.result,

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

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

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

    /**
     * @member {QueryFile} QueryFile
     * @readonly
     * @description
     * {@link QueryFile} class.
     *
     * Available as `pgp.QueryFile`, before and after initializing the library.
     */
    QueryFile: npm.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://github.com/brianc/node-postgres/blob/master/lib/index.js#L17
 */

/**
 * @external Client
 * @see https://github.com/brianc/node-postgres/blob/master/lib/client.js#L21
 */

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

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