/*!
 * Nodeunit
 * Copyright (c) 2010 Caolan McMahon
 * MIT Licensed
 */

/**
 * Module dependencies
 */

var nodeunit = require('../nodeunit'),
    utils = require('../utils'),
    fs = require('fs'),
    track = require('../track'),
    path = require('path'),
    AssertionError = require('../assert').AssertionError;

/**
 * Reporter info string
 */

exports.info = "Nested test reporter";


/**
 * Run all tests within each module, reporting the results to the command-line.
 *
 * @param {Array} files
 * @api public
 */

exports.run = function (files, options, callback) {

    if (!options) {
        // load default options
        var content = fs.readFileSync(
            __dirname + '/../../bin/nodeunit.json',
            'utf8'
        );
        options = JSON.parse(content);
    }

    var error = function (str) {
        return options.error_prefix + str + options.error_suffix;
    };
    var ok    = function (str) {
        return options.ok_prefix + str + options.ok_suffix;
    };
    var bold  = function (str) {
        return options.bold_prefix + str + options.bold_suffix;
    };
    var assertion_message = function (str) {
        return options.assertion_prefix + str + options.assertion_suffix;
    };

    var spaces_per_indent = options.spaces_per_indent || 4;

    var start = new Date().getTime();
    var paths = files.map(function (p) {
        return path.resolve(p);
    });
    var tracker = track.createTracker(function (tracker) {
        var i, names;
        if (tracker.unfinished()) {
            console.log('');
            console.log(error(bold(
                'FAILURES: Undone tests (or their setups/teardowns): '
            )));
            names = tracker.names();
            for (i = 0; i < names.length; i += 1) {
                console.log('- ' + names[i]);
            }
            console.log('');
            console.log('To fix this, make sure all tests call test.done()');
            process.reallyExit(tracker.unfinished());
        }
    });

    // Object to hold status of each 'part' of the testCase/name array,
    // i.e., whether this part has been printed yet.
    tracker.already_printed = {};

    var pass_text = function (txt) {
        // Print in bold green.
        return bold(ok(txt + " (pass)"));
    };

    var fail_text = function (txt) {
        return bold(error(txt + " (fail) ✖ "));
    };

    var status_text = function (txt, status) {
        if (status === 'pass') {
            return pass_text(txt);
        } else {
            return fail_text(txt);
        }
    };

    /**
     *  Slices an array, returns a string by joining the sliced elements.
     *  @example
     *   > name_slice(['TC1', 'TC1.1', 'mytest'], 1);
     *   "TC1,TC1.1"
     */
    var name_slice = function (name_arr, end_index) {
        return name_arr.slice(0, end_index + 1).join(",");
    };

    var indent = (function () {
        var txt = '';
        var i;
        for (i = 0; i < spaces_per_indent; i++) {
            txt += ' ';
        }
        return txt;
    }());

    // Indent once for each indent_level
    var add_indent = function (txt, indent_level) {
        var k;
        for (k = 0; k < indent_level; k++) {
            txt += indent;
        }
        return txt;
    };

    // If it's not the last element of the name_arr, it's a testCase.
    var is_testCase = function (name_arr, index) {
        return index === name_arr.length - 1 ? false : true;
    };

    var testCase_line = function (txt) {
        return txt + "\n";
    };

    /**
     * Prints (console.log) the nested test status line(s).
     *
     * @param {Array} name_arr - Array of name elements.
     * @param {String} status - either 'pass' or 'fail'.
     * @example
     *   > print_status(['TC1', 'TC1.1', 'mytest'], 'pass');
     *   TC1
     *      TC1.1
     *         mytest (pass)
     */
    var print_status = function (name_arr, status) {
        var txt = '';
        var _name_slice, part, i;
        for (i = 0; i < name_arr.length; i++) {
            _name_slice = name_slice(name_arr, i);
            part = name_arr[i];
            if (!tracker.already_printed[_name_slice]) {
                txt = add_indent(txt, i);
                if (is_testCase(name_arr, i)) {
                    txt += testCase_line(part);
                } else {
                    txt += status_text(part, status);
                }
                tracker.already_printed[_name_slice] = true;
            }
        }
        console.log(txt);
    };

    nodeunit.runFiles(paths, {
        testspec: options.testspec,
        testFullSpec: options.testFullSpec,
        moduleStart: function (name) {
            console.log('\n' + bold(name));
        },
        testDone: function (name, assertions) {
            tracker.remove(name);

            if (!assertions.failures()) {
                print_status(name, 'pass');
            } else {
                print_status(name, 'fail');
                assertions.forEach(function (a) {
                    if (a.failed()) {
                        a = utils.betterErrors(a);
                        if (a.error instanceof AssertionError && a.message) {
                            console.log(
                                'Assertion Message: ' +
                                    assertion_message(a.message)
                            );
                        }
                        console.log(a.error.stack + '\n');
                    }
                });
            }
        },
        done: function (assertions, end) {
            end = end || new Date().getTime();
            var duration = end - start;
            if (assertions.failures()) {
                console.log(
                    '\n' + bold(error('FAILURES: ')) + assertions.failures() +
                        '/' + assertions.length + ' assertions failed (' +
                        assertions.duration + 'ms)'
                );
            } else {
                console.log(
                    '\n' + bold(ok('OK: ')) + assertions.length +
                        ' assertions (' + assertions.duration + 'ms)'
                );
            }
            
            if (callback) callback(assertions.failures() ? new Error('We have got test failures.') : undefined);
        },
        testStart: function (name) {
            tracker.put(name);
        }
    });
};
