// Multiple precision decimal numbers
// NOTE: This is only for floating point printing and nothing else
package strconv_decimal

Decimal :: struct {
	digits:        [384]byte, // big-endian digits
	count:         int,
	decimal_point: int,
	neg, trunc:    bool,
}

decimal_to_string :: proc(buf: []byte, a: ^Decimal) -> string {
	digit_zero :: proc(buf: []byte) -> int {
		for _, i in buf {
			buf[i] = '0'
		}
		return len(buf)
	}


	n := 10 + a.count + abs(a.decimal_point)

	// TODO(bill): make this work with a buffer that's not big enough
	assert(len(buf) >= n)
	b := buf[0:n]

	if a.count == 0 {
		b[0] = '0'
		return string(b[0:1])
	}

	w := 0
	if a.decimal_point <= 0 {
		b[w] = '0'; w += 1
		b[w] = '.'; w += 1
		w += digit_zero(b[w : w-a.decimal_point])
		w += copy(b[w:], a.digits[0:a.count])
	} else if a.decimal_point < a.count {
		w += copy(b[w:], a.digits[0:a.decimal_point])
		b[w] = '.'; w += 1
		w += copy(b[w:], a.digits[a.decimal_point : a.count])
	} else {
		w += copy(b[w:], a.digits[0:a.count])
		w += digit_zero(b[w : w+a.decimal_point-a.count])
	}

	return string(b[0:w])
}

// trim trailing zeros
trim :: proc(a: ^Decimal) {
	for a.count > 0 && a.digits[a.count-1] == '0' {
		a.count -= 1
	}
	if a.count == 0 {
		a.decimal_point = 0
	}
}


assign :: proc(a: ^Decimal, idx: u64) {
	buf: [64]byte
	n := 0
	for i := idx; i > 0;  {
		j := i/10
		i -= 10*j
		buf[n] = byte('0'+i)
		n += 1
		i = j
	}

	a.count = 0
	for n -= 1; n >= 0; n -= 1 {
		a.digits[a.count] = buf[n]
		a.count += 1
	}
	a.decimal_point = a.count
	trim(a)
}



shift_right :: proc(a: ^Decimal, k: uint) {
	r := 0 // read index
	w := 0 // write index

	n: uint
	for ; n>>k == 0; r += 1 {
		if r >= a.count {
			if n == 0 {
				// Just in case
				a.count = 0
				return
			}
			for n>>k == 0 {
				n = n * 10
				r += 1
			}
			break
		}
		c := uint(a.digits[r])
		n = n*10 + c - '0'
	}
	a.decimal_point -= r-1

	mask: uint = (1<<k) - 1

	for ; r < a.count; r += 1 {
		c := uint(a.digits[r])
		dig := n>>k
		n &= mask
		a.digits[w] = byte('0' + dig)
		w += 1
		n = n*10 + c - '0'
	}

	for n > 0 {
		dig := n>>k
		n &= mask
		if w < len(a.digits) {
			a.digits[w] = byte('0' + dig)
			w += 1
		} else if dig > 0 {
			a.trunc = true
		}
		n *= 10
	}


	a.count = w
	trim(a)
}

shift_left :: proc(a: ^Decimal, k: uint) {
	// NOTE(bill): used to determine buffer size required for the decimal from the binary shift
	// 'k' means `1<<k` == `2^k` which equates to roundup(k*log10(2)) digits required
	log10_2 :: 0.301029995663981195213738894724493026768189881462108541310
	capacity := int(f64(k)*log10_2 + 1)

	r := a.count          // read index
	w := a.count+capacity // write index

	d := len(a.digits)

	n: uint
	for r -= 1; r >= 0; r -= 1 {
		n += (uint(a.digits[r]) - '0') << k
		quo := n/10
		rem := n - 10*quo
		w -= 1
		if w < d {
			a.digits[w] = byte('0' + rem)
		} else if rem != 0 {
			a.trunc = true
		}
		n = quo
	}

	for n > 0 {
		quo := n/10
		rem := n - 10*quo
		w -= 1
		if w < d {
			a.digits[w] = byte('0' + rem)
		} else if rem != 0 {
			a.trunc = true
		}
		n = quo
	}

	// NOTE(bill): Remove unused buffer size
	assert(w >= 0)
	capacity -= w

	a.count = min(a.count+capacity, d)
	a.decimal_point += capacity
	trim(a)
}

shift :: proc(a: ^Decimal, i: int) {
	uint_size :: 8*size_of(uint)
	max_shift :: uint_size-4

	switch k := i; {
	case a.count == 0:
		// no need to update
	case k > 0:
		for k > max_shift {
			shift_left(a, max_shift)
			k -= max_shift
		}
		shift_left(a, uint(k))


	case k < 0:
		for k < -max_shift {
			shift_right(a, max_shift)
			k += max_shift
		}
		shift_right(a, uint(-k))
	}
}

can_round_up :: proc(a: ^Decimal, nd: int) -> bool {
	if nd < 0 || nd >= a.count { return false  }
	if a.digits[nd] == '5' && nd+1 == a.count {
		if a.trunc {
			return true
		}
		return nd > 0 && (a.digits[nd-1]-'0')%2 != 0
	}

	return a.digits[nd] >= '5'
}

round :: proc(a: ^Decimal, nd: int) {
	if nd < 0 || nd >= a.count { return }
	if can_round_up(a, nd) {
		round_up(a, nd)
	} else {
		round_down(a, nd)
	}
}

round_up :: proc(a: ^Decimal, nd: int) {
	if nd < 0 || nd >= a.count { return }

	for i := nd-1; i >= 0; i -= 1 {
		if c := a.digits[i]; c < '9' {
			a.digits[i] += 1
			a.count = i+1
			return
		}
	}

	// Number is just 9s
	a.digits[0] = '1'
	a.count = 1
	a.decimal_point += 1
}

round_down :: proc(a: ^Decimal, nd: int) {
	if nd < 0 || nd >= a.count { return }
	a.count = nd
	trim(a)
}


// Extract integer part, rounded appropriately. There are no guarantees about overflow.
rounded_integer :: proc(a: ^Decimal) -> u64 {
	if a.decimal_point > 20 {
		return 0xffff_ffff_ffff_ffff
	}
	i: int = 0
	n: u64 = 0
	m := min(a.decimal_point, a.count)
	for ; i < m; i += 1 {
		n = n*10 + u64(a.digits[i]-'0')
	}
	for ; i < a.decimal_point; i += 1 {
		n *= 10
	}
	if can_round_up(a, a.decimal_point) {
		n += 1
	}
	return n
}

