
import { join } from 'path';
import { spawn } from 'child_process';

import { copyAsync, readJsonAsync, chmodAsync } from 'fs-extra-promise';

const debug = require('debug')('build:runner');

import { Downloader } from './Downloader';
import { FFmpegDownloader } from './FFmpegDownloader';
import { BuildConfig } from './config';
import { mergeOptions, findExecutable, findFFmpeg, tmpDir, spawnAsync, extractGeneric } from './util';

interface IRunnerOptions {
    x86?: boolean;
    x64?: boolean;
    chromeApp?: boolean;
    mirror?: string;
    detached?: boolean;
    mute?: boolean;
}

export class Runner {

    public static DEFAULT_OPTIONS: IRunnerOptions = {
        x86: false,
        x64: false,
        chromeApp: false,
        mirror: Downloader.DEFAULT_OPTIONS.mirror,
        detached: false,
        mute: true,
    };

    public options: IRunnerOptions;

    constructor(options: IRunnerOptions = {}, public args: string[]) {

        this.options = mergeOptions(Runner.DEFAULT_OPTIONS, options);

        debug('in constructor', 'args', args);
        debug('in constructor', 'options', this.options);

    }

    public async run(): Promise<number> {

        const platform = process.platform;
        const arch = this.options.x86 || this.options.x64
        ? (this.options.x86 ? 'ia32' : 'x64')
        : process.arch;

        const pkg: any = await readJsonAsync(join(this.args[0], this.options.chromeApp ? 'manifest.json' : 'package.json'));
        const config = new BuildConfig(pkg);

        debug('in run', 'config', config);

        const downloader = new Downloader({
            platform, arch,
            version: config.nwVersion,
            flavor: 'sdk',
            mirror: this.options.mirror,
            useCaches: true,
            showProgress: this.options.mute ? false : true,
        });

        if(!this.options.mute) {
            console.info('Fetching NW.js binary...', {
                platform: downloader.options.platform,
                arch: downloader.options.arch,
                version: downloader.options.version,
                flavor: downloader.options.flavor,
            });
        }

        const runtimeDir = await downloader.fetchAndExtract();

        if(config.ffmpegIntegration) {

            // FIXME: Integrate without overwriting extracted files.
            //await this.integrateFFmpeg(platform, arch, runtimeDir, pkg, config);

            if(!this.options.mute) {
                console.warn('Running with FFmpeg integration is not supported.');
            }

        }

        const executable = await findExecutable(platform, runtimeDir);

        await chmodAsync(executable, 0o555);

        if(!this.options.mute) {
            console.info('Launching NW.js app...');
        }

        const { code, signal } = await spawnAsync(executable, this.args, {
            detached: this.options.detached,
        });

        if(!this.options.mute) {
            if(this.options.detached) {

                console.info('NW.js app detached.');

                await new Promise((resolve, reject) => {
                    setTimeout(resolve, 3000);
                });

            }
            else {
                console.info(`NW.js app exited with ${ code }.`);
            }
        }

        return code;

    }

    protected async integrateFFmpeg(platform: string, arch: string, runtimeDir: string, pkg: any, config: BuildConfig) {

        const downloader = new FFmpegDownloader({
            platform, arch,
            version: config.nwVersion,
            useCaches: true,
            showProgress: this.options.mute ? false : true,
        });

        if(!this.options.mute) {
            console.info('Fetching FFmpeg prebuilt...', {
                platform: downloader.options.platform,
                arch: downloader.options.arch,
                version: downloader.options.version,
            });
        }

        const ffmpegDir = await downloader.fetchAndExtract();

        const src = await findFFmpeg(platform, ffmpegDir);
        const dest = await findFFmpeg(platform, runtimeDir);

        await copyAsync(src, dest);

    }

}
