"use strict";
const Promise = require("bluebird");
const fs = require("fs");
const http = require("http");
const https = require("https");
const path = require("path");
const url = require("url");
const zlib_1 = require("zlib");
exports.fsReaddirPromise = Promise.promisify(fs.readdir);
exports.fsReadFilePromise = Promise.promisify(fs.readFile);
exports.fsRenamePromise = Promise.promisify(fs.rename);
exports.fsStatPromise = Promise.promisify(fs.stat);
exports.fsUnlinkPromise = Promise.promisify(fs.unlink);
exports.fsWriteFilePromise = Promise.promisify(fs.writeFile);
function defer() {
    let resolve;
    let reject;
    let promise = new Promise((r, e) => {
        resolve = r;
        reject = e;
    });
    return {
        resolve,
        reject,
        promise
    };
}
exports.defer = defer;
function retryAsync(func, condition, maxIterations, failureMsg, delay = 100, iteration = 0) {
    function newIteration() {
        return Promise.delay(delay).then(() => retryAsync(func, condition, maxIterations, failureMsg, delay, iteration + 1));
    }
    if (iteration < maxIterations) {
        return func()
            .then(result => {
            if (condition(result)) {
                return result;
            }
            return newIteration();
        }, (e) => {
            return newIteration();
        });
    }
    return Promise.reject(new Error(failureMsg));
}
exports.retryAsync = retryAsync;
function randomInt(minInclusive, maxInclusive) {
    if (minInclusive > maxInclusive) {
        const tmp = minInclusive;
        minInclusive = maxInclusive;
        maxInclusive = tmp;
    }
    return Math.floor(Math.random() * (maxInclusive - minInclusive + 1)) + minInclusive;
}
exports.randomInt = randomInt;
function mkdirp(thePath) {
    if (thePath == ".") {
        return;
    }
    if (!fs.existsSync(thePath)) {
        mkdirp(path.dirname(thePath));
        fs.mkdirSync(thePath);
    }
}
exports.mkdirp = mkdirp;
function fileExistsAsync(thePath) {
    return exports.fsStatPromise(thePath)
        .then((stats) => {
        return true;
    })
        .catch((e) => {
        return false;
    });
}
exports.fileExistsAsync = fileExistsAsync;
function readJsonFileAsync(thePath) {
    return fileExistsAsync(thePath)
        .then((exists) => {
        if (!exists) {
            return Promise.resolve(null);
        }
        return exports.fsReadFilePromise(thePath, "utf8");
    })
        .then((content) => {
        return JSON.parse(content);
    });
}
exports.readJsonFileAsync = readJsonFileAsync;
function writeJsonFileAsync(content, thePath) {
    return exports.fsWriteFilePromise(thePath, JSON.stringify(content));
}
exports.writeJsonFileAsync = writeJsonFileAsync;
function requestAsStream(options) {
    let deferred = defer();
    let req;
    const endpoint = url.parse(options.url);
    const rawRequest = endpoint.protocol === 'https:' ? https.request : http.request;
    const opts = {
        hostname: endpoint.hostname,
        port: endpoint.port ? parseInt(endpoint.port) : (endpoint.protocol === 'https:' ? 443 : 80),
        path: endpoint.path,
        method: options.type || 'GET',
        headers: options.headers,
        agent: options.agent,
        rejectUnauthorized: isBoolean(options.strictSSL) ? options.strictSSL : true
    };
    if (options.user && options.password) {
        opts.auth = options.user + ':' + options.password;
    }
    req = rawRequest(opts, (res) => {
        const followRedirects = isNumber(options.followRedirects) ? options.followRedirects : 3;
        if (res.statusCode >= 300 && res.statusCode < 400 && followRedirects > 0 && res.headers['location']) {
            requestAsStream(Object.assign({}, options, {
                url: res.headers['location'],
                followRedirects: followRedirects - 1
            })).done(deferred.resolve, deferred.reject);
        }
        else {
            let stream = res;
            if (res.headers['content-encoding'] === 'gzip') {
                stream = stream.pipe(zlib_1.createGunzip());
            }
            deferred.resolve({ req, res, stream });
        }
    });
    req.on('error', (e) => {
        deferred.reject(e);
    });
    if (options.timeout) {
        req.setTimeout(options.timeout);
    }
    if (options.data) {
        req.write(options.data);
    }
    req.end();
    return deferred.promise;
}
exports.requestAsStream = requestAsStream;
function download(filePath, context) {
    let deferred = defer();
    const out = fs.createWriteStream(filePath);
    out.once("finish", () => deferred.resolve());
    context.stream.once("error", (e) => deferred.reject(e));
    context.stream.pipe(out);
    return deferred.promise;
}
exports.download = download;
function asJson(context, ignoreBadStatus = false) {
    let deferred = defer();
    if (!ignoreBadStatus && !isSuccess(context)) {
        deferred.reject(new Error("Bad HTTP status: " + context.res.statusCode));
    }
    if (hasNoContent(context)) {
        deferred.resolve(null);
    }
    const buffer = [];
    context.stream.on("data", (d) => buffer.push(d));
    context.stream.on("end", () => {
        let jsonContent = null;
        try {
            jsonContent = JSON.parse(buffer.join(""));
        }
        catch (e) {
            deferred.reject(new Error("Response doesn't appear to be JSON"));
            return;
        }
        deferred.resolve(jsonContent);
    });
    context.stream.on("error", (e) => {
        deferred.reject(e);
    });
    return deferred.promise;
}
exports.asJson = asJson;
function isSuccess(context) {
    return (context.res.statusCode >= 200 && context.res.statusCode < 300) || context.res.statusCode === 1223;
}
function hasNoContent(context) {
    return context.res.statusCode === 204;
}
function isNumber(obj) {
    if ((typeof (obj) === "number" || obj instanceof Number) && !isNaN(obj)) {
        return true;
    }
    return false;
}
function isBoolean(obj) {
    return obj === true || obj === false;
}
