module strconv

/*

printf/sprintf V implementation

Copyright (c) 2020 Dario Deledda. All rights reserved.
Use of this source code is governed by an MIT license
that can be found in the LICENSE file.

This file contains the printf/sprintf functions

*/

import strings

enum Char_parse_state {
	start
	norm_char
	field_char
	pad_ch
	len_set_start
	len_set_in

	check_type
	check_float
	check_float_in

	reset_params
}

enum Align_text {
	right = 0
	left
	center
}

/*

Float conversion utility

*/
const(
	// rounding value
	dec_round = [
		f64(0.44),
		0.044,
		0.0044,
		0.00044,
		0.000044,
		0.0000044,
		0.00000044,
		0.000000044,
		0.0000000044,
		0.00000000044,
		0.000000000044,
		0.0000000000044,
		0.00000000000044,
		0.000000000000044,
		0.0000000000000044,
		0.00000000000000044,
		0.000000000000000044,
		0.0000000000000000044,
		0.00000000000000000044,
		0.000000000000000000044,
	]
)

// max float 1.797693134862315708145274237317043567981e+308

pub fn f64_to_str_lnd(f f64, dec_digit int) string {
	// we add the rounding value
	s := f64_to_str(f + dec_round[dec_digit], 18)
	// check for +inf -inf Nan
	if s.len > 2 && (s[0] == `n` || s[1] == `i`) {
		return s
	}

	m_sgn_flag := false
	mut sgn        := 1
	mut b          := [26]byte{}
	mut d_pos      := 1
	mut i          := 0
	mut i1         := 0
	mut exp        := 0
	mut exp_sgn    := 1

	mut dot_res_sp := -1

	// get sign and deciaml parts
	for c in s {
		if c == `-` {
			sgn = -1
			i++
		} else if c == `+` {
			sgn = 1
			i++
		}
		else if c >= `0` && c <= `9` {
			b[i1++] = c
			i++
		} else if c == `.` {
			if sgn > 0 {
				d_pos = i
			} else {
				d_pos = i-1
			}
			i++
		} else if c == `e` {
			i++
			break
		} else {
			return "[Float conversion error!!]"
		}
	}
	b[i1] = 0

	// get exponent
	if s[i] == `-` {
		exp_sgn = -1
		i++
	} else if s[i] == `+` {
		exp_sgn = 1
		i++
	}
	for c in s[i..] {
		exp = exp * 10 + int(c-`0`)
	}

	// allocate exp+32 chars for the return string
	//mut res := []byte{len:exp+32,init:`0`}
	mut res := []byte{len: exp+32, init: 0}
	mut r_i := 0  // result string buffer index

	//println("s:${sgn} b:${b[0]} es:${exp_sgn} exp:${exp}")

	if sgn == 1 {
		if m_sgn_flag {
			res[r_i++] = `+`
		}
	} else {
		res[r_i++] = `-`
	}

	i = 0
	if exp_sgn >= 0 {
		for b[i] != 0 {
			res[r_i++] = b[i]
			i++
			if i >= d_pos && exp >= 0 {
				if exp == 0 {
					dot_res_sp = r_i
					res[r_i++] = `.`
				}
				exp--
			}
		}
		for exp >= 0 {
			res[r_i++] = `0`
			exp--
		}
		//println("exp: $exp $r_i $dot_res_sp")
	} else {
		mut dot_p := true
		for exp > 0 {
			res[r_i++] = `0`
			exp--
			if dot_p  {
				dot_res_sp = r_i
				res[r_i++] = `.`
				dot_p = false
			}
		}
		for b[i] != 0 {
			res[r_i++] = b[i]
			i++
		}
	}
	//println("r_i-d_pos: ${r_i - d_pos}")
	if dot_res_sp >= 0 {
		if (r_i - dot_res_sp) > dec_digit {
			r_i = dot_res_sp + dec_digit + 1
		}
		res[r_i] = 0
		//println("result: [${tos(&res[0],r_i)}]")
		return tos(res.data, r_i)
	} else {
		if dec_digit > 0 {
			mut c := 0
			res[r_i++] = `.`
			for c < dec_digit {
				res[r_i++] = `0`
				c++
			}
			res[r_i] = 0
		}
		return tos(res.data, r_i)
	}
}

/*

 Single format functions

*/
pub struct BF_param {
	pad_ch       byte       = byte(` `)     // padding char
	len0         int        = -1      // default len for whole the number or string
	len1         int        = 6       // number of decimal digits, if needed
	positive     bool       = true    // mandatory: the sign of the number passed
	sign_flag    bool                 // flag for print sign as prefix in padding
	allign       Align_text = .right  // alignment of the string
	rm_tail_zero bool                 // remove the tail zeros from floats
}

pub fn format_str(s string, p BF_param) string {
	dif := p.len0 - s.len
	if dif <= 0 {
		return s
	}
	mut res := strings.new_builder(s.len + dif)
	if p.allign == .right {
		for i1 :=0; i1 < dif; i1++ {
			res.write_b(p.pad_ch)
		}
	}
	res.write(s)
	if p.allign == .left {
		for i1 :=0; i1 < dif; i1++ {
			res.write_b(p.pad_ch)
		}
	}
	return res.str()
}

// max int64 9223372036854775807
pub fn format_dec(d u64, p BF_param) string {
	mut s := ""
	mut res := strings.new_builder(20)
	mut sign_len_diff := 0
	if p.pad_ch == `0` {
		if p.positive {
			if p.sign_flag {
				res.write_b(`+`)
				sign_len_diff = -1
			}
		} else {
			res.write_b(`-`)
			sign_len_diff = -1
		}
		s = d.str()
	} else {
		if p.positive {
			if p.sign_flag {
				s = "+" + d.str()
			} else {
				s = d.str()
			}
		} else {
			s = "-" + d.str()
		}
	}
	dif := p.len0 - s.len + sign_len_diff

	if p.allign == .right {
		for i1 :=0; i1 < dif; i1++ {
			res.write_b(p.pad_ch)
		}
	}
	res.write(s)
	if p.allign == .left {
		for i1 :=0; i1 < dif; i1++ {
			res.write_b(p.pad_ch)
		}
	}
	return res.str()
}

pub fn format_fl(f f64, p BF_param) string {
	mut s  := ""
	mut fs := f64_to_str_lnd(if f >= 0.0 {f} else {-f}, p.len1)

	// error!!
	if fs[0] == `[` {
		return fs
	}

	if p.rm_tail_zero {
		fs = remove_tail_zeros(fs)
	}
	mut res := strings.new_builder( if p.len0 > fs.len { p.len0 } else { fs.len })

	mut sign_len_diff := 0
	if p.pad_ch == `0` {
		if p.positive {
			if p.sign_flag {
				res.write_b(`+`)
				sign_len_diff = -1
			}
		} else {
			res.write_b(`-`)
			sign_len_diff = -1
		}
		s = fs
	} else {
		if p.positive {
			if p.sign_flag {
				s = "+" + fs
			} else {
				s = fs
			}
		} else {
			s = "-" + fs
		}
	}

	dif := p.len0 - s.len + sign_len_diff

	if p.allign == .right {
		for i1 :=0; i1 < dif; i1++ {
			res.write_b(p.pad_ch)
		}
	}
	res.write(s)
	if p.allign == .left {
		for i1 :=0; i1 < dif; i1++ {
			res.write_b(p.pad_ch)
		}
	}

	return res.str()
}

pub fn format_es(f f64, p BF_param) string {
	mut s := ""
	mut fs := f64_to_str_pad(if f> 0 {f} else {-f},p.len1)
	if p.rm_tail_zero {
		fs = remove_tail_zeros(fs)
	}
	mut res := strings.new_builder( if p.len0 > fs.len { p.len0 } else { fs.len })

	mut sign_len_diff := 0
	if p.pad_ch == `0` {
		if p.positive {
			if p.sign_flag {
				res.write_b(`+`)
				sign_len_diff = -1
			}
		} else {
			res.write_b(`-`)
			sign_len_diff = -1
		}
		s = fs
	} else {
		if p.positive {
			if p.sign_flag {
				s = "+" + fs
			} else {
				s = fs
			}
		} else {
			s = "-" + fs
		}
	}

	dif := p.len0 - s.len + sign_len_diff
	if p.allign == .right {
		for i1 :=0; i1 < dif; i1++ {
			res.write_b(p.pad_ch)
		}
	}
	res.write(s)
	if p.allign == .left {
		for i1 :=0; i1 < dif; i1++ {
			res.write_b(p.pad_ch)
		}
	}
	return res.str()
}

pub fn remove_tail_zeros(s string) string {
	mut i := 0
	mut last_zero_start := -1
	mut dot_pos         := -1
	mut in_decimal := false
	mut prev_ch := byte(0)
	for i < s.len {
		ch := unsafe {s.str[i]}
		if ch == `.` {
			in_decimal = true
			dot_pos = i
		}
		else if in_decimal {
			if ch == `0` && prev_ch != `0` {
				last_zero_start = i
			} else if ch >= `1` && ch <= `9` {
				last_zero_start = -1
			} else if ch == `e` {
				break
			}
		}
		prev_ch = ch
		i++
	}

	mut tmp := ""
	if last_zero_start > 0 {
		if last_zero_start == dot_pos+1 {
			tmp = s[..dot_pos] + s[i..]
		}else {
			tmp = s[..last_zero_start] + s[i..]
		}
	} else {
		tmp = s
	}
	if unsafe {tmp.str[tmp.len-1]} == `.` {
		return tmp[..tmp.len-1]
	}
	return tmp
}

/*

Main functions

*/
pub fn v_printf(str string, pt ... voidptr) {
	print(v_sprintf(str, pt))
}

pub fn v_sprintf(str string, pt ... voidptr) string{
	mut res := strings.new_builder(pt.len * 16)

	mut i            := 0                // main strign index
	mut p_index      := 0                // parameter index
	mut sign         := false            // sign flag
	mut allign       := Align_text.right
	mut len0         := -1               // forced length, if -1 free length
	mut len1         := -1               // decimal part for floats
	def_len1         := 6                // default value for len1
	mut pad_ch       := byte(` `)              // pad char

	// prefix chars for Length field
	mut ch1 := `0`  // +1 char if present else `0`
	mut ch2 := `0`  // +2 char if present else `0`

	mut status := Char_parse_state.norm_char
	for i < str.len {
		if status == .reset_params {
			sign         = false
			allign       = .right
			len0         = -1
			len1         = -1
			pad_ch       = ` `
			status = .norm_char
			ch1 = `0`
			ch2 = `0`
			continue
		}

		ch := str[i]
		if ch != `%` && status == .norm_char {
			res.write_b(ch)
			i++
			continue
		}
		if ch == `%` && status == .norm_char {
			status = .field_char
			i++
			continue
		}

		// single char, manage it here
		if ch == `c` && status == .field_char {
			v_sprintf_panic(p_index, pt.len)
			d1 := unsafe {*(&byte(pt[p_index]))}
			res.write_b(d1)
			status = .reset_params
			p_index++
			i++
			continue
		}

		// pointer, manage it here
		if ch == `p` && status == .field_char {
			v_sprintf_panic(p_index, pt.len)
			res.write("0x")
			res.write(ptr_str(unsafe {pt[p_index]}))
			status = .reset_params
			p_index++
			i++
			continue
		}

		if status == .field_char {
			mut fc_ch1 := `0`
			mut fc_ch2 := `0`
			if (i + 1) < str.len {
				fc_ch1 = str[i+1]
				if (i + 2) < str.len {
					fc_ch2 = str[i+2]
				}
			}
			if ch == `+` {
				sign = true
				i++
				continue
			} else if ch == `-` {
				allign = .left
				i++
				continue
			} else if ch in [`0`,` `] {
				if allign == .right {
					pad_ch = ch
				}
				i++
				continue
			} else if ch == `'` {
				i++
				continue
			} else if ch == `.` && fc_ch1 >= `1` && fc_ch1 <= `9` {
				status = .check_float
				i++
				continue
			}
			// manage "%.*s" precision field
			else if ch == `.` && fc_ch1 == `*` && fc_ch2 == `s` {
				v_sprintf_panic(p_index, pt.len)
				len := unsafe {*(&int(pt[p_index]))}
				p_index++
				v_sprintf_panic(p_index, pt.len)
				mut s := unsafe {*(&string(pt[p_index]))}
				s = s[..len]
				p_index++
				res.write(s)
				status = .reset_params
				i += 3
				continue
			}
			status = .len_set_start
			continue
		}

		if status == .len_set_start {
			if ch >= `1` && ch <= `9` {
				len0 = int(ch - `0`)
				status = .len_set_in
				i++
				continue
			}
			if ch == `.` {
				status = .check_float
				i++
				continue
			}
			status = .check_type
			continue
		}

		if status == .len_set_in {
			if ch >= `0` && ch <= `9` {
				len0 *= 10
				len0 += int(ch - `0`)
				i++
				continue
			}
			if ch == `.` {
				status = .check_float
				i++
				continue
			}
			status = .check_type
			continue
		}

		if status == .check_float {
			if ch >= `0` && ch <= `9` {
				len1 = int(ch - `0`)
				status = .check_float_in
				i++
				continue
			}
			status = .check_type
			continue
		}

		if status == .check_float_in {
			if ch >= `0` && ch <= `9` {
				len1 *= 10
				len1 += int(ch - `0`)
				i++
				continue
			}
			status = .check_type
			continue
		}

		if status == .check_type {

			if ch == `l` {
				if ch1 == `0` {
					ch1 = `l`
					i++
					continue
				} else {
					ch2 = `l`
					i++
					continue
				}
			}
			else if ch == `h` {
				if ch1 == `0` {
					ch1 = `h`
					i++
					continue
				} else {
					ch2 = `h`
					i++
					continue
				}
			}

			// signed integer
			else if ch in [`d`,`i`] {
				mut d1 := u64(0)
				mut positive := true

				//println("$ch1 $ch2")
				match ch1 {
					// h for 16 bit int
					// hh fot 8 bit int
					`h` {
						if ch2 == `h` {
							v_sprintf_panic(p_index, pt.len)
							x := unsafe {*(&i8(pt[p_index]))}
							positive = if x >= 0 { true } else { false }
							d1 = if positive { u64(x) } else { u64(-x) }
						} else {
							x := unsafe {*(&i16(pt[p_index]))}
							positive = if x >= 0 { true } else { false }
							d1 = if positive { u64(x) } else { u64(-x) }
						}
					}
					// l  i64
					// ll i64 for now
					`l` {
						// placeholder for future 128bit integer code
						/*
						if ch2 == `l` {
							v_sprintf_panic(p_index, pt.len)
							x := *(&i128(pt[p_index]))
							positive = if x >= 0 { true } else { false }
							d1 = if positive { u128(x) } else { u128(-x) }
						} else {
							v_sprintf_panic(p_index, pt.len)
							x := *(&i64(pt[p_index]))
							positive = if x >= 0 { true } else { false }
							d1 = if positive { u64(x) } else { u64(-x) }
						}
						*/
						v_sprintf_panic(p_index, pt.len)
						x := unsafe {*(&i64(pt[p_index]))}
						positive = if x >= 0 { true } else { false }
						d1 = if positive { u64(x) } else { u64(-x) }
					}
					// default int
					else {
						v_sprintf_panic(p_index, pt.len)
						x := unsafe {*(&int(pt[p_index]))}
						positive = if x >= 0 { true } else { false }
						d1 = if positive { u64(x) } else { u64(-x) }
					}

				}
				res.write(format_dec(d1,{pad_ch: pad_ch, len0: len0, len1: 0, positive: positive, sign_flag: sign, allign: allign}))
				status = .reset_params
				p_index++
				i++
				ch1 = `0`
				ch2 = `0`
				continue
			}

			// unsigned integer
			else if ch == `u` {
				mut d1 := u64(0)
				positive := true
				v_sprintf_panic(p_index, pt.len)
				match ch1 {
					// h for 16 bit unsigned int
					// hh fot 8 bit unsigned int
					`h` {
						if ch2 == `h` {
							d1 = u64(unsafe {*(&byte(pt[p_index]))})
						} else {
							d1 = u64(unsafe {*(&u16(pt[p_index]))})
						}
					}
					// l  u64
					// ll u64 for now
					`l` {
						// placeholder for future 128bit integer code
						/*
						if ch2 == `l` {
							d1 = u128(*(&u128(pt[p_index])))
						} else {
							d1 = u64(*(&u64(pt[p_index])))
						}
						*/
						d1 = u64(unsafe {*(&u64(pt[p_index]))})
					}
					// default int
					else {
						d1 = u64(unsafe {*(&u32(pt[p_index]))})
					}
				}

				res.write(format_dec(d1,{pad_ch: pad_ch, len0: len0, len1: 0, positive: positive, sign_flag: sign, allign: allign}))
				status = .reset_params
				p_index++
				i++
				continue
			}

			// hex
			else if ch in [`x`, `X`] {
				v_sprintf_panic(p_index, pt.len)
				mut s := ""
				match ch1 {
					// h for 16 bit int
					// hh fot 8 bit int
					`h` {
						if ch2 == `h` {
							x := unsafe {*(&i8(pt[p_index]))}
							s = x.hex()
						} else {
							x := unsafe {*(&i16(pt[p_index]))}
							s = x.hex()
						}
					}
					// l  i64
					// ll i64 for now
					`l` {
						// placeholder for future 128bit integer code
						/*
						if ch2 == `l` {
							x := *(&i128(pt[p_index]))
							s = x.hex()
						} else {
							x := *(&i64(pt[p_index]))
							s = x.hex()
						}
						*/
						x := unsafe {*(&i64(pt[p_index]))}
						s = x.hex()
					}
					else {
						x := unsafe {*(&int(pt[p_index]))}
						s = x.hex()
					}
				}

				if ch == `X` {
					s = s.to_upper()
				}

				res.write(format_str(s,{pad_ch: pad_ch, len0: len0, len1: 0, positive: true, sign_flag: false, allign: allign}))
				status = .reset_params
				p_index++
				i++
				continue
			}

			// float and double
			if ch in [`f`, `F`] {
				v_sprintf_panic(p_index, pt.len)
				x := unsafe {*(&f64(pt[p_index]))}
				positive := x >= f64(0.0)
				len1 = if len1 >= 0 { len1 } else { def_len1 }
				s := format_fl(f64(x), {pad_ch: pad_ch, len0: len0, len1: len1, positive: positive, sign_flag: sign, allign: allign})
				res.write(if ch == `F` {s.to_upper()} else {s})
				status = .reset_params
				p_index++
				i++
				continue
			}
			else if ch in [`e`, `E`] {
				v_sprintf_panic(p_index, pt.len)
				x := unsafe {*(&f64(pt[p_index]))}
				positive := x >= f64(0.0)
				len1 = if len1 >= 0 { len1 } else { def_len1 }
				s := format_es(f64(x), {pad_ch: pad_ch, len0: len0, len1: len1, positive: positive, sign_flag: sign, allign: allign})
				res.write(if ch == `E` {s.to_upper()} else {s})
				status = .reset_params
				p_index++
				i++
				continue
			}
			else if ch in [`g`, `G`] {
				v_sprintf_panic(p_index, pt.len)
				x := unsafe {*(&f64(pt[p_index]))}
				positive := x >= f64(0.0)
				mut s := ""
				tx := fabs(x)
				if tx < 999_999.0 && tx >= 0.00001 {
					//println("Here g format_fl [$tx]")
					len1 = if len1 >= 0 { len1+1 } else { def_len1 }
					s = format_fl(x, {pad_ch: pad_ch, len0: len0, len1: len1, positive: positive, sign_flag: sign, allign: allign, rm_tail_zero: true})
				} else {
					len1 = if len1 >= 0 { len1+1 } else { def_len1 }
					s = format_es(x, {pad_ch: pad_ch, len0: len0, len1: len1, positive: positive, sign_flag: sign, allign: allign, rm_tail_zero: true})
				}
				res.write(if ch == `G` {s.to_upper()} else {s})
				status = .reset_params
				p_index++
				i++
				continue
			}

			// string
			else if ch == `s` {
				v_sprintf_panic(p_index, pt.len)
				s1 := unsafe{*(&string(pt[p_index]))}
				pad_ch = ` `
				res.write(format_str(s1, {pad_ch: pad_ch, len0: len0, len1: 0, positive: true, sign_flag: false, allign: allign}))
				status = .reset_params
				p_index++
				i++
				continue
			}
		}

		status = .reset_params
		p_index++
		i++
	}

	if p_index != pt.len {
		panic('${p_index} % conversion specifiers, but given ${pt.len} args')
	}

	return res.str()
}

[inline]
fn v_sprintf_panic(idx int, len int) {
	if idx >= len {
		panic('${idx+1} % conversion specifiers, but given only ${len} args')
	}
}

fn fabs(x f64) f64 {
	if x < 0.0 {
		return -x
	}
	return x
}