/**************************************************************************************************
 *
 * ADOBE SYSTEMS INCORPORATED
 * Copyright 2015 Adobe Systems Incorporated
 * All Rights Reserved.
 *
 * NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the
 * terms of the Adobe license agreement accompanying it.  If you have received this file from a
 * source other than Adobe, then your use, modification, or distribution of it requires the prior
 * written permission of Adobe.
 *
 **************************************************************************************************/

'use strict';

function init(logger) {
    const os = require('os'),
        EventEmitter = require('events').EventEmitter,
        proxy = new EventEmitter(),
        utils = require('./src/utils'),
        tunnel = require('./src/tunnel'),
        url = require('url');

    if (logger) {
        utils.setLogger(logger);
    }

    const noop = function () {
        return undefined;
    };

    let exports = {
        resolve(url, callback) {
            callback(null, [ {} ]);
        },
        setSettingsChangedCallback: noop,
        unsetSettingsChangedCallback: noop,
        getRootCA(callback) {
            callback(null, []);
        },
        getNegotiateToken(spn, user, password, domain, callback) {
            callback('unimplemented');
        },
        getOSLocale(callback) {
            callback(null, 'en_US');
        },
        getLocalhostPacProxyStatus(callback) {
            callback('noPacFile');
        }
    };

    switch (os.platform()) {
    case 'darwin':
        exports = require(__dirname + '/src/osx').init();
        break;
    case 'win32':
        try {
            exports = require(__dirname + '/src/win').init();
        } catch (e) {
            utils.log(utils.logLevel.ERROR, "Couldn't load win32 stuff. Make sure C++ runtime is installed and the native dlls are present: ", e);
        }
        break;
    default:
        utils.log(utils.logLevel.ERROR, 'OS not supported.');
    }

    const ProxyChanged = utils.throttle(function () {
        proxy.emit('change');
    }, 500, {
        leading: false
    });

    //Throttle Proxy changed since a 500ms lag is better than the cost of calling it again and again.
    exports.setSettingsChangedCallback(ProxyChanged);

    let proxyCache = {};

    proxy.clearCache = function () {
        proxyCache = {};
    };

    proxy.on('change', function () {
        proxy.clearCache();
    });

    proxy.isLocalhostProxiedUnderHttp = function (callback) {
        // PAC files work very differently
        // If a PAC file is present the localhost setting does not have an impact
        // Instead the proxy setting comes from the PAC file.
        exports.getLocalhostPacProxyStatus(function (status) {
            if (status === 'pacFileNoProxy') {
                callback(null, false);
                return;
            }
            if (status === 'proxied') {
                callback(null, true, 'PAC');
                return;
            }
            //This is a workaround for a behavior issue.
            //Say you are behind a proxy and have localhost specified as 127.0.0.1 in the /etc/hosts.
            //The OS(Safari) and all network APIs would give precedence to the hosts file and will
            //return a direct connection to the localhost. The exclude local hostnames flag doesn't matter
            //in gettin the proxy. We have a local DNS override.
            //Chrome does a different thing. It does a query to the DNS and thus respects the proxy settings
            //This is documented by the bug: https://code.google.com/p/chromium/issues/detail?id=117655
            //So we identify the proxy for a URL that's unlikely to be in the hosts file and is a simple hostname.
            //Apple will therefore tell us the most likely configuration for the proxy for this case.
            //If this new URL is having a proxy setting, we will assume that chrome gets one for localhost
            //and we verify the exculde local flag.
            exports.resolve('http://somewierdgarbagelocalhostreplacement', function (err, data) {
                if (err) {
                    return callback(err);
                }
                if (!Array.isArray(data) || data.length < 1 || !data[0].hostname) {
                    return callback(null, false);
                }
                exports.isLocalhostProxied(proxied => {
                    if (proxied === 'true') {
                        callback(null, true, 'Manual');
                    } else if (proxied === 'false') {
                        callback(null, false);
                    } else {
                        callback(proxied);
                    }
                });
            });
        });
    };

    proxy.resolveToURL = function (passedUrl, callback) {
        const cleanUrl = url.format(url.parse(passedUrl));
        if (proxyCache[cleanUrl]) {
            callback(null, proxyCache[cleanUrl]);
            return;
        }
        exports.resolve(cleanUrl, function (err, data) {
            if (!err) {
                proxyCache[cleanUrl] = data;
            }
            callback(err, data);
        });
    };
    proxy.resolve = function (targetURL, callback) {
        proxy.resolveToURL(targetURL, function (err, urls) {
            if (err) {
                callback(err);
            } else if (!Array.isArray(urls) || urls.length === 0 || !urls[0].hostname) {
                callback(null, urls, undefined);
            } else {
                urls.forEach(function (proxyUrl) {
                    const protocol = url.parse(targetURL).protocol;
                    if (proxyUrl && proxyUrl.hostname) {
                        const AgentClass = protocol === 'http:' ? tunnel.HTTPAgent : tunnel.HTTPSAgent;
                        proxyUrl.agentOptions = {
                            proxy: {
                                host: proxyUrl.hostname,
                                port: proxyUrl.port
                            }
                        };
                        proxyUrl.agentClass = AgentClass;
                        proxyUrl.agent = new AgentClass({
                            proxy: {
                                host: proxyUrl.hostname,
                                port: proxyUrl.port
                            }
                        });
                    }
                });
                callback(null, urls);
            }
        });
    };
    proxy.setAuthenticationCallback = function (authFunction) {
        tunnel.agent.authenticate = authFunction;
    };

    proxy.getOSLocale = exports.getOSLocale;
    proxy.getRootCA = exports.getRootCA;
    tunnel.init(exports.getNegotiateToken);
    proxy.setLogger = utils.setLogger;
    proxy.agent = tunnel.agent;
    return proxy;
}

let ProxyResolver = null;

module.exports = function (logger) {
    if (!ProxyResolver) {
        ProxyResolver = init(logger);
    }
    if (logger) {
        ProxyResolver.setLogger(logger);
    }
    return ProxyResolver;
};
