"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs = require("fs");
const node_fetch_1 = require("node-fetch");
const errors_1 = require("./errors");
/**
 * Promisified fs.readFile
 */
function readFile(file) {
    return __awaiter(this, void 0, void 0, function* () {
        return promiseCallback(callback => fs.readFile(file, 'utf8', callback));
    });
}
exports.readFile = readFile;
/**
 * Promisified fs.writeFile
 */
function writeFile(file, contents) {
    return __awaiter(this, void 0, void 0, function* () {
        return promiseCallback(callback => fs.writeFile(file, contents, callback));
    });
}
exports.writeFile = writeFile;
/**
 * Copies a file from one place to another.
 */
function copy(source, destination) {
    return __awaiter(this, void 0, void 0, function* () {
        return new Promise((resolve, reject) => {
            fs
                .createReadStream(source)
                .pipe(fs.createWriteStream(destination))
                .on('error', reject)
                .on('close', resolve);
        });
    });
}
exports.copy = copy;
/**
 * Promisified fs.exists
 */
function exists(file) {
    return __awaiter(this, void 0, void 0, function* () {
        return new Promise(resolve => {
            fs.exists(file, resolve);
        });
    });
}
exports.exists = exists;
/**
 * Promisified fs.readDir
 */
function readDir(dir) {
    return __awaiter(this, void 0, void 0, function* () {
        return promiseCallback(callback => fs.readdir(dir, callback));
    });
}
exports.readDir = readDir;
/**
 * Takes a function that wants a callback, and resolves when that callback
 * is resolved upon or errored.
 */
function promiseCallback(fn) {
    return __awaiter(this, void 0, void 0, function* () {
        return new Promise((resolve, reject) => {
            fn((err, result) => {
                if (err) {
                    reject(err);
                }
                else {
                    resolve(result);
                }
            });
        });
    });
}
exports.promiseCallback = promiseCallback;
/**
 * Waits for the child process to exit. Buffers output and throws it on a
 * non-zero exit code.
 */
function awaitChildProcess(child) {
    return __awaiter(this, void 0, void 0, function* () {
        const stdAll = [];
        child.stdout.on('data', data => {
            stdAll.push(data);
        });
        child.stderr.on('data', data => {
            stdAll.push(data);
        });
        yield new Promise((resolve, reject) => {
            child.on('close', code => {
                if (code === 0) {
                    resolve();
                    return;
                }
                const output = Buffer.concat(stdAll.map(s => (typeof s === 'string' ? Buffer.from(s) : s)));
                reject(new errors_1.SubprocessError(output.toString('utf8')));
            });
        });
    });
}
exports.awaitChildProcess = awaitChildProcess;
/**
 * Returns a promise that resolves after the given duration.
 */
function delay(duration) {
    return __awaiter(this, void 0, void 0, function* () {
        return new Promise(resolve => setTimeout(resolve, duration)); // tslint:disable-line
    });
}
exports.delay = delay;
/**
 * Returns a promise that never resolves.
 */
function never() {
    return __awaiter(this, void 0, void 0, function* () {
        return new Promise(() => { }); // tslint:disable-line
    });
}
exports.never = never;
/**
 * Wraps the error message, preserving its original stacktrace and adding
 * extra information.
 */
function wrapErr(err, message) {
    err.message = `${message}: ${err.message}`;
    return err;
}
exports.wrapErr = wrapErr;
/**
 * Returns a path to the Mixer API.
 */
function api() {
    return process.env.MIIX_API || 'https://mixer.com';
}
exports.api = api;
/**
 * Fetcher is a concrete, node-fetch-based implementation of IRequester.
 */
class Fetcher {
    constructor(policies = [], host = `${api()}/api/v1`, fetch = node_fetch_1.default) {
        this.policies = policies;
        this.host = host;
        this.fetch = fetch;
    }
    /**
     * Returns a new fetcher with the provided policies applied.
     */
    with(...policies) {
        return new Fetcher(this.policies.concat(policies), this.host, this.fetch);
    }
    /**
     * Makes a request with a JSON body to the give path.
     */
    json(method, path, body) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.run(path, {
                method: method.toUpperCase(),
                headers: {
                    'content-type': 'application/json',
                    accept: 'application/json',
                },
                body: JSON.stringify(body),
            });
        });
    }
    /**
     * Makes a generic HTTP request to the given path.
     */
    run(path, init = {}) {
        return __awaiter(this, void 0, void 0, function* () {
            init.headers = init.headers || {};
            if (process.env.MIIX_HEADERS) {
                Object.assign(init.headers, JSON.parse(process.env.MIIX_HEADERS));
            }
            this.policies.forEach(p => Object.assign(init.headers, p.header()));
            return this.fetch(`${this.host}${path}`, init);
        });
    }
}
exports.Fetcher = Fetcher;
