import option;
import std::getopts;
import std::test;
import str;
import vec;
import task;

import core::result;
import result::{ok, err};

import comm::port;
import comm::chan;
import comm::send;
import comm::recv;

import common::config;
import common::mode_run_pass;
import common::mode_run_fail;
import common::mode_compile_fail;
import common::mode_pretty;
import common::mode;
import util::logv;

fn main(args: ~[str]) {
    let config = parse_config(args);
    log_config(config);
    run_tests(config);
}

fn parse_config(args: ~[str]) -> config {
    let opts =
        ~[getopts::reqopt("compile-lib-path"),
          getopts::reqopt("run-lib-path"),
          getopts::reqopt("rustc-path"), getopts::reqopt("src-base"),
          getopts::reqopt("build-base"), getopts::reqopt("aux-base"),
          getopts::reqopt("stage-id"),
          getopts::reqopt("mode"), getopts::optflag("ignored"),
          getopts::optopt("runtool"), getopts::optopt("rustcflags"),
          getopts::optflag("verbose"),
          getopts::optopt("logfile")];

    check (vec::is_not_empty(args));
    let args_ = vec::tail(args);
    let match =
        alt getopts::getopts(args_, opts) {
          ok(m) { m }
          err(f) { fail getopts::fail_str(f) }
        };

    ret {compile_lib_path: getopts::opt_str(match, "compile-lib-path"),
         run_lib_path: getopts::opt_str(match, "run-lib-path"),
         rustc_path: getopts::opt_str(match, "rustc-path"),
         src_base: getopts::opt_str(match, "src-base"),
         build_base: getopts::opt_str(match, "build-base"),
         aux_base: getopts::opt_str(match, "aux-base"),
         stage_id: getopts::opt_str(match, "stage-id"),
         mode: str_mode(getopts::opt_str(match, "mode")),
         run_ignored: getopts::opt_present(match, "ignored"),
         filter:
             if vec::len(match.free) > 0u {
                 option::some(match.free[0])
             } else { option::none },
         logfile: getopts::opt_maybe_str(match, "logfile"),
         runtool: getopts::opt_maybe_str(match, "runtool"),
         rustcflags: getopts::opt_maybe_str(match, "rustcflags"),
         verbose: getopts::opt_present(match, "verbose")};
}

fn log_config(config: config) {
    let c = config;
    logv(c, #fmt["configuration:"]);
    logv(c, #fmt["compile_lib_path: %s", config.compile_lib_path]);
    logv(c, #fmt["run_lib_path: %s", config.run_lib_path]);
    logv(c, #fmt["rustc_path: %s", config.rustc_path]);
    logv(c, #fmt["src_base: %s", config.src_base]);
    logv(c, #fmt["build_base: %s", config.build_base]);
    logv(c, #fmt["stage_id: %s", config.stage_id]);
    logv(c, #fmt["mode: %s", mode_str(config.mode)]);
    logv(c, #fmt["run_ignored: %b", config.run_ignored]);
    logv(c, #fmt["filter: %s", opt_str(config.filter)]);
    logv(c, #fmt["runtool: %s", opt_str(config.runtool)]);
    logv(c, #fmt["rustcflags: %s", opt_str(config.rustcflags)]);
    logv(c, #fmt["verbose: %b", config.verbose]);
    logv(c, #fmt["\n"]);
}

fn opt_str(maybestr: option<str>) -> str {
    alt maybestr { option::some(s) { s } option::none { "(none)" } }
}

fn str_opt(maybestr: str) -> option<str> {
    if maybestr != "(none)" { option::some(maybestr) } else { option::none }
}

fn str_mode(s: str) -> mode {
    alt s {
      "compile-fail" { mode_compile_fail }
      "run-fail" { mode_run_fail }
      "run-pass" { mode_run_pass }
      "pretty" { mode_pretty }
      _ { fail "invalid mode" }
    }
}

fn mode_str(mode: mode) -> str {
    alt mode {
      mode_compile_fail { "compile-fail" }
      mode_run_fail { "run-fail" }
      mode_run_pass { "run-pass" }
      mode_pretty { "pretty" }
    }
}

fn run_tests(config: config) {
    let opts = test_opts(config);
    let tests = make_tests(config);
    let res = test::run_tests_console(opts, tests);
    if !res { fail "Some tests failed"; }
}

fn test_opts(config: config) -> test::test_opts {
    {filter:
         alt config.filter {
           option::some(s) { option::some(s) }
           option::none { option::none }
         },
     run_ignored: config.run_ignored,
     logfile:
         alt config.logfile {
           option::some(s) { option::some(s) }
           option::none { option::none }
         }
    }
}

fn make_tests(config: config) -> ~[test::test_desc] {
    #debug("making tests from %s", config.src_base);
    let mut tests = ~[];
    for os::list_dir_path(config.src_base).each |file| {
        let file = file;
        #debug("inspecting file %s", file);
        if is_test(config, file) {
            vec::push(tests, make_test(config, file))
        }
    }
    ret tests;
}

fn is_test(config: config, testfile: str) -> bool {
    // Pretty-printer does not work with .rc files yet
    let valid_extensions =
        alt config.mode { mode_pretty { ~[".rs"] } _ { ~[".rc", ".rs"] } };
    let invalid_prefixes = ~[".", "#", "~"];
    let name = path::basename(testfile);

    let mut valid = false;

    for valid_extensions.each |ext| {
        if str::ends_with(name, ext) { valid = true; }
    }

    for invalid_prefixes.each |pre| {
        if str::starts_with(name, pre) { valid = false; }
    }

    ret valid;
}

fn make_test(config: config, testfile: str) ->
   test::test_desc {
    {
        name: make_test_name(config, testfile),
        fn: make_test_closure(config, testfile),
        ignore: header::is_test_ignored(config, testfile),
        should_fail: false
    }
}

fn make_test_name(config: config, testfile: str) -> str {
    #fmt["[%s] %s", mode_str(config.mode), testfile]
}

fn make_test_closure(config: config, testfile: str) -> test::test_fn {
    fn~() { runtest::run(config, copy testfile) }
}

// Local Variables:
// fill-column: 78;
// indent-tabs-mode: nil
// c-basic-offset: 4
// buffer-file-coding-system: utf-8-unix
// End:
