'use strict'

var http = require('http');
var https = require('https');
var tunnel = require('tunnel');
var url = require('url');
var zlib = require('zlib');
var fs = require('fs');
var EventEmitter = require('events').EventEmitter;

/**
 * Downloads a file using http get and request
 * @param {string} src - The http URL to download from
 * @param {string} output - The filepath to save to
 * @param {object} options - Options object
 * @param {object} _parentEvent - Used for when their is a 302 redirect and need to maintain state to a new request
 * @param {number} redirects - The number of redirects, used to prevent infinite loops
 * @returns {*|EventEmitter}
 */
function download(src, output, options, _parentEvent, redirects) {
    if(typeof redirects === "undefined") {
        redirects = 0;
    }
    var downloader = _parentEvent || new EventEmitter(),
        srcUrl,
        tunnelAgent,
        req;

    if (options) {
        options = parseOptions('download', options);
    } else {
        options = {
            gunzip: false
        };
    }
    srcUrl = url.parse(src);
    srcUrl.protocol = cleanProtocol(srcUrl.protocol);

    req = request({
        protocol: srcUrl.protocol,
        host: srcUrl.hostname,
        port: srcUrl.port,
        path: srcUrl.pathname + (srcUrl.search || ""),
        proxy: options?options.proxy:undefined,
        method: 'GET'
    }, function(res) {
        var fileSize, writeStream, downloadedSize;
        var gunzip = zlib.createGunzip();

        // Handle 302 redirects
        if(res.statusCode === 301 || res.statusCode === 302) {
            redirects++;
            if(redirects >= 10) {
                downloader.emit('error', 'Infinite redirect loop detected');
            }
            download(res.headers.location, output, options, downloader, redirects);
        }

        if (res.statusCode === 200) {
            downloadedSize = 0;
            fileSize = res.headers['content-length'];
            writeStream = fs.createWriteStream(output, {
                flags: 'w+',
                encoding: 'binary'
            });

            res.on('error', function(err) {
                writeStream.end();
                downloader.emit('error', err);
            });

            var encoding = "";
            if(typeof res.headers['content-encoding'] === "string") {
                encoding = res.headers['content-encoding'];
            }

            // If the user has specified to unzip, and the file is gzip encoded, pipe to gunzip
            if(options.gunzip === true && encoding === "gzip") {
                res.pipe(gunzip);
            } else {
                res.pipe(writeStream);
            }
            // Data handlers
            res.on('data', function(chunk) {
                downloadedSize += chunk.length;
                downloader.emit('progress', downloadedSize/fileSize);
            });
            gunzip.on('data', function(chunk) {
                writeStream.write(chunk);
            });

            writeStream.on('finish', function() {
                writeStream.end();
                downloader.emit('end', "Finished writing to disk");
                req.end('finished');
            });
        } else if(res.statusCode !== 200 && res.statusCode !== 301 && res.statusCode !== 302) {
            downloader.emit('error', 'Server responded with unhandled status: ' + res.statusCode);
        }
    });

    req.end('done');
    req.on('error', function(err) {
        downloader.emit('error', err);
    });
    // Attach request to our EventEmitter for backwards compatibility, enables actions such as
    // req.abort();
    downloader.req = req;

    return downloader;
}

function request(options, callback) {
    var newOptions = {}, newProxy = {}, key;
    options = parseOptions('request', options);
    if (options.protocol === 'http') {
        if (options.proxy) {
            for (key in options.proxy) {
                if (key !== 'protocol') {
                    newProxy[key] = options.proxy[key];
                }
            }
            if (options.proxy.protocol === 'http') {
                options.agent = tunnel.httpOverHttp({proxy: newProxy});
            } else if (options.proxy.protocol === 'https') {
                options.agent = tunnel.httpOverHttps({proxy: newProxy});
            } else {
                throw options.proxy.protocol + ' proxy is not supported!';
            }
        }
        for (key in options) {
            if (key !== 'protocol' && key !== 'proxy') {
                newOptions[key] = options[key];
            }
        }
        return http.request(newOptions, callback);
    }
    if (options.protocol === 'https') {
        if (options.proxy) {
            for (key in options.proxy) {
                if (key !== 'protocol') {
                    newProxy[key] = options.proxy[key];
                }
            }
            if (options.proxy.protocol === 'http') {
                options.agent = tunnel.httpsOverHttp({proxy: newProxy});
            } else if (options.proxy.protocol === 'https') {
                options.agent = tunnel.httpsOverHttps({proxy: newProxy});
            } else {
                throw options.proxy.protocol + ' proxy is not supported!';
            }
        }
        for (key in options) {
            if (key !== 'protocol' && key !== 'proxy') {
                newOptions[key] = options[key];
            }
        }
        return https.request(newOptions, callback);
    }
    throw 'only allow http or https request!';
}

function parseOptions(type, options) {
    var proxy;
    if (type === 'download') {
        if (options.proxy) {
            if (typeof options.proxy === 'string') {
                proxy = url.parse(options.proxy);
                options.proxy = {};
                options.proxy.protocol = cleanProtocol(proxy.protocol);
                options.proxy.host = proxy.hostname;
                options.proxy.port = proxy.port;
                options.proxy.proxyAuth = proxy.auth;
                options.proxy.headers = {'User-Agent': 'Node'};
            }
        }
        return options;
    }
    if (type === 'request') {
        if (!options.protocol) {
            options.protocol = 'http';
        }
        options.protocol = cleanProtocol(options.protocol);

        if (options.proxy) {
            if (typeof options.proxy === 'string') {
                proxy = url.parse(options.proxy);
                options.proxy = {};
                options.proxy.protocol = cleanProtocol(proxy.protocol);
                options.proxy.host = proxy.hostname;
                options.proxy.port = proxy.port;
                options.proxy.proxyAuth = proxy.auth;
                options.proxy.headers = {'User-Agent': 'Node'};
            }
        }

        options.gunzip = options.gunzip || false;
        return options;
    }
}

function cleanProtocol(str) {
    return str.trim().toLowerCase().replace(/:$/, '');
}

exports.download = download;
exports.request = request;