#include <stdio.h>
#include "lib/mlr_arch.h"
#include "lib/mlrutil.h"
#include "input/line_readers.h"

// ----------------------------------------------------------------
char* mlr_alloc_read_line_single_delimiter(
	FILE*      fp,
	int        delimiter,
	size_t*    pold_then_new_strlen,
	int        do_auto_line_term,
	context_t* pctx)
{
	size_t linecap = power_of_two_above(*pold_then_new_strlen + 1); // +1 for null-terminator
	char* restrict line = mlr_malloc_or_die(linecap);
	char* restrict p = line;
	int reached_eof = FALSE;
	int c;
	int nread = 0;

	while (TRUE) {
		size_t offset = p - line;
		if (offset >= linecap) {
			linecap = linecap << 1;
			line = mlr_realloc_or_die(line, linecap);
			p = line + offset;
		}
		c = mlr_arch_getc(fp);
		if (c == EOF) {
			*p = 0;
			reached_eof = TRUE;
			break;
		} else if (c == delimiter) {
			nread++;
			*p = 0;
			break;
		} else {
			nread++;
			*(p++) = c;
		}
	}

	if (do_auto_line_term) {
		char* q = p - 1;
		if (q >= line && *q == '\r') {
			*q = 0;
			context_set_autodetected_crlf(pctx);
			p = q;
		} else {
			context_set_autodetected_lf(pctx);
		}
	}

	// linelen excludes line-ending characters.
	// nread   includes line-ending characters.
	int linelen = p - line;
	if (nread == 0 && reached_eof) {
		free(line);
		line = NULL;
		linelen = 0;
	}
	*pold_then_new_strlen = linelen;

	return line;
}

// ----------------------------------------------------------------
char* mlr_alloc_read_line_multiple_delimiter(
	FILE*      fp,
	char*      delimiter,
	int        delimiter_length,
	size_t*    pold_then_new_strlen)
{
	size_t linecap = power_of_two_above(*pold_then_new_strlen + 1); // +1 for null-terminator
	char* line = mlr_malloc_or_die(linecap);
	char* p = line; // points to null-terminator in (chomped) output string
	char* q = line; // points to end of line in (non-chomped) data read from file
	int reached_eof = FALSE;
	int c;
	int nread = 0;
	int dlm1 = delimiter_length - 1;
	char delimend = delimiter[dlm1];

	while (TRUE) {
		size_t offset = q - line;
		if (offset >= linecap-1) {
			linecap = linecap << 1;
			line = mlr_realloc_or_die(line, linecap);
			q = line + offset;
		}
		c = mlr_arch_getc(fp);
		if (c == EOF) {
			*q = 0;
			reached_eof = TRUE;
			p = q;
			break;
		} else if (c == delimend) {
			// For efficiency, do a single-character test to see if we've seen
			// the last character in the line-ending sequence. If we have, then
			// strcmp back to see if we've seen the entire line-ending sequence.
			//
			// This function exists separately from in order to avoid the performance
			// penalty of this strcmp.
			nread++;
			*(q++) = c;
			p = q - delimiter_length;
			if (q - line >= delimiter_length && memcmp(p, delimiter, delimiter_length) == 0) {
				*p = 0;
				break;
			}
		} else {
			nread++;
			*(q++) = c;
		}
	}

	// linelen excludes line-ending characters.
	// nread   includes line-ending characters.
	int linelen = p - line;
	if (nread == 0 && reached_eof) {
		free(line);
		line = NULL;
		linelen = 0;
	}
	*pold_then_new_strlen = linelen;

	return line;
}

// ----------------------------------------------------------------
char* mlr_alloc_read_line_single_delimiter_stripping_comments(
	FILE*      fp,
	int        delimiter,
	size_t*    pold_then_new_strlen,
	int        do_auto_line_term,
	comment_handling_t comment_handling,
	char*      comment_string,
	context_t* pctx)
{
	return mlr_alloc_read_line_single_delimiter_stripping_comments_aux(
		fp,
		delimiter,
		pold_then_new_strlen,
		do_auto_line_term,
		comment_handling,
		comment_string,
		NULL,
		pctx);
}

char* mlr_alloc_read_line_single_delimiter_stripping_comments_aux(
	FILE*      fp,
	int        delimiter,
	size_t*    pold_then_new_strlen,
	int        do_auto_line_term,
	comment_handling_t comment_handling,
	char*      comment_string,
	int*       pnum_lines_comment_skipped,
	context_t* pctx)
{
	if (pnum_lines_comment_skipped != NULL)
		*pnum_lines_comment_skipped = 0;
	while (TRUE) {
		char* line = mlr_alloc_read_line_single_delimiter(
			fp, delimiter, pold_then_new_strlen, do_auto_line_term, pctx);
		if (line == NULL) {
			return line;
		} else if (string_starts_with(line, comment_string)) {
			if (pnum_lines_comment_skipped != NULL)
				(*pnum_lines_comment_skipped)++;
			if (comment_handling == PASS_COMMENTS) {
				fputs(line, stdout);
				if (do_auto_line_term) {
					fputs(pctx->auto_line_term, stdout);
				} else {
					fputc(delimiter, stdout);
				}
				fflush(stdout);
			}
			free(line);
		} else {
			return line;
		}
	}
}

// ----------------------------------------------------------------
char* mlr_alloc_read_line_multiple_delimiter_stripping_comments(
	FILE*      fp,
	char*      delimiter,
	int        delimiter_length,
	size_t*    pold_then_new_strlen,
	comment_handling_t comment_handling,
	char*      comment_string)
{
	return mlr_alloc_read_line_multiple_delimiter_stripping_comments_aux(
		fp,
		delimiter,
		delimiter_length,
		pold_then_new_strlen,
		comment_handling,
		comment_string,
		NULL);
}

// ----------------------------------------------------------------
char* mlr_alloc_read_line_multiple_delimiter_stripping_comments_aux(
	FILE*      fp,
	char*      delimiter,
	int        delimiter_length,
	size_t*    pold_then_new_strlen,
	comment_handling_t comment_handling,
	char*      comment_string,
	int*       pnum_lines_comment_skipped)
{
	if (pnum_lines_comment_skipped != NULL)
		*pnum_lines_comment_skipped = 0;
	while (TRUE) {
		char* line = mlr_alloc_read_line_multiple_delimiter(
			fp, delimiter, delimiter_length, pold_then_new_strlen);
		if (line == NULL) {
			return line;
		} else if (string_starts_with(line, comment_string)) {
			if (pnum_lines_comment_skipped != NULL)
				(*pnum_lines_comment_skipped)++;
			if (comment_handling == PASS_COMMENTS) {
				fputs(line, stdout);
				fputs(delimiter, stdout);
				fflush(stdout);
			}
			free(line);
		} else {
			return line;
		}
	}
}
