fn main() {
    let ok = test_string_to_int_impl();
    if not ok {
        println("tests failed!");
    }
}

fn test_string_to_int_impl() -> bool {
        test("should convert zero",      assert_int_equal(string_to_int_impl("0"), 0))
    and test("should convert decimal",   assert_int_equal(string_to_int_impl("10"), 10))
    and test("should convert binary",    assert_int_equal(string_to_int_impl("0b110"), 6))
    and test("should convert octal",     assert_int_equal(string_to_int_impl("071"), 57))
    // and test("should convert hex",       assert_int_equal(string_to_int_impl("0xaa"), 170))
    and test("should fail",              assert_int_equal(string_to_int_impl("john"), -1))
    // and test("should fail",              assert_int_equal(string_to_int_impl(""), -1))
}

fn assert_int_equal(value: int, target: int) -> bool {
    if value != target {
        println("assertion failed: " + itos(value) + " != " + itos(target));
        return false;
    }
    true
}

fn test(name: string, assertion: bool) -> bool {
    println(" * test: " + name + " -> " + if assertion { "ok" } else { "failed" });
    assertion
}

fn string_to_int_impl(text: string) -> int {
    let base_2_digits = "01";
    let base_8_digits = base_2_digits + "234567";
    let base_10_digits = base_8_digits + "89";
    let base_16_digits = base_10_digits + "abcdef";

    let len = string_length(text);
    if len == 0 {
        return -1;
    }
    if text[0] == "0"[0] {
        if len == 1 {
            0
        } else if text[1] == "b"[0] {
            parse_digits(string_slice(text, 2, -1), 2, base_2_digits)
        } else if text[1] == "x"[0] {
            parse_digits(string_slice(text, 2, -1), 16, base_16_digits)
        } else {
            parse_digits(string_slice(text, 1, -1), 8, base_8_digits)
        }
    } else {
        parse_digits(text, 10, base_10_digits)
    }
}

fn parse_digits(text: string, base: int, digit_set: string) -> int {
    let val = 0;
    let len = string_length(text);
    for (let i = 0; i < len; i += 1) {
        let ch = text[i];
        if not string_contains(digit_set, ch) {
            return -1;
        }
        val = val * base;
        val += char_val(ch);
    }
    val
}

fn char_val(ch: int) -> int {
    if ch >= "0"[0] and ch <= "9"[0] {
        ch - "0"[0]
    } else if ch >= "a"[0] and ch <= "f"[0] {
        ch - "a"[0] + 10
    } else {
        -1
    }
}

fn string_slice(str: string, from: int, to: int) -> string {
    let result = "";
    let len = string_length(str);
    let abs_to =
        if to >= len { len }
        else if to < 0 { len + to + 1 }
        else { to };
    for (let i = from; i < abs_to; i += 1) {
        result = string_push_char(result, str[i]);
    }
    result
}

fn string_contains(str: string, ch: int) -> bool {
    let len = string_length(str);
    for (let i = 0; i < len; i += 1) {
        if str[i] == ch {
            return true;
        }
    }
    false
}

fn print(msg: string) #[builtin(Print)] {}
fn println(msg: string) { print(msg + "\n") }

fn string_push_char(str: string, value: int) -> string #[builtin(StringPushChar)] {}
fn string_length(str: string) -> int #[builtin(StringLength)] {}

fn itos(number: int) -> string #[builtin(IntToString)] {}