/*  Copyright (C) CZ.NIC, z.s.p.o. and contributors
 *  SPDX-License-Identifier: GPL-2.0-or-later
 *  For more information, see <https://www.knot-dns.cz/>
 */

#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>

#include "libknot/yparser/yparser.h"
#include "libknot/attribute.h"
#include "libknot/errcode.h"

extern int _yp_start_state;
extern int _yp_parse(yp_parser_t *parser);

_public_
void yp_init(
	yp_parser_t *parser)
{
	if (parser == NULL) {
		return;
	}

	memset(parser, 0, sizeof(*parser));

	parser->cs = _yp_start_state;
	parser->file.descriptor = -1;
	parser->line_count = 1;
}

_public_
void yp_deinit(
	yp_parser_t *parser)
{
	if (parser == NULL) {
		return;
	}

	if (parser->file.descriptor != -1) {
		munmap((void *)parser->input.start,
		       parser->input.end - parser->input.start);
		close(parser->file.descriptor);
		free(parser->file.name);
	}
}

_public_
int yp_set_input_string(
	yp_parser_t *parser,
	const char *input,
	size_t size)
{
	if (parser == NULL || input == NULL) {
		return KNOT_EINVAL;
	}

	// Reinitialize the parser.
	yp_deinit(parser);
	yp_init(parser);

	// Set the parser input limits.
	parser->input.start   = input;
	parser->input.current = input;
	parser->input.end     = input + size;
	parser->input.eof     = false;

	return KNOT_EOK;
}

_public_
int yp_set_input_file(
	yp_parser_t *parser,
	const char *file_name)
{
	if (parser == NULL || file_name == NULL) {
		return KNOT_EINVAL;
	}

	// Reinitialize the parser.
	yp_deinit(parser);
	yp_init(parser);

	// Try to open the file.
	parser->file.descriptor = open(file_name, O_RDONLY);
	if (parser->file.descriptor == -1) {
		return knot_map_errno();
	}

	// Check for regular file input.
	struct stat file_stat;
	if (fstat(parser->file.descriptor, &file_stat) == -1) {
		close(parser->file.descriptor);
		return knot_map_errno();
	} else if (!S_ISREG(file_stat.st_mode)) {
		close(parser->file.descriptor);
		return KNOT_EFILE;
	}

	// Check for empty file (cannot mmap).
	if (file_stat.st_size > 0) {
		// Map the file to the memory.
		char *start = mmap(0, file_stat.st_size, PROT_READ, MAP_SHARED,
		                   parser->file.descriptor, 0);
		if (start == MAP_FAILED) {
			close(parser->file.descriptor);
			return KNOT_ENOMEM;
		}

		// Try to set the mapped memory advise to sequential.
#if defined(MADV_SEQUENTIAL) && !defined(__sun)
		(void)madvise(start, file_stat.st_size, MADV_SEQUENTIAL);
#else
#ifdef POSIX_MADV_SEQUENTIAL
		(void)posix_madvise(start, file_stat.st_size, POSIX_MADV_SEQUENTIAL);
#endif /* POSIX_MADV_SEQUENTIAL */
#endif /* MADV_SEQUENTIAL && !__sun */

		// Set the parser input limits.
		parser->input.start   = start;
		parser->input.current = start;
		parser->input.end     = start + file_stat.st_size;

		parser->input.eof = false;
	} else {
		parser->input.eof = true;
	}

	parser->file.name = strdup(file_name);

	return KNOT_EOK;
}

_public_
int yp_parse(
	yp_parser_t *parser)
{
	if (parser == NULL) {
		return KNOT_EINVAL;
	}

	int ret = KNOT_EPARSEFAIL;

	// Run the parser until found new item, error or end of input.
	do {
		// Check for the end of the input.
		if (parser->input.current == parser->input.end) {
			if (parser->input.eof) {
				// End of parsing.
				return KNOT_EOF;
			} else {
				// Set the parser to final parsing.
				parser->input.eof = true;
			}
		}

		// Parse the next item.
		ret = _yp_parse(parser);
	} while (ret == KNOT_EFEWDATA);

	return ret;
}
