/*
 * Copyright (c) 2019 Miroslav Foltýn.  All Rights Reserved.
 * 
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 */
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdbool.h>

#include "parsers.h"
#include "parser-ragel.h"
#include "parsers-utils.h"
#include "utils.h"

%%{
	machine statsd;
	write data;
}%%


/**
 * Ragel parser entry point
 * Parsers given buffer and populates datagram with parsed data if they are valid
 * @arg str - Buffer to be parsed
 * @arg datagram - Placeholder for parsed data
 * @return 1 on success, 0 on fail 
 */
int
ragel_parser_parse(char* str, struct statsd_datagram** datagram) {
	*datagram = (struct statsd_datagram*) malloc(sizeof(struct statsd_datagram));
	*(*datagram) = (struct statsd_datagram) {0};
	ALLOC_CHECK("Not enough memory to save datagram");
	size_t length = strlen(str);
	char *p = str, *pe = (str + length + 1);
	char *eof = pe;
	int cs;
	size_t current_index = 0;
	size_t current_segment_start_index = 0;
	struct tag_collection* tags = NULL;
	char* tag_key = NULL;
	char* tag_value = NULL;
	int tag_key_allocated = 0;
	int tag_value_allocated = 0;
	int any_tags = 0;

	%%{

		action error_handler {
			goto error_clean_up;
		}	

		action not_final_state_finish {
			goto error_clean_up;
		}	

		action increment_character_counter {
			current_index++;
		}

		action name_parsing {
			current_segment_start_index = current_index;
		}

		action name_parsed {
			size_t current_segment_length = current_index - current_segment_start_index;
			(*datagram)->name = (char*) malloc(current_segment_length + 1);
			ALLOC_CHECK("Not enough memory to save metric name");
			memcpy(
				(*datagram)->name,
				&str[current_segment_start_index],
				current_segment_length
			);
			(*datagram)->name[current_segment_length] = '\0';
			current_segment_start_index = current_index + 1; 
		}

		action tag_parsed {
			size_t key_len = strlen(tag_key) + 1;
			size_t value_len = strlen(tag_value) + 1;
			struct tag* t = (struct tag*) malloc(sizeof(struct tag));
			ALLOC_CHECK("Unable to allocate memory for tag.");
			t->key = (char*) malloc(key_len);
			ALLOC_CHECK("Unable to allocate memory for tag key.");
			memcpy(t->key, tag_key, key_len);
			t->value = (char*) malloc(value_len);
			ALLOC_CHECK("Unable to allocate memory for tag value.");
			memcpy(t->value, tag_value, value_len);
			if (!any_tags) {
				tags = (struct tag_collection*) malloc(sizeof(struct tag_collection));
				ALLOC_CHECK("Unable to allocate memory for tag collection.");
				tags->values = (struct tag**) malloc(sizeof(struct tag*));
				tags->values[0] = t;
				tags->length = 1;
				any_tags = 1;
			} else {
				struct tag** new_tags =
					(struct tag**) realloc(tags->values, sizeof(struct tag*) * (tags->length + 1));
				ALLOC_CHECK("Unable to allocate memory for tags");
				if (tags != NULL) {
					tags->values = new_tags;
					tags->values[tags->length] = t;
					tags->length++;
				}
			}
			free(tag_key);
			free(tag_value);
			tag_key = NULL;
			tag_value = NULL;
			tag_key_allocated = 0;
			tag_value_allocated = 0;
		}

		action value_parsed {
			char* startptr;
			char* endptr;
			if (str[current_segment_start_index] == '+') {
				(*datagram)->explicit_sign = SIGN_PLUS;
				startptr = &str[current_segment_start_index + 1];
			} else if (str[current_segment_start_index] == '-') {
				(*datagram)->explicit_sign = SIGN_MINUS;
				startptr = &str[current_segment_start_index + 1];
			} else {
				(*datagram)->explicit_sign = SIGN_NONE;
				startptr = &str[current_segment_start_index];
			}
			double value = strtod(startptr, &endptr);
			if (startptr == endptr || errno == ERANGE) {
				goto error_clean_up;
			}
			(*datagram)->value = value;
			current_segment_start_index = current_index + 1;
		}

		action type_parsed {
			if (str[current_segment_start_index] == 'c') {
				(*datagram)->type = METRIC_TYPE_COUNTER;
			} else if (str[current_segment_start_index] == 'g') {
				(*datagram)->type = METRIC_TYPE_GAUGE;
			} else {
				(*datagram)->type = METRIC_TYPE_DURATION;
			}
			current_segment_start_index = current_index + 1;
		}

		action tag_key_parsed {
			size_t current_segment_length = current_index - current_segment_start_index;
			tag_key = (char *) realloc(tag_key, current_segment_length + 1);
			ALLOC_CHECK("Not enough memory for tag key buffer.");
			tag_key_allocated = 1;
			memcpy(
				tag_key,
				&str[current_segment_start_index], 
				current_segment_length
			);
			tag_key[current_segment_length] = '\0';
			current_segment_start_index = current_index + 1;
		}

		action tag_value_parsed {
			size_t current_segment_length = current_index - current_segment_start_index;
			tag_value = (char *) realloc(tag_value, current_segment_length + 1);
			ALLOC_CHECK("Not enough memory for tag key buffer.");
			tag_value_allocated = 1;
			memcpy(
				tag_value,
				&str[current_segment_start_index], 
				current_segment_length
			);
			tag_value[current_segment_length] = '\0';
			current_segment_start_index = current_index + 1;
		}

		str_value = [a-z][a-zA-Z0-9_.]*;
		tag_string = [a-zA-Z0-9_.]{1,};
		name = str_value[:,]{1};
		value = ('+'|'-')?[0-9]+(('.')[0-9]+)?(('e'|'E')('+'|'-')?[0-9]+)?('|');
		type = ('c'|'g'|'ms')('@'|'\0'|'\n\0'|("|#"));
		tags = ((tag_string'=')@tag_key_parsed(tag_string(':'|',')@tag_value_parsed))+;
		tags_end = ((tag_string':')@tag_key_parsed(tag_string(','|'\0'|'\n\0')@tag_value_parsed))+;
		main := ((name >name_parsing @name_parsed . (tags @ tag_parsed)? . value @value_parsed . type @type_parsed . (tags_end @ tag_parsed)? ))
			  $~increment_character_counter @!error_handler @/not_final_state_finish;
		# */
		# Initialize and execute.
		write init;
		write exec;

	}%%
	(void)statsd_en_main;
	(void)statsd_error;
	(void)statsd_first_final;

	if (any_tags) {
		char* json = tag_collection_to_json(tags);
		if (json != NULL) {
			(*datagram)->tags = json;
			(*datagram)->tags_pair_count = tags->length;
		}
		free_tag_collection(tags);
		if (tag_key_allocated) free(tag_key);
		if (tag_value_allocated) free(tag_value);
	}
	if (str[length - 1] == '\n')
        str[length - 1] = 0;
	VERBOSE_LOG(2, "Parsed: %s", str);
	return 1;

	error_clean_up:
	if (any_tags) {
		free_tag_collection(tags);
	}
	if (tag_key_allocated) free(tag_key);
	if (tag_value_allocated) free(tag_value);
	if (str[length - 1] == '\n')
        str[length - 1] = 0;
	free_datagram(*datagram);
	METRIC_PROCESSING_ERR_LOG("Throwing away datagram. REASON: unable to parse: %s", str);
	return 0;
};


/**
 * --------------------------------------
 * |                                    |
 * |     UNIT TEST FOR THIS FILE        |
 * |                                    |
 * --------------------------------------
 */
#if _TEST_TARGET == 2

int
main() {
    INIT_TEST("Running tests for ragel parser:", ragel_parser_parse);
    SUITE_HEADER("Unparsable values")
    CHECK_ERROR("", NULL, NULL, 0, METRIC_TYPE_NONE, SIGN_NONE);
    CHECK_ERROR("wow", NULL, NULL, 0, METRIC_TYPE_NONE, SIGN_NONE);
    CHECK_ERROR("wow:2", NULL, NULL, 0, METRIC_TYPE_NONE, SIGN_NONE);
    CHECK_ERROR("wow|g", NULL, NULL, 0, METRIC_TYPE_NONE, SIGN_NONE);
    CHECK_ERROR("2|g", NULL, NULL, 0, METRIC_TYPE_NONE, SIGN_NONE);
    CHECK_ERROR("1:1|c", NULL, NULL, 0, METRIC_TYPE_NONE, SIGN_NONE);
    CHECK_ERROR("e x-2 ple:20|c", NULL, NULL, 0, METRIC_TYPE_NONE, SIGN_NONE);
    CHECK_ERROR("example,tags=dwq=qwddqwd=qwd:10|c", NULL, NULL, 0, METRIC_TYPE_NONE, SIGN_NONE);
    SUITE_HEADER("Basic values");
    CHECK_ERROR("example:-1|c", "example", NULL, 1, METRIC_TYPE_COUNTER, SIGN_MINUS);
    CHECK_ERROR("example:+1|g", "example", NULL, 1, METRIC_TYPE_GAUGE, SIGN_PLUS);
    CHECK_ERROR("example:1|ms", "example", NULL, 1, METRIC_TYPE_DURATION, SIGN_NONE);
    SUITE_HEADER("Sanitizable metric name");
    SUITE_HEADER("Non integer values")
    CHECK_ERROR("example:1.2|c", "example", NULL, 1.2, METRIC_TYPE_COUNTER, SIGN_NONE);
    CHECK_ERROR("example:1.000000004|c", "example", NULL, 1.000000004, METRIC_TYPE_COUNTER, SIGN_NONE);
    CHECK_ERROR("example:1.00000000000000000000000000000004|c", "example", NULL, 1.00000000000000000000000000000004, METRIC_TYPE_COUNTER, SIGN_NONE);
    SUITE_HEADER("Instance descriptors")
    CHECK_ERROR("example,instance=20:20|c", "example", "{\"instance\":\"20\"}", 20, METRIC_TYPE_COUNTER, SIGN_NONE);
    SUITE_HEADER("Tag descriptors")
    CHECK_ERROR("example,tagY=20,tagX=10:10|c", "example", "{\"tagX\":\"10\",\"tagY\":\"20\"}", 10, METRIC_TYPE_COUNTER, SIGN_NONE);
	CHECK_ERROR("example,tagY=20:10|c|#tagY:20,tagW:W", "example", "{\"tagW\":\"W\",\"tagY\":\"20\"}", 10, METRIC_TYPE_COUNTER, SIGN_NONE);
    SUITE_HEADER("Malformed tags")
    SUITE_HEADER("Too long tag descriptor (gets thrown away)")
    CHECK_ERROR("example,[encoded data, 4101 characters]:10|c", "example", NULL, 10, METRIC_TYPE_COUNTER, SIGN_NONE);
    END_TEST();
}

#endif
