#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stdbool.h>

#define INLINE static inline

/* === CSV-STRING Interface ===  */

typedef struct {
	char *str;
	int cap, pos;
} csv_string_t;

INLINE void
csv_string_init(csv_string_t *csv_string)
{
	static const int INITIAL_CAP = 16;

	if ( (csv_string->str = malloc(INITIAL_CAP)) != NULL ) {
		csv_string->cap = INITIAL_CAP;
		csv_string->pos = 0;
	}
}

INLINE void
csv_string_reset(csv_string_t *csv_string)
{
	csv_string->pos = 0;
}

INLINE bool
csv_string_empty(csv_string_t *csv_string)
{
	return ( csv_string->pos == 0 ) ? true : false;
}

INLINE void
csv_string_append(csv_string_t *csv_string, int ch)
{
	if ( csv_string->pos == csv_string->cap ) {	/* grow if needed */
		int cap = (csv_string->cap * 3) / 2;	/* *= 1.5 */
		char *str = realloc(csv_string->str, cap);
		if ( str == NULL ) {
			return;
		}
		csv_string->str = str;
		csv_string->cap = cap;
	}

	csv_string->str[csv_string->pos++] = ch; /* append char */
}

INLINE void
csv_string_free(csv_string_t *csv_string)
{
	free(csv_string->str);
}

/* === CSV-FIELD Interface === */

typedef struct {
	int *fields;
	int cap, pos;
} csv_field_t;

INLINE void
csv_field_init(csv_field_t *csv_field)
{
	static const size_t INITIAL_CAP = 16;

	if ( (csv_field->fields = malloc(INITIAL_CAP * sizeof(csv_field->fields[0]))) != NULL ) {
		csv_field->cap = INITIAL_CAP;
		csv_field->pos = 0;
	}
}

INLINE void
csv_field_reset(csv_field_t *csv_field)
{
	csv_field->pos = 0;
}

INLINE void
csv_field_append(csv_field_t *csv_field, int idx)
{
	if ( csv_field->pos == csv_field->cap ) {	/* grow if needed */
		int cap = (csv_field->cap * 3) / 2;	/* *= 1.5 */
		int *fields = realloc(csv_field->fields, cap * sizeof(csv_field->fields[0]));
		if ( fields == NULL ) {
			return;
		}
		csv_field->fields = fields;
		csv_field->cap = cap;
	}

	csv_field->fields[csv_field->pos++] = idx; /* append idx */
}

INLINE void
csv_field_free(csv_field_t *csv_field)
{
	free(csv_field->fields);
}

/* === CSV Interface === */

typedef struct {
	char quote_symbol;
	char sep_symbol;
} csv_options_t;

static csv_options_t default_csv_options = {
	.quote_symbol = '"',
	.sep_symbol = ','
};

typedef struct {
	csv_options_t *csv_options;
	csv_string_t csv_string;
	csv_field_t csv_field;
} csv_t;

void
csv_init_opt(csv_t *csv, csv_options_t *csv_options)
{
	csv->csv_options = ( csv_options != NULL ) ? csv_options : &default_csv_options;

	csv_string_init(&csv->csv_string);
	csv_field_init(&csv->csv_field);
}

void
csv_init(csv_t *csv)
{
	csv_init_opt(csv, &default_csv_options);
}

void
csv_free(csv_t *csv)
{
	csv_string_free(&csv->csv_string);
	csv_field_free(&csv->csv_field);
}

int
csv_nfields(csv_t *csv)
{
	if ( csv->csv_string.pos != 0 ) {
		return csv->csv_field.pos;
	}
	return 0;
}

const char *
csv_field(csv_t *csv, int idx)
{
	return &csv->csv_string.str[csv->csv_field.fields[idx]];
}

int
csv_read(csv_t *csv, FILE *in)
{
	enum {
		ZST_START_FIELD,
		ZST_QUOTED_FIELD,
		ZST_SIMPLE_FIELD,
		ZST_END_FIELD,
		ZST_END_LINE,
		ZST_END_FILE,
	};

	register const int QUOTE = csv->csv_options->quote_symbol;
	register const int SEP = csv->csv_options->sep_symbol;

	csv_string_reset(&csv->csv_string);
	csv_field_reset(&csv->csv_field);

	for ( int zst = ZST_START_FIELD; ; ) {
		int ch;

		switch ( zst ) {
		case ZST_START_FIELD:
			csv_field_append(&csv->csv_field, csv->csv_string.pos);

			ch = getc(in);
			if ( ch == EOF ) {
				zst = ZST_END_FILE;
			}
			else if ( ch == '\r' ) {	/* Teste auf CR.. */
				ch = getc(in);
				if ( ch != '\n' ) {		/* ... LF */
					ungetc(ch, in);
				}
				zst = ZST_END_LINE;
			}
			else if ( ch == '\n' ) {
				zst = ZST_END_LINE;
			}
			else if ( ch == SEP ) {
				zst = ZST_END_FIELD;
			}
			else if ( ch == QUOTE ) {
				zst = ZST_QUOTED_FIELD;
			}
			else {
				csv_string_append(&csv->csv_string, ch);
				zst = ZST_SIMPLE_FIELD;
			}
			break;

		case ZST_QUOTED_FIELD:
			do {
				ch = getc(in);
				if ( ch == EOF ) {
					zst = ZST_END_FILE;
				}
				else if ( ch == QUOTE ) {
					ch = getc(in);
					if ( ch == EOF ) {
						zst = ZST_END_FILE;
					}
					else if ( ch == QUOTE ) {
						csv_string_append(&csv->csv_string, QUOTE);
					}
					else if ( ch == SEP ) {
						zst = ZST_END_FIELD;
					}
					else if ( ch == '\r' ) {
						ch = getc(in);
						if ( ch != '\n' ) {
							ungetc(ch, in);
						}
						zst = ZST_END_LINE;
					}
					else if ( ch == '\n' ) {
						zst = ZST_END_LINE;
					}
					else {
						csv_string_append(&csv->csv_string, QUOTE);
						ungetc(ch, in);	/* zuviel gelesenes Zeichen zurückstellen */
					}
				}
				else {
					csv_string_append(&csv->csv_string, ch);
				}
			} while ( zst == ZST_QUOTED_FIELD );
			break;

		case ZST_SIMPLE_FIELD:
			do {
				ch = getc(in);
				if ( ch == EOF ) {
					zst = ZST_END_FILE;
				}
				else if ( ch == SEP ) {
					zst = ZST_END_FIELD;
				}
				else if ( ch == '\r' ) {
					ch = getc(in);
					if ( ch != '\n' ) {
						ungetc(ch, in);
					}
					zst = ZST_END_LINE;
				}
				else if ( ch == '\n' ) {
					zst = ZST_END_LINE;
				}
				else {
					csv_string_append(&csv->csv_string, ch);
				}
			} while ( zst == ZST_SIMPLE_FIELD );
			break;

		case ZST_END_FIELD:
			csv_string_append(&csv->csv_string, '\0');
			zst = ZST_START_FIELD;
			break;

		case ZST_END_LINE:
			csv_string_append(&csv->csv_string, '\0');
			return csv->csv_field.pos;

		case ZST_END_FILE:
			if ( !csv_string_empty(&csv->csv_string) ) {
				/* EOF wurde gelesen, allerdings sind Daten zur Vearbeitung vorhanden! */
				csv_string_append(&csv->csv_string, '\0');
				return csv->csv_field.pos;
			}
			return -1;	/* EOF reached */
		}
	}
}

int
main(void)
{
	csv_t csv[1];
	int n, line = 0;

	setvbuf(stdin, NULL, _IOFBF, 16384);

#if 0
	char data[] = "\"\"aaa\",\"b\"\"bb\",\"ccc\"\n"
		"zzz,,yyy,xxx\n"
		",\n"
		"";
	FILE *in = fmemopen(data, strlen(data), "rb");

	if ( in == NULL ) {
		printf("no data...\n");
		return 0;
	}

	csv_init(csv);
	while ( (n = csv_read(csv, in)) != -1 ) {
		printf("%4d: ", ++line);

		for ( int i = 0; i < n; ++i ) {
			printf("'%s' ", csv_field(csv, i));
		}
		putchar('\n');

		if ( line == 100 )
			break;
	}
	csv_free(csv);
#endif

#if 0
	csv_init(csv);
	while ( (n = csv_read(csv, stdin)) != -1 ) {
		printf("%4d: ", ++line);

		for ( int i = 0; i < n; ++i ) {
			printf("'%s' ", csv_field(csv, i));
		}
		putchar('\n');

		if ( line == 100 )
			break;
	}
	csv_free(csv);
#endif

#if 1
	clock_t start = clock();

	csv_init(csv);
	while ( (n = csv_read(csv, stdin)) != -1 ) {
		++line;
	}
	csv_free(csv);

	clock_t end = clock();

	printf("%d lines processed, duration: %.3lf sec\n", line, ((double)(end - start)) / CLOCKS_PER_SEC);

	return EXIT_SUCCESS;
#endif
}
