'use strict';

var $npm = {
    con: require('manakin').local,
    path: require('path'),
    pg: require('pg'),
    minify: require('pg-minify'),
    array: require('./array'),
    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'),
    package: require('../package.json')
};

/**
 * @author Vitaly Tomilov
 * @module pg-promise
 *
 * @description
 * ### Initialization Options
 *
 * Below is the complete list of _Initialization Options_ for the library.
 *
 * @param {object} [options]
 * Library Initialization Options.
 *
 * @param {boolean} [options.pgFormatting=false]
 * Redirects query formatting to PG.
 *
 * This property can be set dynamically (before or after initialization).
 *
 * @param {boolean} [options.pgNative=false]
 * Use $[Native Bindings]. Library $[pg-native] must be installed, or else it will throw an error.
 *
 * This is a static property (can only be set prior to initialization).
 *
 * @param {object|function} [options.promiseLib=Promise]
 * Overrides the default promise library.
 *
 * This is a static property (can only be set prior to initialization).
 *
 * @param {boolean} [options.noLocking=false]
 * Prevents protocol locking.
 *
 * By default, the library locks 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 property can be set dynamically (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`.
 *
 * This is purely a cosmetic feature.
 *
 * This property can be set dynamically (before or after initialization).
 *
 * @param {boolean} [options.noWarnings=false]
 * Disables all diagnostic warnings in the library (which is ill-advised).
 *
 * This property can be set dynamically (before or after initialization).
 *
 * @param {function} [options.connect]
 * Global event {@link event:connect connect} handler.
 *
 * This property can be set dynamically (before or after initialization).
 *
 * @param {function} [options.disconnect]
 * Global event {@link event:disconnect disconnect} handler.
 *
 * This property can be set dynamically (before or after initialization).
 *
 * @param {function} [options.query]
 * Global event {@link event:query query} handler.
 *
 * This property can be set dynamically (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 property can be set dynamically (before or after initialization).
 *
 * @param {function} [options.transact]
 * Global event {@link event:transact transact} handler.
 *
 * This property can be set dynamically (before or after initialization).
 *
 * @param {function} [options.error]
 * Global event {@link event:error error} handler.
 *
 * This property can be set dynamically (before or after initialization).
 *
 * @param {function} [options.extend]
 * Global event {@link event:extend extend} handler.
 *
 * This property can be set dynamically (before or after initialization).
 *
 * @example
 *
 * var options = {
 *   // Initialization Options
 * };
 *
 * var pgp = require('pg-promise')(options);
 *
 */
function $main(options) {

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

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

        if (!options.noWarnings) {
            for (var 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;
                }
            }
        }
    }

    var pg = $npm.pg, p = $npm.promise(options.promiseLib);

    var 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.');
        }
    }

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

    var 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.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
     * Terminates pg library (call it when exiting the application).
     *
     * Available as `pgp.end`, after initializing the library.
     */
    $npm.utils.addReadProp(inst, 'end', () => {
        pg.end();
    });

    /**
     * @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;
}

var 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 that's used.
     *
     * 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#L20
 */

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

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