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

module.exports.init = function () {
    'use strict';
    var exports = {};
    var utils = require('./utils');
    var ffi = require('ffi-napi');
    var URL = require('url');
    var os = require('os');
    var nativeCallback = ffi.Function('void', ['CString']);

    function loadViaDotNetLib(libName) {
        utils.log(utils.logLevel.INFO, 'Trying to use: ' + libName);
        //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

        var libProxy = ffi.Library(__dirname + libName, {
            'ProxiesForURL': ['CString', ['CString']],
            'StartListeningForProxySettingsChanges': ['int', [nativeCallback]],
            'StopListeningForProxySettingsChanges': ['void', []],
            'FreeProxyReturnString': ['void', []],
            'SystemRootCA': ['CString', []],
            'FreeSystemRootCAReturnString': ['void', []],
            'GenerateNegotiateToken': ['void', ['CString', 'CString', 'CString', 'CString', nativeCallback]],
            'LocalhostProxied': ['CString', []],
            'GetOSLocale': ['CString', []],
            'FreeOSLocaleReturnString': ['void', []],
            'PacProxyStatus': ['CString', []]
        });

        libProxy.ProxiesForURL = utils.wrapWithPostCall(libProxy.ProxiesForURL, libProxy.FreeProxyReturnString);
        libProxy.SystemRootCA = utils.wrapWithPostCall(libProxy.SystemRootCA, libProxy.FreeSystemRootCAReturnString);
        libProxy.LocalhostProxied = utils.wrapWithPostCall(libProxy.LocalhostProxied, libProxy.FreeProxyReturnString);
        libProxy.StartListeningForProxySettingsChanges = utils.wrapNativeCallback(libProxy.StartListeningForProxySettingsChanges);
        libProxy.GenerateNegotiateToken = utils.wrapNativeCallback(libProxy.GenerateNegotiateToken);
        libProxy.GetOSLocale = utils.wrapWithPostCall(libProxy.GetOSLocale, libProxy.FreeOSLocaleReturnString);

        exports.getNegotiateToken = function (spn, user, password, domain, callback) {
            libProxy.GenerateNegotiateToken(spn, user, password, domain, function (data) {
                var d = {};
                try {
                    d = JSON.parse(data);
                } catch (e) {
                    callback('Error parsing native data for negotiate token: ' + data);
                    return;
                }

                callback(d.error, d.data, d.more);
            });
        };

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

        exports.unsetSettingsChangedCallback = libProxy.StopListeningForProxySettingsChanges;

        exports.resolve = function (url, callback) {
            try {
                var data = JSON.parse(libProxy.ProxiesForURL(url));
                var passedData = data.data;
                if (passedData) {
                    if (URL.parse(passedData).href === URL.parse(url).href) {
                        passedData = {};
                    } else {
                        passedData = URL.parse(passedData);
                        passedData.port = passedData.port || 80;
                    }
                }
                callback(data.error, [passedData]);
            } catch (e) {
                callback('Error parsing response from native windows APIs:' + e);
            }
        };


        exports.getRootCA = function (callback) {
            var nativeCerts = libProxy.SystemRootCA();
            var returnArray = [];
            var errors;
            try {
                nativeCerts = JSON.parse(nativeCerts);
                // CERTIFICATE STORE TYPES IN WINDOWS
                // See https://msdn.microsoft.com/en-us/library/system.security.cryptography.x509certificates.storename(v=vs.110).aspx
                //We accept the folowing:
                //AuthRoot - third-party certificate authorities (CAs).
                //Root     - trusted root certificate authorities (CAs).
                //CA       - intermediate certificate authorities (CAs).
                errors = nativeCerts.errors || [];
                var invalidCerts = 0;
                nativeCerts.data.forEach(function (authority) {
                    try {
                        if (['Root', 'CA', 'AuthRoot'].indexOf(authority.name) !== -1) {
                            authority.certificates.forEach(function (cert) {
                                if (returnArray.indexOf(cert.pem) === -1 && cert.valid) {
                                    returnArray.push(cert.pem);
                                } else {
                                    invalidCerts++;
                                }
                            });
                        }
                    } catch (e) {
                        errors.push(e);
                    }
                });
                utils.log(utils.logLevel.INFO, 'Number of invalid certificates: ' + invalidCerts);

            } catch (e) {
                utils.log(utils.logLevel.WARNING, 'Error parsing native certificate response: ' + e + '\nResponse:\n' + nativeCerts);
            }
            if (Array.isArray(errors) && errors.length > 0) {
                utils.log(utils.logLevel.WARNING, 'Errors while fetching Root Certs ' + JSON.stringify(errors));
            }

            callback(null, returnArray);
        };

        exports.getOSLocale = libProxy.GetOSLocale;

        exports.isLocalhostProxied = libProxy.LocalhostProxied;

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

    }

    //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
    var release = os.release().split('.');

    if (release[0] === '6' && (release[1] === '1' || release[1] === '0')) {
        loadViaDotNetLib('/../build/Release/ProxyResolverWin7'); // .NET 3
    } else {
        loadViaDotNetLib('/../build/Release/ProxyResolverWin'); // .NET 4.5
    }
    return exports;
};
