package http

import "core:strings"

// A case-insensitive ASCII map for storing headers.
Headers :: struct {
	_kv:      map[string]string,
	readonly: bool,
}

headers_init :: proc(h: ^Headers, allocator := context.temp_allocator) {
	h._kv.allocator = allocator
}

headers_count :: #force_inline proc(h: Headers) -> int {
	return len(h._kv)
}

/*
Sets a header, given key is first sanitized, final (sanitized) key is returned.
*/
headers_set :: proc(h: ^Headers, k: string, v: string, loc := #caller_location) -> string {
	if h.readonly {
		panic("these headers are readonly, did you accidentally try to set a header on the request?", loc)
	}

	l := sanitize_key(h^, k)
	h._kv[l] = v
	return l
}

/*
Unsafely set header, given key is assumed to be a lowercase string and to be without newlines.
*/
headers_set_unsafe :: #force_inline proc(h: ^Headers, k: string, v: string, loc := #caller_location) {
	assert(!h.readonly, "these headers are readonly, did you accidentally try to set a header on the request?", loc)
	h._kv[k] = v
}

headers_get :: proc(h: Headers, k: string) -> (string, bool) #optional_ok {
	return h._kv[sanitize_key(h, k)]
}

/*
Unsafely get header, given key is assumed to be a lowercase string.
*/
headers_get_unsafe :: #force_inline proc(h: Headers, k: string) -> (string, bool) #optional_ok {
	return h._kv[k]
}

headers_has :: proc(h: Headers, k: string) -> bool {
	return sanitize_key(h, k) in h._kv
}

/*
Unsafely check for a header, given key is assumed to be a lowercase string.
*/
headers_has_unsafe :: #force_inline proc(h: Headers, k: string) -> bool {
	return k in h._kv
}

headers_delete :: proc(h: ^Headers, k: string) -> (deleted_key: string, deleted_value: string) {
	return delete_key(&h._kv, sanitize_key(h^, k))
}

/*
Unsafely delete a header, given key is assumed to be a lowercase string.
*/
headers_delete_unsafe :: #force_inline proc(h: ^Headers, k: string) {
	delete_key(&h._kv, k)
}

/* Common Helpers */

headers_set_content_type :: proc {
	headers_set_content_type_mime,
	headers_set_content_type_string,
}

headers_set_content_type_string :: #force_inline proc(h: ^Headers, ct: string) {
	headers_set_unsafe(h, "content-type", ct)
}

headers_set_content_type_mime :: #force_inline proc(h: ^Headers, ct: Mime_Type) {
	headers_set_unsafe(h, "content-type", mime_to_content_type(ct))
}

headers_set_close :: #force_inline proc(h: ^Headers) {
	headers_set_unsafe(h, "connection", "close")
}

/*
Escapes any newlines and converts ASCII to lowercase.
*/
@(private="file")
sanitize_key :: proc(h: Headers, k: string) -> string {
	allocator := h._kv.allocator if h._kv.allocator.procedure != nil else context.temp_allocator

	// general +4 in rare case of newlines, so we might not need to reallocate.
	b := strings.builder_make(0, len(k)+4, allocator)
	for c in k {
		switch c {
		case 'A'..='Z': strings.write_rune(&b, c + 32)
		case '\n':      strings.write_string(&b, "\\n")
		case:           strings.write_rune(&b, c)
		}
	}
	return strings.to_string(b)

	// NOTE: implementation that only allocates if needed, but we use arena's anyway so just allocating
	// some space should be about as fast?
	//
	// b: strings.Builder = ---
	// i: int
	// for c in v {
	// 	if c == '\n' || (c >= 'A' && c <= 'Z') {
	// 		b = strings.builder_make(0, len(v)+4, allocator)
	// 		strings.write_string(&b, v[:i])
	// 		alloc = true
	// 		break
	// 	}
	// 	i+=1
	// }
	//
	// if !alloc {
	// 	return v, false
	// }
	//
	// for c in v[i:] {
	//  switch c {
	//  case 'A'..='Z': strings.write_rune(&b, c + 32)
	//  case '\n':      strings.write_string(&b, "\\n")
	//  case:           strings.write_rune(&b, c)
	//  }
	// }
	//
	// return strings.to_string(b), true
}