/**************************************************************************************************
 *
 * 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.
 *
 **************************************************************************************************/
const crypto = require('crypto'); 

const UniqueCertFinder = () => {
    let seen = new Set;
    return (binaryCertData) => {
        const sha = crypto.createHash('sha256').update(binaryCertData).digest('base64');
        if (!seen.has(sha)) {
            seen.add(sha);
            return true;
        }
        return false;
    };
};

const toPem = (binaryData) => {
    const dataStr = binaryData.toString('base64');
    const lines = [ '-----BEGIN CERTIFICATE-----' ];
    for (let i = 0, len = dataStr.length; i < len; i += 64) {
        lines.push(dataStr.substr(i, 64));
    }
    lines.push('-----END CERTIFICATE-----', '');
    return lines.join('\r\n');
};

module.exports.init = function init() {
    'use strict';
    const exports = {};
    const utils = require('./utils');
    const  { URL }  = require('url');
    const proxyresolverwin = require ('../build/Release/ProxyResolverWin.node');
    const win_cert_store = require('vscode-windows-ca-certs');

    function initProxyResolverWindows() {
        //OS versions for windows.
        //https://msdn.microsoft.com/en-us/library/windows/desktop/ms724832(v=vs.85).aspx
        //http://blogs.msdn.com/b/astebner/archive/2007/03/14/mailbag-what-version-of-the-net-framework-is-included-in-what-version-of-the-os.aspx
        exports.getNegotiateToken = function (spn, username, password, domain, callback) {
            proxyresolverwin.GenerateNegotiateToken({ spn, username, password, domain }, function (data) {
                callback(data.error, data.data, data.more);
            });
        };

        exports.setSettingsChangedCallback = function (callback) {
            proxyresolverwin.StartListeningForProxySettingsChanges(function callbackHandler(message) {
                if (message === 'changed') {
                    callback();
                } else {
                    utils.log(utils.logLevel.WARNING, 'Error from the proxy detecting worker ' + message);
                }
            });
        };

        exports.unsetSettingsChangedCallback = proxyresolverwin.StopListeningForProxySettingsChanges;

        exports.resolve = function (url, callback) {
            proxyresolverwin.ProxiesForURL({ input:url }, function callbackHandler(proxy) {
                let parsedProxyURL;
                let parsedInputURL;
                try {
                    parsedInputURL = new URL(url);
                } catch (e) {
                    utils.log(utils.logLevel.WARNING, `Unable to parse url (${url}).`);
                    return callback('Error parsing input url.');
                }
                let needToAppendProtocol = false;
                try {
                    parsedProxyURL = new URL(proxy);
                } catch (e) {
                    if (e.code === 'ERR_INVALID_URL') {
                        needToAppendProtocol = true;
                    }
                }
                if (needToAppendProtocol || parsedProxyURL.host === '') {
                    proxy = parsedInputURL.protocol + '//' + proxy;
                    try {
                        parsedProxyURL = new URL(proxy);
                    } catch (e) {
                        // Possibly something wrong with the format of the specified proxy URL, return no proxy.
                        utils.log(utils.logLevel.WARNING, `Incorrect Proxy URL (${proxy}), defaulting to no proxy.`);
                        return callback('Error parsing configured proxy URL.');
                    }
                }
                if (parsedProxyURL.href === parsedInputURL.href) {
                    parsedProxyURL = {};
                } else {
                    if (parsedInputURL.protocol === 'http:') {
                        parsedProxyURL.port = parsedProxyURL.port || 80;
                    } else if (parsedInputURL.protocol === 'https:') {
                        parsedProxyURL.port = parsedProxyURL.port || 443;
                    }
                }

                callback(null,[ parsedProxyURL ]);
            });
        };

        exports.getRootCA = function (callback) {
            const returnArray = [];
            const uniqueCertFinder = UniqueCertFinder();
            [ 'CA', 'AuthRoot', 'Root' ].forEach(storeName => {
                const store = new win_cert_store.Crypt32(storeName);
                for (let cert = store.next(); cert; cert = store.next()) {
                    if (uniqueCertFinder(cert)) {
                        returnArray.push(toPem(cert));
                    }
                }
                store.done();
            });
            callback(null, returnArray);
        };

        exports.getOSLocale = function (callback) {
            proxyresolverwin.GetOSLocale(function (locale) {
                callback(null, locale);
            });
        };

        exports.isLocalhostProxied = proxyresolverwin.LocalhostProxied;

        exports.getLocalhostPacProxyStatus = function (callback) {
            proxyresolverwin.PacProxyStatus(function (status) {
                if ([ 'pacFileNoProxy', 'noPacFile', 'proxied' ].indexOf(status) === -1) {
                    utils.log(utils.logLevel.WARNING, 'Error fetching pac content');
                    status = 'noPacFile';
                }
                callback(status);
            });
        };
    }

    initProxyResolverWindows();
    return exports;
};
