// Copyright (c) 2019-2022 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module json2

import strings

fn write_value(v Any, i int, len int, mut wr strings.Builder) {
	str := v.json_str()
	if v is string {
		wr.write_string('"$str"')
	} else {
		wr.write_string(str)
	}
	if i >= len - 1 {
		return
	}
	wr.write_byte(`,`)
}

// str returns the string representation of the `map[string]Any`.
[manualfree]
pub fn (flds map[string]Any) str() string {
	mut wr := strings.new_builder(200)
	wr.write_byte(`{`)
	mut i := 0
	for k, v in flds {
		wr.write_string('"$k":')
		write_value(v, i, flds.len, mut wr)
		i++
	}
	wr.write_byte(`}`)
	defer {
		unsafe { wr.free() }
	}
	res := wr.str()
	return res
}

// str returns the string representation of the `[]Any`.
[manualfree]
pub fn (flds []Any) str() string {
	mut wr := strings.new_builder(200)
	wr.write_byte(`[`)
	for i, v in flds {
		write_value(v, i, flds.len, mut wr)
	}
	wr.write_byte(`]`)
	defer {
		unsafe { wr.free() }
	}
	res := wr.str()
	return res
}

// str returns the string representation of the `Any` type. Use the `json_str` method
// if you want to use the escaped str() version of the `Any` type.
pub fn (f Any) str() string {
	if f is string {
		return f
	} else {
		return f.json_str()
	}
}

// json_str returns the JSON string representation of the `Any` type.
pub fn (f Any) json_str() string {
	match f {
		string {
			return json_string(f)
		}
		bool, int, u64, i64 {
			return f.str()
		}
		f32 {
			$if !nofloat ? {
				str_f32 := f.str()
				if str_f32.ends_with('.') {
					return '${str_f32}0'
				}
				return str_f32
			}

			return '0'
		}
		f64 {
			$if !nofloat ? {
				str_f64 := f.str()
				if str_f64.ends_with('.') {
					return '${str_f64}0'
				}
				return str_f64
			}
			return '0'
		}
		map[string]Any {
			return f.str()
		}
		[]Any {
			return f.str()
		}
		Null {
			return 'null'
		}
	}
}

// char_len_list is a modified version of builtin.utf8_str_len
// that returns an array of character lengths. (e.g "t✔" => [1,2])
fn char_len_list(s string) []int {
	mut l := 1
	mut ls := []int{}
	for i := 0; i < s.len; i++ {
		c := s[i]
		if (c & (1 << 7)) != 0 {
			for t := byte(1 << 6); (c & t) != 0; t >>= 1 {
				l++
				i++
			}
		}
		ls << l
		l = 1
	}
	return ls
}

const escaped_chars = [r'\b', r'\f', r'\n', r'\r', r'\t']

// json_string returns the JSON spec-compliant version of the string.
[manualfree]
fn json_string(s string) string {
	// not the best implementation but will revisit it soon
	char_lens := char_len_list(s)
	mut sb := strings.new_builder(s.len)
	mut i := 0
	defer {
		unsafe {
			char_lens.free()
			// freeing string builder on defer after
			// returning .str() still isn't working :(
			// sb.free()
		}
	}
	for char_len in char_lens {
		if char_len == 1 {
			chr := s[i]
			if chr in important_escapable_chars {
				for j := 0; j < important_escapable_chars.len; j++ {
					if chr == important_escapable_chars[j] {
						sb.write_string(json2.escaped_chars[j])
						break
					}
				}
			} else if chr == `"` || chr == `/` || chr == `\\` {
				sb.write_string('\\' + chr.ascii_str())
			} else if int(chr) < 0x20 {
				hex_code := chr.hex()
				sb.write_string('\\u00$hex_code')
			} else {
				sb.write_byte(chr)
			}
		} else {
			slice := s[i..i + char_len]
			hex_code := slice.utf32_code().hex()
			if hex_code.len < 4 {
				// an utf8 codepoint
				sb.write_string(slice)
			} else if hex_code.len == 4 {
				sb.write_string('\\u$hex_code')
			} else {
				// TODO: still figuring out what
				// to do with more than 4 chars
				sb.write_byte(` `)
			}
			unsafe {
				slice.free()
				hex_code.free()
			}
		}
		i += char_len
	}
	str := sb.str()
	unsafe { sb.free() }
	return str
}