
header "sys" as sys

use mem
use io
use time

global num2str_buf1 : ?ptr (@undefined)
global num2str_buf2 : ?ptr (@undefined)

+ trait Number {

    + fn to_str() String $to {
        return this.to_base(10)
    }
    + fn to_hex() String {
        return this.to_base(16)
    }

    + fn round_up(modulo: SELF) SELF {
        return this + ((modulo - (this % modulo)) % modulo)
    }
    + fn round_down(modulo: SELF) SELF {
        return this - (this % modulo)
    }

    + fn to_base(base: SELF) String {

        if this == 0 || base == 0 : return "0"

        let buf = num2str_buf1
        if !isset(buf) {
            buf = mem:alloc(64)
            num2str_buf1 = buf
        }
        let len = this.to_base_to_ptr(base, buf)

        return String.make_from_ptr(buf, len)
    }

    fn to_base_to_ptr(base: SELF, result: ptr, lowercase: bool (false)) uint {

        if base > 16 : base = 16
        #if is_signed_type(SELF)
        if base < 2 : base = 10
        #end
        let base_ = base

        let chars = lowercase ? "0123456789abcdef".data : "0123456789ABCDEF".data

        let rev = num2str_buf2
        if !isset(rev) {
            rev = mem:alloc(64)
            num2str_buf2 = rev
        }

        let val = this
        let negative = false
        #if is_signed_type(SELF)
        if val < 0 {
            negative = true
            val = val * -1
        }
        #end

        let i : uint = 0
        while val > 0 {
            @ptrv(rev, u8, i) = @ptrv(chars, u8, (val % base_))
            i++
            val = val / base_
        }
        // Set len
        if i == 0 {
            @ptrv(result, u8, 0) = '0'
            // @ptrv(result, u8, 1) = 0
            return 1
        }
        let len = i
        #if is_signed_type(SELF)
        if negative {
            len++
            @ptrv(result, u8, 0) = '-'
        }
        #end
        // Reverse
        let amount = i
        let offset : uint = negative ? 1 : 0
        while i-- > 0 {
            @ptrv(result, u8, i + offset) = @ptrv(rev, u8, amount - i - 1)
        }
        // @ptrv(result, u8, len) = 0

        return len
    }

    + fn character_length(base: SELF) uint {
        if this == 0 : return 1
        let val = this
        let i : uint = 0
        while val > 0 {
            i++
            val = val / base
        }
        return i
    }

    + fn print(base: SELF) {

        let buf = num2str_buf1
        if !isset(buf) { 
            buf = mem:alloc(64)
            num2str_buf1 = buf
        }
        let len = this.to_base_to_ptr(base, buf)

        io:write_bytes(1, buf, len) !? 0
    }

    + fn equals_str(str: String) bool $eq {
        #if is_signed_type(SELF)
        let v = str.to_int() ! return false
        #else
        let v = str.to_uint() ! return false
        #end
        return this == v
    }

    + static fn write_little_endian(v: SELF, to: ptr[u8 x size_of(SELF)]) {
        let i = 0
        while i < size_of(SELF) {
            to[i++] = (v & 0xFF).to(u8)
            v = v >> 8
        }
    }
    + static fn write_big_endian(v: SELF, to: ptr[u8 x size_of(SELF)]) {
        let i = size_of(SELF)
        while i-- > 0 {
            to[i] = (v & 0xFF).to(u8)
            v = v >> 8
        }
    }

    + static fn read_little_endian(from: ptr[u8 x size_of(SELF)]) SELF {
        let v : SELF = 0
        let sh : SELF = 0
        let i = 0
        while i < size_of(SELF) {
            v = v | (from[i++].to(SELF) << sh)
            sh += 8
        }
        return v
    }
    + static fn read_big_endian(from: ptr[u8 x size_of(SELF)]) SELF {
        let v : SELF = 0
        let sh : SELF = 0
        let i = size_of(SELF)
        while i-- > 0 {
            v = v | (from[i].to(SELF) << sh)
            sh += 8
        }
        return v
    }

    + static fn random() SELF {
        sys:srand(time:microtime().to(u32))
        let v : [i32 x 2] = @undefined
        v[0] = sys:rand()
        v[1] = sys:rand()
        return @ptrv(v, u64).to(SELF)
    }
}
