#include "./perfdata.h"
#include "../plugins/common.h"
#include "../plugins/utils.h"
#include "utils_base.h"

#include <assert.h>
#include <limits.h>
#include <stdlib.h>

char *pd_value_to_string(const mp_perfdata_value pd) {
	char *result = NULL;

	assert(pd.type != PD_TYPE_NONE);

	switch (pd.type) {
	case PD_TYPE_INT:
		asprintf(&result, "%lli", pd.pd_int);
		break;
	case PD_TYPE_UINT:
		asprintf(&result, "%llu", pd.pd_int);
		break;
	case PD_TYPE_DOUBLE:
		asprintf(&result, "%f", pd.pd_double);
		break;
	default:
		// die here
		die(STATE_UNKNOWN, "Invalid mp_perfdata mode\n");
	}

	return result;
}

char *pd_to_string(mp_perfdata pd) {
	assert(pd.label != NULL);
	char *result = NULL;

	if (strchr(pd.label, '\'') == NULL) {
		asprintf(&result, "'%s'=", pd.label);
	} else {
		// we have an illegal single quote in the string
		// replace it silently instead of complaining
		for (char *ptr = pd.label; *ptr != '\0'; ptr++) {
			if (*ptr == '\'') {
				*ptr = '_';
			}
		}
	}

	asprintf(&result, "%s%s", result, pd_value_to_string(pd.value));

	if (pd.uom != NULL) {
		asprintf(&result, "%s%s", result, pd.uom);
	}

	if (pd.warn_present) {
		asprintf(&result, "%s;%s", result, mp_range_to_string(pd.warn));
	} else {
		asprintf(&result, "%s;", result);
	}

	if (pd.crit_present) {
		asprintf(&result, "%s;%s", result, mp_range_to_string(pd.crit));
	} else {
		asprintf(&result, "%s;", result);
	}
	if (pd.min_present) {
		asprintf(&result, "%s;%s", result, pd_value_to_string(pd.min));
	} else {
		asprintf(&result, "%s;", result);
	}

	if (pd.max_present) {
		asprintf(&result, "%s;%s", result, pd_value_to_string(pd.max));
	}

	/*printf("pd_to_string: %s\n", result); */

	return result;
}

char *pd_list_to_string(const pd_list pd) {
	char *result = pd_to_string(pd.data);

	for (pd_list *elem = pd.next; elem != NULL; elem = elem->next) {
		asprintf(&result, "%s %s", result, pd_to_string(elem->data));
	}

	return result;
}

mp_perfdata perfdata_init() {
	mp_perfdata pd = {};
	return pd;
}

pd_list *pd_list_init() {
	pd_list *tmp = (pd_list *)calloc(1, sizeof(pd_list));
	if (tmp == NULL) {
		die(STATE_UNKNOWN, "calloc failed\n");
	}
	tmp->next = NULL;
	return tmp;
}

mp_range mp_range_init() {
	mp_range result = {
		.alert_on_inside_range = OUTSIDE,
		.start = {},
		.start_infinity = true,
		.end = {},
		.end_infinity = true,
	};

	return result;
}

mp_range mp_range_set_start(mp_range input, mp_perfdata_value perf_val) {
	input.start = perf_val;
	input.start_infinity = false;
	return input;
}

mp_range mp_range_set_end(mp_range input, mp_perfdata_value perf_val) {
	input.end = perf_val;
	input.end_infinity = false;
	return input;
}

void pd_list_append(pd_list pdl[1], const mp_perfdata pd) {
	assert(pdl != NULL);

	if (pdl->data.value.type == PD_TYPE_NONE) {
		// first entry is still empty
		pdl->data = pd;
	} else {
		// find last element in the list
		pd_list *curr = pdl;
		pd_list *next = pdl->next;

		while (next != NULL) {
			curr = next;
			next = next->next;
		}

		if (curr->data.value.type == PD_TYPE_NONE) {
			// still empty
			curr->data = pd;
		} else {
			// new a new one
			curr->next = pd_list_init();
			curr->next->data = pd;
		}
	}
}

void pd_list_free(pd_list pdl[1]) {
	while (pdl != NULL) {
		pd_list *old = pdl;
		pdl = pdl->next;
		free(old);
	}
}

/*
 * returns -1 if a < b, 0 if a == b, 1 if a > b
 */
int cmp_perfdata_value(const mp_perfdata_value a, const mp_perfdata_value b) {
	// Test if types are different
	if (a.type == b.type) {

		switch (a.type) {
		case PD_TYPE_UINT:
			if (a.pd_uint < b.pd_uint) {
				return -1;
			} else if (a.pd_uint == b.pd_uint) {
				return 0;
			} else {
				return 1;
			}
			break;
		case PD_TYPE_INT:
			if (a.pd_int < b.pd_int) {
				return -1;
			} else if (a.pd_int == b.pd_int) {
				return 0;
			} else {
				return 1;
			}
			break;
		case PD_TYPE_DOUBLE:
			if (a.pd_int < b.pd_int) {
				return -1;
			} else if (a.pd_int == b.pd_int) {
				return 0;
			} else {
				return 1;
			}
			break;
		default:
			die(STATE_UNKNOWN, "Error in %s line: %d!", __FILE__, __LINE__);
		}
	}

	// Get dirty here
	long double floating_a = 0;

	switch (a.type) {
	case PD_TYPE_UINT:
		floating_a = a.pd_uint;
		break;
	case PD_TYPE_INT:
		floating_a = a.pd_int;
		break;
	case PD_TYPE_DOUBLE:
		floating_a = a.pd_double;
		break;
	default:
		die(STATE_UNKNOWN, "Error in %s line: %d!", __FILE__, __LINE__);
	}

	long double floating_b = 0;
	switch (b.type) {
	case PD_TYPE_UINT:
		floating_b = b.pd_uint;
		break;
	case PD_TYPE_INT:
		floating_b = b.pd_int;
		break;
	case PD_TYPE_DOUBLE:
		floating_b = b.pd_double;
		break;
	default:
		die(STATE_UNKNOWN, "Error in %s line: %d!", __FILE__, __LINE__);
	}

	if (floating_a < floating_b) {
		return -1;
	}
	if (floating_a == floating_b) {
		return 0;
	}
	return 1;
}

char *mp_range_to_string(const mp_range input) {
	char *result = "";
	if (input.alert_on_inside_range == INSIDE) {
		asprintf(&result, "@");
	}

	if (input.start_infinity) {
		asprintf(&result, "%s~:", result);
	} else {
		// check for zeroes, so we can use the short form
		if ((input.start.type == PD_TYPE_NONE) ||
		     ((input.start.type == PD_TYPE_INT) && (input.start.pd_int == 0)) ||
		     ((input.start.type == PD_TYPE_UINT) && (input.start.pd_uint == 0)) ||
		     ((input.start.type == PD_TYPE_DOUBLE) && (input.start.pd_double == 0))){
			// nothing to do here
		} else {
			// Start value is an actual value
			asprintf(&result, "%s%s:", result, pd_value_to_string(input.start));
		}
	}

	if (!input.end_infinity) {
		asprintf(&result, "%s%s", result, pd_value_to_string(input.end));
	}
	return result;
}

mp_perfdata mp_set_pd_value_float(mp_perfdata pd, float value) {
	return mp_set_pd_value_double(pd, value);
}

mp_perfdata mp_set_pd_value_double(mp_perfdata pd, double value) {
	pd.value.pd_double = value;
	pd.value.type = PD_TYPE_DOUBLE;
	return pd;
}

mp_perfdata mp_set_pd_value_char(mp_perfdata pd, char value) {
	return mp_set_pd_value_long_long(pd, (long long)value);
}

mp_perfdata mp_set_pd_value_u_char(mp_perfdata pd, unsigned char value) {
	return mp_set_pd_value_u_long_long(pd, (unsigned long long)value);
}

mp_perfdata mp_set_pd_value_int(mp_perfdata pd, int value) {
	return mp_set_pd_value_long_long(pd, (long long)value);
}

mp_perfdata mp_set_pd_value_u_int(mp_perfdata pd, unsigned int value) {
	return mp_set_pd_value_u_long_long(pd, (unsigned long long)value);
}

mp_perfdata mp_set_pd_value_long(mp_perfdata pd, long value) {
	return mp_set_pd_value_long_long(pd, (long long)value);
}

mp_perfdata mp_set_pd_value_u_long(mp_perfdata pd, unsigned long value) {
	return mp_set_pd_value_u_long_long(pd, (unsigned long long)value);
}

mp_perfdata mp_set_pd_value_long_long(mp_perfdata pd, long long value) {
	pd.value.pd_int = value;
	pd.value.type = PD_TYPE_INT;
	return pd;
}

mp_perfdata mp_set_pd_value_u_long_long(mp_perfdata pd, unsigned long long value) {
	pd.value.pd_uint = value;
	pd.value.type = PD_TYPE_UINT;
	return pd;
}

mp_perfdata_value mp_create_pd_value_double(double value) {
	mp_perfdata_value res = {0};
	res.type = PD_TYPE_DOUBLE;
	res.pd_double = value;
	return res;
}

mp_perfdata_value mp_create_pd_value_float(float value) {
	return mp_create_pd_value_double((double)value);
}

mp_perfdata_value mp_create_pd_value_char(char value) {
	return mp_create_pd_value_long_long((long long)value);
}

mp_perfdata_value mp_create_pd_value_u_char(unsigned char value) {
	return mp_create_pd_value_u_long_long((unsigned long long)value);
}

mp_perfdata_value mp_create_pd_value_int(int value) {
	return mp_create_pd_value_long_long((long long)value);
}

mp_perfdata_value mp_create_pd_value_u_int(unsigned int value) {
	return mp_create_pd_value_u_long_long((unsigned long long)value);
}

mp_perfdata_value mp_create_pd_value_long(long value) {
	return mp_create_pd_value_long_long((long long)value);
}

mp_perfdata_value mp_create_pd_value_u_long(unsigned long value) {
	return mp_create_pd_value_u_long_long((unsigned long long)value);
}

mp_perfdata_value mp_create_pd_value_long_long(long long value) {
	mp_perfdata_value res = {0};
	res.type = PD_TYPE_INT;
	res.pd_int = value;
	return res;
}

mp_perfdata_value mp_create_pd_value_u_long_long(unsigned long long value) {
	mp_perfdata_value res = {0};
	res.type = PD_TYPE_UINT;
	res.pd_uint = value;
	return res;
}

char *fmt_range(range foo) { return foo.text; }

typedef struct integer_parser_wrapper {
	int error;
	mp_perfdata_value value;
} integer_parser_wrapper;

typedef struct double_parser_wrapper {
	int error;
	mp_perfdata_value value;
} double_parser_wrapper;

typedef struct perfdata_value_parser_wrapper {
	int error;
	mp_perfdata_value value;
} perfdata_value_parser_wrapper;

double_parser_wrapper parse_double(const char *input);
integer_parser_wrapper parse_integer(const char *input);
perfdata_value_parser_wrapper parse_pd_value(const char *input);

mp_range_parsed mp_parse_range_string(const char *input) {
	if (input == NULL) {
		mp_range_parsed result = {
			.error = MP_RANGE_PARSING_FAILURE,
		};
		return result;
	}

	if (strlen(input) == 0) {
		mp_range_parsed result = {
			.error = MP_RANGE_PARSING_FAILURE,
		};
		return result;
	}

	mp_range_parsed result = {
		.range = mp_range_init(),
		.error = MP_PARSING_SUCCESS,
	};

	if (input[0] == '@') {
		// found an '@' at beginning, so invert the range logic
		result.range.alert_on_inside_range = INSIDE;

		// advance the pointer one symbol
		input++;
	}

	char *working_copy = strdup(input);
	if (working_copy == NULL) {
		// strdup error, probably
		mp_range_parsed result = {
			.error = MP_RANGE_PARSING_FAILURE,
		};
		return result;
	}
	input = working_copy;

	char *separator = index(working_copy, ':');
	if (separator != NULL) {
		// Found a separator
		// set the separator to 0, so we have two different strings
		*separator = '\0';

		if (input[0] == '~') {
			// the beginning starts with '~', so it might be infinity
			if (&input[1] != separator) {
				// the next symbol after '~' is not the separator!
				// so input is probably wrong
				result.error = MP_RANGE_PARSING_FAILURE;
				free(working_copy);
				return result;
			}

			result.range.start_infinity = true;
		} else {
			// No '~' at the beginning, so this should be a number
			result.range.start_infinity = false;
			perfdata_value_parser_wrapper parsed_pd = parse_pd_value(input);

			if (parsed_pd.error != MP_PARSING_SUCCESS) {
				result.error = parsed_pd.error;
				free(working_copy);
				return result;
			}

			result.range.start = parsed_pd.value;
			result.range.start_infinity = false;
		}
		// got the first part now
		// advance the pointer
		input = separator + 1;
	}

	// End part or no separator
	if (input[0] == '\0') {
		// the end is infinite
		result.range.end_infinity = true;
	} else {
		perfdata_value_parser_wrapper parsed_pd = parse_pd_value(input);

		if (parsed_pd.error != MP_PARSING_SUCCESS) {
			result.error = parsed_pd.error;
			return result;
		}
		result.range.end = parsed_pd.value;
		result.range.end_infinity = false;
	}
	free(working_copy);
	return result;
}

double_parser_wrapper parse_double(const char *input) {
	double_parser_wrapper result = {
		.error = MP_PARSING_SUCCESS,
	};

	if (input == NULL) {
		result.error = MP_PARSING_FAILURE;
		return result;
	}

	char *endptr = NULL;
	errno = 0;
	double tmp = strtod(input, &endptr);

	if (input == endptr) {
		// man 3 strtod says, no conversion performed
		result.error = MP_PARSING_FAILURE;
		return result;
	}

	if (errno) {
		// some other error
		// TODO maybe differentiate a little bit
		result.error = MP_PARSING_FAILURE;
		return result;
	}

	result.value = mp_create_pd_value(tmp);
	return result;
}

integer_parser_wrapper parse_integer(const char *input) {
	integer_parser_wrapper result = {
		.error = MP_PARSING_SUCCESS,
	};

	if (input == NULL) {
		result.error = MP_PARSING_FAILURE;
		return result;
	}

	char *endptr = NULL;
	errno = 0;
	long long tmp = strtoll(input, &endptr, 0);

	// validating *sigh*
	if (*endptr != '\0') {
		// something went wrong in strtoll
		if (tmp == LLONG_MIN) {
			// underflow
			result.error = MP_RANGE_PARSING_UNDERFLOW;
			return result;
		}

		if (tmp == LLONG_MAX) {
			// overflow
			result.error = MP_RANGE_PARSING_OVERFLOW;
			return result;
		}

		// still wrong, but not sure why, probably invalid characters
		if (errno == EINVAL) {
			result.error = MP_RANGE_PARSING_INVALID_CHAR;
			return result;
		}

		// some other error, do catch all here
		result.error = MP_RANGE_PARSING_FAILURE;
		return result;
	}

	// no error, should be fine
	result.value = mp_create_pd_value(tmp);
	return result;
}

perfdata_value_parser_wrapper parse_pd_value(const char *input) {
	// try integer first
	integer_parser_wrapper tmp_int = parse_integer(input);

	if (tmp_int.error == MP_PARSING_SUCCESS) {
		perfdata_value_parser_wrapper result = {
			.error = tmp_int.error,
			.value = tmp_int.value,
		};
		return result;
	}

	double_parser_wrapper tmp_double = parse_double(input);
	perfdata_value_parser_wrapper result = {};
	if (tmp_double.error == MP_PARSING_SUCCESS) {
		result.error = tmp_double.error;
		result.value = tmp_double.value;
	} else {
		result.error = tmp_double.error;
	}
	return result;
}

mp_perfdata mp_set_pd_max_value(mp_perfdata perfdata, mp_perfdata_value value) {
	perfdata.max = value;
	perfdata.max_present = true;
	return perfdata;
}

mp_perfdata mp_set_pd_min_value(mp_perfdata perfdata, mp_perfdata_value value) {
	perfdata.min = value;
	perfdata.min_present = true;
	return perfdata;
}

double mp_get_pd_value(mp_perfdata_value value) {
	assert(value.type != PD_TYPE_NONE);
	switch (value.type) {
	case PD_TYPE_DOUBLE:
		return value.pd_double;
	case PD_TYPE_INT:
		return (double)value.pd_int;
	case PD_TYPE_UINT:
		return (double)value.pd_uint;
	default:
		return 0; // just to make the compiler happy
	}
}

mp_perfdata_value mp_pd_value_multiply(mp_perfdata_value left, mp_perfdata_value right) {
	if (left.type == right.type) {
		switch (left.type) {
		case PD_TYPE_DOUBLE:
			left.pd_double *= right.pd_double;
			return left;
		case PD_TYPE_INT:
			left.pd_int *= right.pd_int;
			return left;
		case PD_TYPE_UINT:
			left.pd_uint *= right.pd_uint;
			return left;
		default:
			// what to here?
			return left;
		}
	}

	// Different types, oh boy, just do the lazy thing for now and switch to double
	switch (left.type) {
	case PD_TYPE_INT:
		left.pd_double = (double)left.pd_int;
		left.type = PD_TYPE_DOUBLE;
		break;
	case PD_TYPE_UINT:
		left.pd_double = (double)left.pd_uint;
		left.type = PD_TYPE_DOUBLE;
		break;
	}

	switch (right.type) {
	case PD_TYPE_INT:
		right.pd_double = (double)right.pd_int;
		right.type = PD_TYPE_DOUBLE;
		break;
	case PD_TYPE_UINT:
		right.pd_double = (double)right.pd_uint;
		right.type = PD_TYPE_DOUBLE;
		break;
	}

	left.pd_double *= right.pd_double;
	return left;
}

mp_range mp_range_multiply(mp_range range, mp_perfdata_value factor) {
	if (!range.end_infinity) {
		range.end = mp_pd_value_multiply(range.end, factor);
	}
	if (!range.start_infinity) {
		range.start = mp_pd_value_multiply(range.start, factor);
	}
	return range;
}
