"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 FFmpeg = require("fluent-ffmpeg");
const fs = require("fs");
const mkdirp = require("mkdirp");
const path = require("path");
const config_1 = require("../../config");
const util = require("util");
const sha1 = require("sha1");
const qs = require("qs");
const storyboard_1 = require("storyboard");
const assert = require("assert");
const ENCODERS = {
    mp3: 'libmp3lame'
};
exports.ENCODER_PRESETS = {
    low: {
        format: 'mp3',
        quality: 4,
        name: 'low',
        cache_size: 128,
    },
    normal: {
        format: 'mp3',
        quality: 2,
        cache_size: 64,
        name: 'string'
    },
    high: {
        format: 'mp3',
        quality: 0,
        cache_size: 16,
        name: 'high'
    },
    original: {
        format: 'original',
        name: 'original'
    }
};
class Encoder {
    constructor(quality) {
        this.listeners = {};
        this.cache = {};
        this.hashCash = {};
        this.opts = quality;
        this.loadCache();
    }
    setEncoderOptions(opts) {
        this.opts = opts;
        this.cache[opts.name] = this.cache[opts.name] || [];
    }
    createCache() {
        return new Promise((resolve) => {
            const folder = path.join(config_1.default.get('dataDirectory'), 'cache');
            mkdirp(folder, (err) => {
                resolve();
            });
        });
    }
    on(event, handler) {
        this.listeners[event] = [].concat(this.listeners[event] || [], handler);
    }
    getCacheFolder() {
        return path.join(config_1.default.get('dataDirectory'), 'cache');
    }
    getCacheFile(filename) {
        return path.join(config_1.default.get('dataDirectory'), 'cache', filename);
    }
    getCache(input) {
        assert(this.opts.format !== 'original');
        const { name } = this.opts;
        const id = input + '?' + qs.stringify(this.opts);
        storyboard_1.mainStory.info('encoder', 'loading cache ' + id);
        const hash = this.hashCash[id] ? this.hashCash[id] : name + '-' + sha1(id);
        this.hashCash[id] = hash;
        if (this.cache[name].includes(hash)) {
            const pos = this.cache[name].indexOf(hash);
            // Send cache[name] entry to top
            this.cache[name].splice(pos, 1);
            this.cache[name].unshift(hash);
            return [this.getCacheFile(hash), this.getCacheFile(hash)];
        }
        else {
            this.cache[name].unshift(hash);
            // if the max cache[name] length is reached, remove the least used entry
            if (this.cache[name].length > this.opts.cache_size) {
                const [removed] = this.cache[name].splice(-1, 1);
                fs.unlinkSync(this.getCacheFile(removed));
            }
            return [null, this.getCacheFile(hash)];
        }
    }
    loadCache() {
        return __awaiter(this, void 0, void 0, function* () {
            const readdir = util.promisify(fs.readdir);
            yield this.createCache();
            const files = yield readdir(this.getCacheFolder());
            files.forEach((file) => {
                const [name, hash] = file.split('-');
                this.cache[name] = (this.cache[name] || []).concat(file);
            });
        });
    }
    run(file, out) {
        const readFile = util.promisify(fs.readFile);
        const writeFile = util.promisify(fs.writeFile);
        if (this.opts.format === 'original') {
            if (!out) {
                return readFile(file);
            }
            if (typeof out == 'string') {
                return readFile(file).then((buffer) => {
                    writeFile(out, buffer);
                });
            }
            else {
                fs.createReadStream(file).pipe(out);
                return;
            }
        }
        const [cached, target] = this.getCache(file);
        if (cached) {
            if (!out) {
                return readFile(cached);
            }
            if (typeof out == 'string') {
                return readFile(cached).then((buffer) => {
                    writeFile(out, buffer);
                });
            }
            else {
                fs.createReadStream(cached).pipe(out);
            }
        }
        const command = FFmpeg(file, {});
        command.audioCodec(ENCODERS[this.opts.format]);
        command.format(this.opts.format);
        Object.keys(this.listeners).forEach((event) => {
            this.listeners[event].forEach((handler) => {
                console.log(event, handler);
                command.on(event, handler);
            });
        });
        if (this.opts.bitrate) {
            command.audioBitrate(this.opts.bitrate);
        }
        else {
            command.audioQuality(this.opts.quality);
        }
        if (!out) {
            return this.createCache().then(() => {
                return new Promise((resolve, reject) => {
                    command.on('end', () => {
                        readFile(target).then((buffer) => {
                            resolve(buffer);
                        }).catch((err) => {
                            reject(err);
                        });
                    });
                    command.save(target);
                });
            });
        }
        if (typeof out == 'string') {
            return new Promise((resolve, reject) => {
                command.on('end', () => {
                    readFile(target).then((buffer) => {
                        return writeFile(out, buffer);
                    }).then(() => {
                        resolve();
                    }).catch((err) => {
                        reject(err);
                    });
                });
                command.save(target);
            });
        }
        else {
            command.pipe(out);
        }
    }
}
Encoder.INSTANCE = new Encoder(exports.ENCODER_PRESETS.normal);
Encoder.MAX_CACHE_LENGTH = 64;
exports.default = Encoder;
//# sourceMappingURL=encoder.js.map