#!/usr/bin/env node

var async, exec, fs, summary;

function makeShellString(value) {
    if (typeof value === 'string') {
        // Newlines are tricky
        return value.split(/\n/).map(function (chunk) {
            return JSON.stringify(chunk);
        }).join('"\n"');
    }

    if (typeof value === 'number') {
        return value;
    }

    return 'ERR_CONVERTING';
}

function addToEnvironment(name, value) {
    var result;

    if (Array.isArray(value)) {
        result = [
            '('
        ];
        value.forEach(function (subValue) {
            result.push(makeShellString(subValue));
        });
        result.push(')');

        return name + '=' + result.join(' ');
    }

    // Sometimes the __tag__ property of the code in the lambdas may
    // be missing.  :-(
    if (typeof value === 'object') {
        if (value.__tag__ === 'code' || (value.ruby && value.php && value.perl)) {
            if (value.bash) {
                return name + '() { ' + value.bash + '; }';
            }

            return name + '() { perl -e \'print ((' + value.perl + ')->("\'"$1"\'"))\'; }';
        }
    }


    if (typeof value === 'object') {
        return '# ' + name + ' is an object and will not work in bash';
    }

    if (typeof value === 'boolean') {
        if (value) {
            return name + '="true"';
        }

        return name + '=""';
    }

    return name + '=' + makeShellString(value);
}

function runTest(test, done) {
    var output, partials, script;

    script = [
        '#!/bin/bash'
    ];
    partials = test.partials || {};

    Object.keys(test.data).forEach(function (name) {
        script.push(addToEnvironment(name, test.data[name]));
    });
    script.push('. mo');
    script.push('mo spec-runner/spec-template');
    test.script = script.join('\n');
    async.series([
        function (taskDone) {
            fs.mkdir("spec-runner/", function (err) {
                if (err && err.code !== 'EEXIST') {
                    return taskDone(err);
                }

                taskDone();
            });
        },
        function (taskDone) {
            fs.writeFile('spec-runner/spec-script', test.script, taskDone);
        },
        function (taskDone) {
            fs.writeFile('spec-runner/spec-template', test.template, taskDone);
        },
        function (taskDone) {
            async.eachSeries(Object.keys(partials), function (partialName, partialDone) {
                fs.writeFile('spec-runner/' + partialName, test.partials[partialName], partialDone);
            }, taskDone);
        },
        function (taskDone) {
            exec('bash spec-runner/spec-script', function (err, stdout) {
                if (err) {
                    return taskDone(err);
                }

                output = stdout;
                taskDone();
            });
        },
        function (taskDone) {
            async.eachSeries(Object.keys(partials), function (partialName, partialDone) {
                fs.unlink('spec-runner/' + partialName, partialDone);
            }, taskDone);
        },
        function (taskDone) {
            fs.unlink('spec-runner/spec-script', taskDone);
        },
        function (taskDone) {
            fs.unlink('spec-runner/spec-template', taskDone);
        },
        function (taskDone) {
            fs.rmdir('spec-runner/', taskDone);
        }
    ], function (err) {
        if (err) {
            return done(err);
        }
        
        done(null, output);
    });
    
    return '';
}

function prepareAndRunTest(test, done) {
    async.waterfall([
        function (taskDone) {
            console.log('### ' + test.name);
            console.log('');
            console.log(test.desc);
            console.log('');
            runTest(test, taskDone);
        },
        function (actual, taskDone) {
            test.actual = actual;
            test.pass = (test.actual === test.expected);

            if (test.pass) {
                console.log('Passed.');
            } else {
                console.log('Failed.');
                console.log('');
                console.log(test);
            }

            console.log('');
            taskDone();
        }
    ], done);
}

function specFileToName(file) {
    return file.replace(/.*\//, '').replace('.json', '').replace('~', '').replace(/(^|-)[a-z]/g, function (match) {
        return match.toUpperCase();
    });
}

function processSpecFile(specFile, done) {
    fs.readFile(specFile, 'utf8', function (err, data) {
        var name;

        if (err) {
            return done(err);
        }

        name = specFileToName(specFile);
        data = JSON.parse(data);
        console.log(name);
        console.log('====================');
        console.log('');
        console.log(data.overview);
        console.log('');
        console.log('Tests');
        console.log('-----');
        console.log('');
        async.series([
            function (taskDone) {
                async.eachSeries(data.tests, prepareAndRunTest, taskDone);
            },
            function (taskDone) {
                summary[name] = {};
                data.tests.forEach(function (test) {
                    summary[name][test.name] = test.pass;
                });
                taskDone();
            }
        ], done);
    });
}

// 0 = node, 1 = script, 2 = file
if (process.argv.length < 3) {
    console.log('Specify one or more JSON spec files on the command line');
    process.exit();
}

async = require('async');
fs = require('fs');
exec = require('child_process').exec;
summary = {};
async.eachSeries(process.argv.slice(2), processSpecFile, function () {
    var fail, pass;

    console.log('');
    console.log('Summary');
    console.log('=======');
    console.log('');
    pass = 0;
    fail = 0;
    Object.keys(summary).forEach(function (name) {
        var groupPass, groupFail, testResults;
       
        testResults = [];
        groupPass = 0;
        groupFail = 0;
        Object.keys(summary[name]).forEach(function (testName) {
            if (summary[name][testName]) {
                testResults.push('    * pass - ' + testName);
                groupPass += 1;
                pass += 1;
            } else {
                testResults.push('    * FAIL - ' + testName);
                groupFail += 1;
                fail += 1;
            }
        });
        testResults.unshift('* ' + name + ' (failed ' + groupFail + ' out of ' + (groupPass + groupFail) + ' tests)');
        console.log(testResults.join('\n'));
    });

    console.log('');
    console.log('Failed ' + fail + ' out of ' + (pass + fail) + ' tests');
});