/**************************************************************************************************
 *
 * 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.
 *
 **************************************************************************************************/

/*jslint vars: true, node: true, plusplus: true, unparam: true, nomen: true */
/*global GLOBAL */
"use strict";

function init(logger) {
    var 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);
    }

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

    var exports = {
        resolve: function (url, callback) {
            callback(null, [{}]);
        },
        setSettingsChangedCallback: noop,
        unsetSettingsChangedCallback: noop,
        getRootCA: function (callback) {
            callback(null, []);
        },
        getNegotiateToken: function (spn, user, password, domain, callback) {
            callback("unimplemented");
        },
        getOSLocale: function () {
            return 'en_US';
        },
        getLocalhostPacProxyStatus: function (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.");
    }

    var 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);

    var proxyCache = {};

    proxy.on("change", function () {
        proxyCache = {};
    });

    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);
                }
                var proxied = exports.isLocalhostProxied();
                if (proxied === 'true') {
                    callback(null, true, 'Manual');
                } else if (proxied === 'false') {
                    callback(null, false);
                } else {
                    callback(proxied);
                }
            });
        });
    };

    proxy.resolveToURL = function (passedUrl, callback) {
        var 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) {
                    var protocol = url.parse(targetURL).protocol;
                    if (proxyUrl && proxyUrl.hostname) {
                        var 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;
}

var ProxyResolver = null;

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