/**
 * This will require the latest (unreleased) version of `osslsigncode` with pkcs11 patch
 * Build it like this:
 *
 * curl -L http://sourceforge.net/projects/osslsigncode/files/osslsigncode/osslsigncode-1.7.1.tar.gz/download -o osslsigncode.tar.gz
 * tar -zxvf osslsigncode.tar.gz
 * git clone https://git.code.sf.net/p/osslsigncode/osslsigncode osslsigncode-master
 * cp osslsigncode-master/osslsigncode.c osslsigncode-1.7.1/osslsigncode.c
 * rm osslsigncode.tar.gz
 * rm -rf osslsigncode-master
 * cd osslsigncode-1.7.1/
 * export PKG_CONFIG_PATH=/usr/local/opt/openssl/lib/pkgconfig
 * ./configure
 * make
 * sudo cp osslsigncode /usr/local/bin/osslsigncode
 *
 * Install this:
 *  brew install opensc
 *  brew install engine_pkcs11
 *
 * https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Build_Instructions/Signing_an_executable_with_Authenticode
 */

const fs = require('fs');

module.exports = function(grunt) {
    grunt.registerMultiTask(
        'sign-exe',
        'Signs exe file with authenticode certificate',
        async function() {
            const opt = this.options();
            const done = this.async();
            if (opt.pvk) {
                const keytar = require('keytar');
                keytar
                    .getPassword(opt.keytarPasswordService, opt.keytarPasswordAccount)
                    .then(password => {
                        if (!password) {
                            return grunt.warn('Code sign password not found');
                        }
                        const promises = Object.keys(opt.files).map(file =>
                            signFile(file, opt.files[file], opt, password)
                        );
                        Promise.all(promises).then(done);
                    })
                    .catch(e => {
                        grunt.warn('Code sign error: ' + e);
                    });
            } else {
                const sign = require('../util/sign');
                const pin = await sign.getPin();
                for (const file of Object.keys(opt.files)) {
                    await signFile(file, opt.files[file], opt, pin);
                }
                done();
            }
        }
    );

    function signFile(file, name, opt, password) {
        const signedFile = file + '.sign';
        return new Promise((resolve, reject) => {
            const pkcsArgs = opt.pvk
                ? []
                : [
                      '-pkcs11engine',
                      '/usr/local/lib/engines/engine_pkcs11.so',
                      '-pkcs11module',
                      '/usr/local/lib/opensc-pkcs11.so'
                  ];
            const args = [
                '-spc',
                opt.spc,
                '-key',
                opt.pvk ? require('path').resolve(opt.pvk) : opt.key,
                '-pass',
                password,
                '-h',
                opt.algo,
                '-n',
                name,
                '-i',
                opt.url,
                '-t',
                'http://timestamp.verisign.com/scripts/timstamp.dll',
                ...pkcsArgs,
                '-in',
                file,
                '-out',
                signedFile
            ];
            const spawned = grunt.util.spawn(
                {
                    cmd: 'osslsigncode',
                    args
                },
                (error, result, code) => {
                    if (error || code) {
                        spawned.kill();
                        grunt.warn(`Cannot sign file ${file}, signtool error ${code}: ${error}`);
                        return reject();
                    }
                    grunt.util.spawn(
                        {
                            cmd: 'osslsigncode',
                            args: ['verify', signedFile]
                        },
                        (ex, result, code) => {
                            if (code) {
                                grunt.warn(`Verify error ${file}: \n${result.stdout.toString()}`);
                                return;
                            }
                            if (fs.existsSync(file)) {
                                fs.renameSync(signedFile, file);
                            }
                            grunt.log.writeln(`Signed ${file}: ${name}`);
                            resolve();
                        }
                    );
                }
            );
            // spawned.stdout.pipe(process.stdout);
            spawned.stderr.pipe(process.stderr);
            // spawned.stdin.setEncoding('utf-8');
            // spawned.stdin.write(password);
            // spawned.stdin.write('\n');
        });
    }
};
