/*  liucsv - microlib for CSV parsing
    Copyright (C) 2024  Tibor 'Igor2' Palinkas

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library 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
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 31 Milk Street, # 960789 Boston, MA 02196 USA
*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "libucsv.h"

#define in_quote ((ctx->state & UCSV_DQUOTE) || (ctx->state & UCSV_SQUOTE))

#define GROW 512

static void append(ucsv_ctx_t *ctx, char c)
{
	if (ctx->used >= ctx->alloced) {
		ctx->alloced += GROW;
		ctx->buf = realloc(ctx->buf, ctx->alloced+1);
	}
	ctx->buf[ctx->used] = c;
	ctx->used++;
	ctx->buf[ctx->used] = '\0';
}

#define RETURN(val) \
do { \
	ctx->used = 0; \
	return val; \
} while(0)

static ucsv_token_t ucsv_parser_char_(ucsv_ctx_t *ctx, int c)
{
	if (ctx->forced_eof)
		return UCSV_END;

	if (ctx->state & UCSV_ESCAPE) {
		ctx->state &= ~UCSV_ESCAPE;
		append(ctx, c);
		goto more;
	}

	switch(c) {
		case '\\':
			if (!ctx->enable_backslash)
				break; /* normal append if not enabled */
			ctx->state |= UCSV_ESCAPE;
			break;

		case ',':
			if (in_quote)
				break; /* comma within quoted normal append */
			RETURN(UCSV_TOK_TEXT);

		case '\r':
			if (in_quote)
				break; /* newline within quoted normal append */
			return UCSV_MORE; /* ignore outside of quotes */

		case '\n':
			if (in_quote)
				break; /* newline within quoted normal append */
			RETURN(UCSV_TOK_END_LINE);

		case '\'':
			if (!ctx->enable_squote)
				break; /* normal append if not enabled */
			if (ctx->state & UCSV_SQUOTE)
				ctx->state &= ~UCSV_SQUOTE;
			else
				ctx->state |= UCSV_SQUOTE;
			goto more;

		case '"':
			if ((ctx->last == '"') && !ctx->disable_dqdq) {
				append(ctx, c);
				/* and go on inverting the quote state to revert the effect of the previous dq */
			}

			if (ctx->state & UCSV_DQUOTE)
				ctx->state &= ~UCSV_DQUOTE;
			else
				ctx->state |= UCSV_DQUOTE;
			goto more;

		case EOF:
			if (ctx->last != '\n') {
				/* need to flush last cell if the file doesn't end in a newline */
				ctx->forced_eof = 1;
				RETURN(UCSV_TOK_TEXT);
			}

			if (in_quote) {
				/* error! eof in quote */
			}
			return UCSV_END;
	}
	append(ctx, c);
	more:;
	return UCSV_MORE;
}

ucsv_token_t ucsv_parser_char(ucsv_ctx_t *ctx, int c)
{
	ucsv_token_t tok = ucsv_parser_char_(ctx, c);
	ctx->last = c;
	return tok;
}

void ucsv_parser_uninit(ucsv_ctx_t *ctx)
{
	free(ctx->buf);
	ctx->buf = NULL;
	ctx->used = ctx->alloced = 0;
	ctx->last = 0;
	ctx->forced_eof = 0;
	ctx->state = UCSV_NONE;
}

void ucsv_print_cell(ucsv_ctx_t *ctx, FILE *f, const char *str, int is_first)
{
	const char *s;

	if (!is_first)
		fputc(',', f);

	if ((str == NULL) || (*str == '\0'))
		return; /* don't print "", that could also mean an escaped double-quote character */

	fputc('"', f);

	/* only double quote needs to be escaped, and that's done by repeating it */
	for(s = str; *s != '\0'; s++) {
		if (*s == '"') {
			if (ctx->disable_dqdq && ctx->enable_backslash)
				fputc('\\', f);
			else
				fputc('"', f);
		}
		fputc(*s, f);
	}

	fputc('"', f);
}

void ucsv_print_endline(FILE *f)
{
	fputc('\r', f);
	fputc('\n', f);
}
