// Copyright (c) 2019 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the GNU LGPLv3.0 license
// a copy of which can be found in the LICENSE file.

#include <stdarg.h>
#include <stddef.h>
#include <assert.h>
#include "lib.h"
#include <stdio.h>

struct ScratchBuf scratch_buffer;

int str_findlist(const char *value, unsigned count, const char** elements)
{
	for (unsigned i = 0; i < count; i++)
	{
		if (strcmp(value, elements[i]) == 0) return (int)i;
	}
	return -1;
}


bool str_is_valid_module_name(const char *name)
{
	char c = *name++;
	if (c == '_' || char_is_upper(c)) return false;
	while ((c = *(name++)) != '\0')
	{
		if (c == '_')
		{
			// Prevent trailing `_` and `__`
			if (name[-2] == ':' || name[0] == '_' || name[0] == ':' || name[0] == '\0') return false;
		}
		if (char_is_upper(c)) return false;
	}
	return true;
}
bool str_has_no_uppercase(const char *string)
{
	char c;
	while ((c = *(string++)) != '\0')
	{
		if (char_is_upper(c)) return false;
	}
	return true;
}

bool str_is_valid_lowercase_name(const char *string)
{
	char c;
	// Must start with a lower case
	if (!char_is_lower(string[0])) return false;
	int length = 0;
	while ((c = *(string++)) != '\0')
	{
		if (!char_is_lower_alphanum_(c)) return false;
		if (++length > 127) return false;
	}
	return true;
}

static const char *scan_past_underscore(const char *string)
{
    while (string[0] == '_') string++;
    if (string[0] == '\0') return NULL;
    return string;
}

const char *str_unescape(char *string)
{
    ASSERT(string[0] == '"');
    char c;
    size_t index = 0;
    while ((c = string++[0]) != '"')
    {
	    if (c == 0) return NULL;
	    if (c == '\\')
	    {
		    c = string++[0];
	    }
	    string[index++] = c;
    }
	string[index] = '\0';
	return string;
}

bool str_is_type(const char *string)
{
    string = scan_past_underscore(string);
    if (!string) return false;
    char c = string++[0];
    if (!char_is_upper(c)) return false;
    bool found_lower = false;
    while ((c = *(string++)) != '\0')
    {
        if (char_is_lower(c))
        {
            found_lower = true;
            continue;
        }
        if (!char_is_alphanum_(c)) return false;
    }
    return found_lower;
}

bool slice_is_type(const char *string, size_t len)
{
	const char *begin = scan_past_underscore(string);
	if (!begin) return false;
	const char *end = string  + len;
	if (begin == end) return false;
	char c = begin++[0];
	if (!char_is_upper(c)) return false;
	bool found_lower = false;
	while (begin != end)
	{
		c = begin++[0];
		if (char_is_lower(c))
		{
			found_lower = true;
			continue;
		}
		if (!char_is_alphanum_(c)) return false;
	}
	return found_lower;
}

bool str_is_identifier(const char *string)
{
    string = scan_past_underscore(string);
    if (!string) return false;
    char c = string++[0];
    if (!char_is_lower(c)) return false;
    while ((c = *(string++)) != '\0')
    {
        if (!char_is_alphanum_(c)) return false;
    }
    return true;
}

bool str_eq(const char *str1, const char *str2)
{
	return str1 == str2 || (str1 && str2 && strcmp(str1, str2) == 0);
}

bool str_ends_with(const char *str, const char *end)
{
	size_t str_len = strlen(str);
	size_t end_len = strlen(end);
	if (end_len > str_len) return false;
	return memcmp(str + str_len - end_len, end, end_len) == 0;
}

bool str_is_integer(const char *string)
{
	if (string[0] == '-') string++;
    if (!string[0]) return false;
    char c;
    // Must start with a lower case
    while ((c = *(string++)) != '\0')
    {
        if (!char_is_digit(c)) return false;
    }
    return true;
}

bool str_is_valid_constant(const char *string)
{
    string = scan_past_underscore(string);
    if (!string) return false;
    char c = string++[0];
    if (!char_is_upper(c)) return false;
    while ((c = *(string++)) != '\0') {
        if (!char_is_upper_alphanum_(c)) return false;
    }
    return true;
}

void str_elide_in_place(char *string, size_t max_size_shown)
{
	size_t len = strlen(string);
	if (max_size_shown > len) return;
	for (int i = 0; i < 3; i++)
	{
		string[max_size_shown - i] = '.';
	}
	string[max_size_shown + 1] = 0;
}

char *str_vprintf(const char *var, va_list list)
{
	va_list copy;
	va_copy(copy, list);
	int len = vsnprintf(NULL, 0, var, copy);
	va_end(copy);
	if (len < 1)
	{
		return "";
	}
	char *buffer = malloc_string((uint32_t)len + 1);
	int new_len = vsnprintf(buffer, len + 1, var, list);
	ASSERT(len == new_len);
	(void)new_len;
	return buffer;
}

char *str_printf(const char *var, ...)
{
	va_list list;
	va_start(list, var);
	char *res = str_vprintf(var, list);
	va_end(list);
	return res;
}

const char *str_remove_suffix(const char *name, const char *suffix)
{
	size_t name_len = strlen(name);
	size_t suffix_len = strlen(suffix);
	if (name_len <= suffix_len) return NULL;
	if (memcmp(name + name_len - suffix_len, suffix, suffix_len) != 0) return NULL;
	size_t result_len = name_len - suffix_len;
	char *name_copy = malloc_string(result_len + 1);
	memcpy(name_copy, name, result_len);
	name_copy[result_len] = 0;
	return name_copy;
}

bool str_has_suffix(const char *name, const char *suffix)
{
	size_t name_len = strlen(name);
	size_t suffix_len = strlen(suffix);
	if (name_len <= suffix_len) return false;
	return memcmp(name + name_len - suffix_len, suffix, suffix_len) == 0;
}

bool str_start_with(const char *name, const char *prefix)
{
	size_t suffix_len = strlen(prefix);
	for (size_t i = 0; i < suffix_len; i++)
	{
		char c = name[i];
		if (c == 0) return false;
		if (c != prefix[i]) return false;
	}
	return true;
}


StringSlice slice_next_token(StringSlice *slice, char separator)
{
	for (size_t i = 0; i < slice->len; i++)
	{
		if (slice->ptr[i] == separator)
		{
			StringSlice result = { slice->ptr, i };
			slice->ptr = slice->ptr + i + 1;
			slice->len = slice->len - i - 1;
			return result;
		}
	}
	StringSlice result = *slice;
	slice->ptr = slice->ptr + slice->len;
	slice->len = 0;
	return result;
}

void slice_trim(StringSlice *slice)
{
	size_t i;
	for (i = 0; i < slice->len; i++)
	{
		if (slice->ptr[i] != ' ') break;
	}
	slice->ptr += i;
	slice->len -= i;
	for (i = slice->len; i > 0; i--)
	{
		if (slice->ptr[i - 1] != ' ') break;
	}
	slice->len = i;
}

char *str_trim(char *str)
{
	str_trim_end(str);
	return (char *)str_trim_start(str);
}

const char *str_trim_start(const char *str)
{
	while (str[0] != 0)
	{
		switch (str[0])
		{
			case ' ':
			case '\t':
			case '\n':
			case '\r':
				str++;
				continue;
			default:
				break;
		}
		break;
	}
	return str;
}

void str_trim_end(char *str)
{
	size_t len = strlen(str);
	char *end = str + len - 1;
	while (len > 0)
	{
		switch (*end)
		{
			case ' ':
			case '\t':
			case '\n':
			case '\r':
				len--;
				end--;
				continue;
			default:
				end[1] = 0;
				return;
		}
	}
}

char *str_cat(const char *a, const char *b)
{
	unsigned a_len = (unsigned)strlen(a);
	unsigned b_len = (unsigned)strlen(b);
	char *buffer = malloc_string(a_len + b_len + 1);
	memcpy(buffer, a, a_len);
	memcpy(buffer + a_len, b, b_len);
	buffer[a_len + b_len] = '\0';
	return buffer;
}

char *str_cat_len(const char *a, size_t a_len, const char *b, size_t b_len)
{
	char *buffer = malloc_string(a_len + b_len + 1);
	memcpy(buffer, a, a_len);
	memcpy(buffer + a_len, b, b_len);
	buffer[a_len + b_len] = '\0';
	return buffer;
}

char *str_dup(const char *str)
{
	return str_copy(str, strlen(str));
}

char *str_copy(const char *start, size_t str_len)
{
	char *dst = calloc_string(str_len + 1);
	memcpy(dst, start, str_len);
	// No need to set the end
	return dst;
}

void scratch_buffer_delete(size_t len)
{
	ASSERT(scratch_buffer.len >= len);
	scratch_buffer.len -= len;
}

void scratch_buffer_clear(void)
{
	scratch_buffer.len = 0;
}

bool scratch_buffer_may_append(size_t len)
{
	return len + scratch_buffer.len < MAX_STRING_BUFFER - 1;
}

void scratch_buffer_append_len(const char *string, size_t len)
{
	if (!scratch_buffer_may_append(len))
	{
		error_exit("Scratch buffer size (%d chars) exceeded", MAX_STRING_BUFFER - 1);
	}
	memcpy(scratch_buffer.str + scratch_buffer.len, string, len);
	scratch_buffer.len += (uint32_t)len;
}

static void scratch_buffer_append_double_quoted(const char *string)
{
	scratch_buffer_append_char('"');
	size_t len = strlen(string);
	for (size_t i = 0; i < len; )
	{
		char c = string[i++];
		switch (c)
		{
			case '"':
				scratch_buffer_append("\\\"");
				continue;
			case '\\':
			{
				int backslash_count = 1;
				for (; i < len && string[i] == '\\'; i++, backslash_count++) {}
				if (i == len || string[i] == '"')
				{
					scratch_buffer_append_char_repeat('\\', backslash_count * 2);
				}
				else
				{
					scratch_buffer_append_char_repeat('\\', backslash_count);
				}
				continue;
			}
		}
		scratch_buffer_append_char(c);
	}
	scratch_buffer_append_char('"');
}

#if PLATFORM_WINDOWS
static bool contains_whitespace_or_quotes(const char *string)
{
	char c;
	while ((c = *string++) != '\0')
	{
		switch (c)
		{
			case ' ':
			case '\t':
			case '\n':
			case '\r':
			case '"':
				return true;
		}
	}
	return false;
}
#endif

void scratch_buffer_append_cmd_argument(const char *string)
{
#if PLATFORM_WINDOWS
	if (contains_whitespace_or_quotes(string))
	{
		scratch_buffer_append_double_quoted(string);
	}
	else
	{
		scratch_buffer_append(string);
	}
#else
	scratch_buffer_append_shell_escaped(string);
#endif
}


void scratch_buffer_append_shell_escaped(const char *string)
{
	char c;
	while ((c = string++[0]) != '\0')
	{
		if ((unsigned)c < 0x80)
		{
			switch (c)
			{
				case LOWER_CHAR_CASE:
				case UPPER_CHAR_CASE:
				case NUMBER_CHAR_CASE:
				case '_':
				case '/':
				case '.':
				case ',':
				case '-':
					break;
				default:
					scratch_buffer_append_char('\\');
					break;
			}
		}
		scratch_buffer_append_char(c);
	}
}
void scratch_buffer_append(const char *string)
{
	scratch_buffer_append_len(string, strlen(string));
}

void scratch_buffer_append_signed_int(int64_t i)
{
	scratch_buffer_printf("%lld", (long long)i);
}

void scratch_buffer_append_double(double d)
{
	scratch_buffer_printf("%f", d);

	//removing unused zeroes and dot
	while (scratch_buffer.len > 0)
	{
		if (scratch_buffer.str[scratch_buffer.len - 1] != '0' && scratch_buffer.str[scratch_buffer.len - 1] != '.')
		{
			return;
		}
		scratch_buffer.len--;
	}
}

void scratch_buffer_append_unsigned_int(uint64_t i)
{
	scratch_buffer_printf("%llu", (unsigned long long)i);
}

void scratch_buffer_printf(const char *format, ...)
{
	va_list args;
	va_start(args, format);
	size_t available = MAX_STRING_BUFFER - scratch_buffer.len;
	uint32_t len_needed = (uint32_t)vsnprintf(&scratch_buffer.str[scratch_buffer.len], available, format, args);
	if (len_needed > available - 1)
	{
		error_exit("Scratch buffer size (%d chars) exceeded", MAX_STRING_BUFFER - 1);
	}
	va_end(args);
	scratch_buffer.len += len_needed;
}

void scratch_buffer_append_in_quote(const char *string)
{
	size_t len = strlen(string);
	for (size_t i = 0; i < len; )
	{
		char c = string[i++];
		switch (c)
		{
			case '"':
				scratch_buffer_append("\\\"");
				continue;
			case '\\':
				scratch_buffer_append("\\\\");
				continue;
		}
		scratch_buffer_append_char(c);
	}
}

void scratch_buffer_append_remove_space(const char *start, int len)
{
	char clast = ' ';
	int printed = 0;
	for (int i = 0; i < len; i++)
	{
		char ch = start[i];
		if (ch == '\n' || ch == '\t') ch = ' ';
		if (ch == ' ' && clast == ch) continue;
		scratch_buffer_append_char(ch);
		clast = ch;
		printed++;
	}
	if (clast == ' ' && printed > 0) scratch_buffer_delete(1);
}

void scratch_buffer_append_char(char c)
{
	if (scratch_buffer.len + 1 > MAX_STRING_BUFFER - 1)
	{
		error_exit("Scratch buffer size (%d chars) exceeded", MAX_STRING_BUFFER - 1);
	}

	scratch_buffer.str[scratch_buffer.len++] = c;
}

void scratch_buffer_append_char_repeat(char c, size_t count)
{
	for (size_t i = 0; i < count; i++)
	{
		scratch_buffer_append_char(c);
	}
}

char *scratch_buffer_to_string(void)
{
	scratch_buffer.str[scratch_buffer.len] = '\0';
	return scratch_buffer.str;
}

char *scratch_buffer_copy(void)
{
	return str_copy(scratch_buffer.str, scratch_buffer.len);
}


