/** @license 2023 Neil Edelman, distributed under the terms of the
 [MIT License](https://opensource.org/licenses/MIT).
 @std C11 */
#include "../src/flights.h"
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>


static void flight_to_string(const union line64 line, const struct flight *u,
	char (*const a)[12]) { (void)u; date32_to_string(line.date, a); }
static int flight_compare(const union line64 a, const union line64 b)
	{ return a.u64 > b.u64; }
#define TREE_NAME flight
#define TREE_KEY union line64
#define TREE_VALUE struct flight
#define TREE_COMPARE
#define TREE_TO_STRING
#define TREE_BODY
#include "../src/tree.h"


/*!conditions:re2c*/

static int scan(struct flight_tree *const f,
	union date32 date, const char *const text) {
	const char *YYCURSOR, *YYMARKER, *yyt1, *yyt2, *s0, *s1, *t0, *t1;
	enum YYCONDTYPE condition = yycline;
	size_t line = 1;
	char datestr[12] = {0};
	const char *why = "unexpected";
	struct flight *flight = 0;
	assert(f && text);
	YYCURSOR = YYMARKER = yyt1 = text;
	/*!re2c /**/
	re2c:define:YYCTYPE = char;
	re2c:yyfill:enable = 0;
	re2c:define:YYGETCONDITION = "condition";
	re2c:define:YYSETCONDITION = "condition = @@;";
	re2c:define:YYGETCONDITION:naked = 1;
	re2c:define:YYSETCONDITION:naked = 1;

	ws = [ \t];
	glyph = [^\x00-\x20\x7f]; // [^\x00\n\t ] + all weird
	semitext = glyph \ ";";
	natural = [1-9][0-9]*;
	minutes = [0-5][0-9];
	zero_natural = natural | "0";
	airport = [A-Z0-9]{4,4};
	*/
	for( ; ; ) { /*!re2c /**/
		/* Default ignore. */
		<skip> [^\n\x00] { continue; }
		<skip> "\x00" { why = "no newline at end of file"; goto catch; }
		<line> "\x00" { return 1; }
		<line, skip> "\n" => line { line++; continue; }
		<line> * :=> skip

		/* Except this . . . */
		<line> "[glider]" :=> glider_type
		/* type, reg, launch, how, height, landing, pilot, dual, instr, remarks
		 eg, [glider] 2-33A; C-GCLK; CYQQ; A; 2000'; CYQQ; ;:13;; Peters D1 */
		<glider_type> * { why = "type unrecognized"; goto catch; }
		<glider_reg> * { why = "reg unrecognized"; goto catch; }
		<glider_launch> * { why = "launch unrecognized"; goto catch; }
		<glider_how> * { why = "how unrecognized"; goto catch; }
		<glider_height> * { why = "height unrecognized"; goto catch; }
		<glider_landing> * { why = "landing unrecognized"; goto catch; }
		<glider_pilot> * { why = "pilot unrecognized"; goto catch; }
		<glider_dual> * { why = "dual unrecognized"; goto catch; }
		<glider_instr> * { why = "instr unrecognized"; goto catch; }
		<glider_remarks> * { why = "remarks unrecognized"; goto catch; }
		<glider_type> ws* @s0 semitext+ @s1 ws* ";" => glider_reg {
			const union line64 key
				= {{ (uint32_t)line, {{ date.day, date.month, date.year }} }};
			assert(!flight);
			if(line > UINT32_MAX) { why = "line overflow"; goto catch; }
			switch(flight_tree_try(f, key, &flight)) {
			case TREE_PRESENT: why = "duplicate key";
			case TREE_ERROR: goto catch;
			case TREE_ABSENT: flight->type = GLIDER; break;
			}
			flight->glider.type.a = s0, flight->glider.type.b = s1;
			continue;
		}
		<glider_reg> ws* @s0 semitext+ @s1 ws* ";" => glider_launch
			{ flight->glider.reg.a = s0, flight->glider.reg.b = s1; continue; }
		<glider_launch> ws* @s0 airport @s1 ws* ";" => glider_how
			{ flight->glider.launch.a = s0, flight->glider.launch.b = s1;
			continue; }
		<glider_how> ws* @s0 [MWA] ws* ";" => glider_height {
			switch(*s0) {
			case 'M': flight->glider.how = MotorCarTow; break;
			case 'W': flight->glider.how = Winch; break;
			case 'A': flight->glider.how = AeroTow; break;
			default: assert(0); break;
			}
			continue;
		}
		<glider_height> ws* @s0 natural @s1 "'" ws* ";" => glider_landing
			{ if(!pair_to_natural(s0, s1, &flight->glider.height_ft));
			continue; }
		<glider_landing> ws* @s0 airport @s1 ws* ";" => glider_pilot
			{ flight->glider.landing.a = s0, flight->glider.landing.b = s1;
			continue; }
		<glider_pilot> ws* ";" => glider_dual /* not PIC */
			{ flight->glider.pilot_min = 0; continue; }
		<glider_pilot> ws* @s0 natural? @s1 ":" @t0 minutes @t1 ws* ";"
			=> glider_dual { if(!pair_colon_to_minutes(s0, s1, t0, t1,
			&flight->glider.pilot_min)) { why = "pilot time"; goto catch; }
			continue; }
		<glider_dual> ws* ";" => glider_instr
			{ flight->glider.dual_min = 0; continue; }
		<glider_dual> ws* @s0 natural? @s1 ":" @t0 minutes @t1 ws* ";"
			=> glider_instr { if(!pair_colon_to_minutes(s0, s1, t0, t1,
			&flight->glider.dual_min)) { why = "dual time"; goto catch; }
			continue; }
		<glider_instr> ws* ";" => glider_remarks
			{ flight->glider.instr_min = 0; continue; }
		<glider_instr> ws* @s0 natural? @s1 ":" @t0 minutes @t1 ws* ";"
			=> glider_remarks { if(!pair_hours_to_minutes(s0, s1, t0, t1,
			&flight->glider.instr_min)) { why = "instr time"; goto catch; }
			continue; }
		<glider_remarks> ws* "\n" => line
			{ flight->glider.remarks.a = flight->glider.remarks.b = 0;
			flight = 0; line++; continue; }
		<glider_remarks> ws* @s0 glyph+ (ws+ glyph+)* @s1 "\n" => line
			{ flight->glider.remarks.a = s0, flight->glider.remarks.b = s1;
			flight = 0; line++; continue; }

		/* And this . . . */
		<line> "[flight]" :=> flight_type
		/* type; registration; launch -- landing; pic; sic;
		 single engine day dual; pilot; instrument simulated; actual; remarks */
		<flight_type> * { why = "type unrecognized"; goto catch; }
		<flight_reg> * { why = "reg unrecognized"; goto catch; }
		<flight_airports> * { why = "airports unrecognized"; goto catch; }
		<flight_pic> * { why = "pic unrecognized"; goto catch; }
		<flight_sic> * { why = "sic unrecognized"; goto catch; }
		<flight_dual> * { why = "dual time unrecognized"; goto catch; }
		<flight_pilot> * { why = "pilot time unrecognized"; goto catch; }
		<flight_ifrsim> * { why = "simulated ifr time unrecognized";
			goto catch; }
		<flight_ifr> * { why = "ifr time unrecognized"; goto catch; }
		<flight_remarks> * { why = "remarks unrecognized"; goto catch; }
		<flight_type> ws* @s0 semitext+ @s1 ws* ";" => flight_reg {
			const union line64 key
				= {{ (uint32_t)line, {{ date.day, date.month, date.year }} }};
			assert(!flight);
			if(line > UINT32_MAX) { why = "line overflow"; goto catch; }
			switch(flight_tree_try(f, key, &flight)) {
			case TREE_PRESENT: why = "duplicate key";
			case TREE_ERROR: goto catch;
			case TREE_ABSENT: flight->type = POWER; break;
			}
			flight->power.type.a = s0, flight->power.type.b = s1;
			continue;
		}
		<flight_reg> ws* @s0 semitext+ @s1 ws* ";" => flight_airports
			{ flight->power.reg.a = s0, flight->power.reg.b = s1; continue; }
		<flight_airports> ws* @s0 airport @s1 ws* "--"
			ws* @t0 airport @t1 ws* ";" => flight_pic {
			flight->power.launch.a = s0, flight->power.launch.b = s1;
			flight->power.landing.a = t0, flight->power.landing.b = t1;
			continue;
		}
		<flight_pic> ws* @s0 semitext+ (ws+ semitext+)* @s1 /* ws*? */";"
			=> flight_sic { flight->power.pilot.a = s0,
			flight->power.pilot.b = s1; continue; }
		<flight_sic> ws* ";" => flight_dual
			{ flight->power.copilot.a = flight->power.copilot.b = 0; continue; }
		<flight_sic> ws* @s0 semitext+ (ws+ semitext+)* @s1 ";"
			=> flight_dual { flight->power.copilot.a = s0,
			flight->power.copilot.b = s1; continue; }
		<flight_dual> ws* ";" => flight_pilot
			{ flight->power.dual_min = 0; continue; }
		<flight_dual> ws* @s0 zero_natural? @s1 "." @t0 [0-9] @t1 "h" ws* ";"
			=> flight_pilot { if(!pair_hours_to_minutes(s0, s1, t0, t1,
			&flight->power.dual_min)) { why = "dual time"; goto catch; }
			continue; }
		<flight_pilot> ws* ";" => flight_ifrsim
			{ flight->power.pilot_min = 0; continue; }
		<flight_pilot> ws* @s0 zero_natural? @s1 "." @t0 [0-9] @t1 "h" ws* ";"
			=> flight_ifrsim { if(!pair_hours_to_minutes(s0, s1, t0, t1,
			&flight->power.pilot_min)) { why = "pilot time"; goto catch; }
			continue; }
		<flight_ifrsim> ws* ";" => flight_ifr
			{ flight->power.ifrsim_min = 0; continue; }
		<flight_ifrsim> ws* @s0 zero_natural? @s1 "." @t0 [0-9] @t1 "h" ws* ";"
			=> flight_ifr { if(!pair_hours_to_minutes(s0, s1, t0, t1,
			&flight->power.ifrsim_min)) { why = "simulated ifr time";
			goto catch; } continue; }
		<flight_ifr> ws* ";" => flight_remarks
			{ flight->power.ifr_min = 0; continue; }
		<flight_ifr> ws* @s0 zero_natural? @s1 "." @t0 [0-9] @t1 "h" ws* ";"
			=> flight_remarks { if(!pair_hours_to_minutes(s0, s1, t0, t1,
			&flight->power.ifr_min)) { why = "pilot time"; goto catch; }
			continue; }
		<flight_remarks> ws* "\n" => line
			{ flight->power.remarks.a = flight->power.remarks.b = 0;
			flight = 0; line++; continue; }
		<flight_remarks> ws* @s0 glyph+ (ws+ glyph+)* @s1 "\n" => line
			{ flight->power.remarks.a = s0, flight->power.remarks.b = s1;
			flight = 0; line++; continue; }
	*/ }
	assert(0); /* Never gets here. */
catch:
	if(!errno) errno = EILSEQ;
	date32_to_string(date, &datestr);
	fprintf(stderr, "%s line %zu: %s.\n", datestr, line, why);
	return 0;
}
void flights_(struct flight_tree *const f) { flight_tree_(f); }
struct flight_tree flights(struct journal *const j) {
	struct flight_tree f = flight_tree();
	struct journal_iterator it;
	union date32 date;
	const char *text;
	assert(j);
	it = journal_iterator(j);
	while(journal_next(&it, &date, &text)) if(!scan(&f, date, text)) goto catch;
	goto finally;
catch:
	flights_(&f);
finally:
	return f;
}
const char *flights_to_string(const struct flight_tree *const f)
	{ return flight_tree_to_string(f); }
struct flight_tree_iterator flights_iterator(struct flight_tree *const f)
	{ return flight_tree_iterator(f); }
int flights_next(struct flight_tree_iterator *const it, union line64 *const k,
	const struct flight **const v) {
	assert(it && k && v);
	if(!flight_tree_next(it)) return 0;
	*k = flight_tree_key(it);
	*v = flight_tree_value(it);
	return 1;
}
