/* $Id: lorefile.c,v 1.5 1997/07/01 14:44:41 archer Exp $
 *	LOREFILES.C - Report module of the Lorenai mapping tool
 */
#include "lorenai.h"
#include "misc.h"
#include <ctype.h>
#include <memory.h>
#include <string.h>
/*** HACK ***/
#define no_units_in_area()	0
#define unit_present_in_area(a)	0


/**
 ** Global variables
 **/
area_s	lorenai_map[MAX_MAP+1][MAX_MAP+1];
char	work[8192];
char	*my_faction_name;
int	my_faction_number;
ttype_s	*area_types;
aname_s	*area_names;
item_s	*known_items;
build_s	*all_buildings;
int	month, year;


/**
 ** Privates variables
 **/
static FILE	*parsed_file;
static area_s	*current_area;


/**
 ** FREE_ITEMS
 **	Free a list of items
 **/
static void free_items(poss_s *p)
{
	if (!p)
		return;
	free_items(p->next);
	free(p);
}


/**
 ** PRINT_ITEM_LIST
 **	List a set of items, with prices (if marketed)
 **/
static void print_item_list(FILE *f, poss_s *list)
{
	fprintf(f, "%d %s", list->amount, list->amount == 1 ?
					  list->kind->singular :
					  list->kind->plural);
	if (list->price)
		fprintf(f, " at $%d", list->price);
	if (list->next) {
		putc(',', f);
		putc(' ', f);
		print_item_list(f, list->next);
	} else {
		putc('.', f);
		putc('\n', f);
	}
}


/**
 ** REVERSE_STRUCTURE
 **	Print the list of structures in reverse order
 **/
static void reverse_structure(FILE *f, build_s *b)
{
	if (!b)
		return;
	reverse_structure(f, b->next);
	fprintf(f, "+ %s [%d], %s.\n", b->name, b->number, b->type->name);
}


/**
 ** WRITE_GUILD_MAP
 **	Update a guild map
 **/
void write_guild_map(FILE *f)
{
area_s	*area;
int	v, h;
/*
 * Just checking
 */
	if (f == 0)
		return;
/*
 * Loop
 */
	for (v = 1; v <= MAX_MAP; v++)
		for (h = 1; h <= MAX_MAP; h++) {
			area = &lorenai_map[h][v];
			if (area->name) {
				fputs("------------------------------------------------------------------------\n", f);
				fprintf(f, "%s (%d,%d), %s, ", area->name->name, v, h, area->type->name);
				if (area->population) {
					fprintf(f, "population %d ", area->population);
					if (area->inhabitants) {
						fprintf(f, "%s", area->inhabitants->plural);
					}
					if (area->income)
						fprintf(f, ", $%d.", area->income);
				}
				putc('\n', f);
/*
 * Check info
 */
				if (area->faction_checked)
					fprintf(f, "(%d/%d:%d)\n", area->month_checked, area->year_checked, area->faction_checked);
/*
 * Details?
 */
				if (area->wages)
					fprintf(f, "  Wages: $%d\n", area->wages);
				if (area->taxed)
					fprintf(f, "  Taxes: %d%%\n", area->taxed);
				if (area->entertain)
					fprintf(f, "  Entertainment available: $%d\n", area->entertain);
				if (area->sold) {
					fprintf(f, "  For sale: ");
					print_item_list(f, area->sold);
				}
				if (area->wanted) {
					fprintf(f, "  Wanted: ");
					print_item_list(f, area->wanted);
				}
				if (area->products) {
					fprintf(f, "  Resources: ");
					print_item_list(f, area->products);
				}
				reverse_structure(f, area->structures);
			}
		}
/*
 * End of map
 */
	fputs("------------------------------------------------------------------------\n", f);
	if (f != stdout)
		fclose(f);
}


/**
 ** IS_LOCATION_CANDIDATE
 **	Validate if a line has the form "Name (x,y), word..."
 **/
static int is_location_candidate(char *s)
{
/*
 * Must begin with a letter
 */
	if (!isalpha(*s++))
		return 0;
	while (*s)
		if (!isspace(*s) && !isalnum(*s))
			break;
		else
			s++;
	if (*s++ != '(')
		return 0;
	if (!isdigit(*s++))
		return 0;
	while (isdigit(*s))
		s++;
	if (*s++ != ',')
		return 0;
	if (!isdigit(*s++))
		return 0;
	while (isdigit(*s))
		s++;
	if (*s++ != ')')
		return 0;
	if (*s++ != ',')
		return 0;
	return 1;
}


/**
 ** IS_ITEM
 **	This is a product. Therefore, it isn't a man
 **/
static void is_item(poss_s *list)
{
	while (list) {
		list->kind->man = 0;
		list = list->next;
	}
}


/**
 ** ITEM_KIND
 **	Return the item from the name
 **/
static item_s *item_kind(char **adv, int amount)
{
item_s	*i;
char	*s;
int	l;
char	namebuf[1024];
/*
 * Complex algorithm to figure out the item type
 */
	s = *adv;
	l = 0;
	while (*s) {
		if (!isalpha(*s) && !isspace(*s))
			break;
		if (strncmp(s, " at ", 4) == 0)
			break;
		l++;
		s++;
	}
	s = *adv;
/*
 * Find items
 */
	for (i = known_items; i; i = i->next) {
		if (strncmp(i->singular, s, l) == 0 && i->singular[l] == 0)
			break;
		if (strncmp(i->plural, s, l) == 0 && i->plural[l] == 0)
			break;
	}
	if (i == 0) {
		i = (item_s *)malloc(sizeof(*i));
		i->next = known_items;
		known_items = i;
		strncpy(namebuf, s, l);
		namebuf[l] = 0;
		s = namebuf + l;
		i->man = 0;
		if (amount == 1) {
			i->singular = strdup(namebuf);
			if (strcmp(s-3, "man") == 0) {
				strcpy(s-3, "men");
				i->man = 1;
			} else if (s[-1] == 'f')
				strcpy(s-1, "ves");
			else if (s[-1] != 's')
				strcpy(s, "s");
			i->plural = strdup(namebuf);
		} else {
			i->plural = strdup(namebuf);
			if (strcmp(s-3, "men") == 0) {
				strcpy(s-3, "man");
				i->man = 1;
			} else if (strcmp(s-3, "ves") == 0)
				strcpy(s-3, "f");
			else if (s[-1] == 's')
				s[-1] = 0;
			i->singular = strdup(namebuf);
		}
		if (strcmp(s-7, " leader") == 0 || strcmp(s-8, " leaders") == 0)
			i->man = 1;
	}
/*
 * Advance reading pointer
 */
	*adv += l;
	return i;
}


/**
 ** PARSE_ITEM_LIST
 **	Parse a list of items. Either as possessions, or a shopping list.
 **/
static poss_s *parse_item_list(char *s)
{
poss_s	*item;
int	q;
/*
 * Must start with a quantity
 */
	if (!isdigit(*s))
		return 0;
	q = atoi(s);
	while (isdigit(*s))
		s++;
	while (isspace(*s))
		s++;
/*
 * Might be on the next line
 */
	if (!*s) {
		file_gets(parsed_file);
		s = work;
		while (isspace(*s))
			s++;
	}
/*
 * Amount and type known
 */
	item = (poss_s *)malloc(sizeof(*item));
	memset(item, 0, sizeof(*item));
	item->amount = q;
	item->kind = item_kind(&s, q);
	if (!*s) {
		file_gets(parsed_file);
		s = work;
	}
	while (isspace(*s))
		s++;
/*
 * Might have a price...
 */
	if (strncmp(s, "at $", 4) == 0) {
		s += 4;
		item->price = atoi(s);
		while (isdigit(*s))
			s++;
		if (!*s) {
			file_gets(parsed_file);
			s = work;
		}
		while (isspace(*s))
			s++;
	}
/*
 * List continues?
 */
	if (*s == ',') {
		s++;
		if (!*s) {
			file_gets(parsed_file);
			s = work;
		}
		while (isspace(*s))
			s++;
		item->next = parse_item_list(s);
	}
	return item;
}


/**
 ** PARSE_LOCATIONAL_DATA
 **	Parse a set of lines describing what's there
 **/
static void parse_locational_data(void)
{
char	*s;
/*
 * Locational data or not?
 */
	for (; work[0]; file_gets(parsed_file)) {
		if (!isspace(work[0]))
			break;
		for (s = work; isspace(*s); s++)
			;
		if (strncmp(s, "Wages: $", 8) == 0) {
			current_area->wages = atoi(s+8);
			continue;
		}
		if (strncmp(s, "Taxes: ", 7) == 0) {
			current_area->taxed = atoi(s+7);
			continue;
		}
		if (strncmp(s, "Entertainment available: $", 26) == 0) {
			current_area->entertain = atoi(s+26);
			continue;
		}
		if (strncmp(s, "For sale: ", 10) == 0) {
			free_items(current_area->sold);
			current_area->sold = parse_item_list(s+10);
			continue;
		}
		if (strncmp(s, "Wanted: ", 8) == 0) {
			free_items(current_area->wanted);
			is_item(current_area->wanted = parse_item_list(s+8));
			continue;
		}
		if (strncmp(s, "Resources: ", 11) == 0) {
			free_items(current_area->products);
			is_item(current_area->products = parse_item_list(s+11));
			continue;
		}
	}
}


/**
 ** TYPE_OF_AREA
 **	Figure out the type of the area
 **/
static char *type_of_area(char *e, ttype_s **tt)
{
ttype_s	*t;
char	*s;
/*
 * Find out the word
 */
	while (isspace(*e))
		e++;
	s = e;
	while (isalpha(*s) || isspace(*s))
		s++;
	if (*s)
		*s++ = 0;
	for (t = area_types; t; t = t->next)
		if (strcmp(t->name, e) == 0)
			break;
/*
 * Uh oh! An unknown terrain type!
 */
	if (!t) {
		t = (ttype_s *)malloc(sizeof(*t));
		*t = unknown_area_type;
		t->next = area_types;
		area_types = t;
		t->name = strdup(e);
	}
	*tt = t;
	return s;
}


/**
 ** PARSE_LOCATION
 **	Parse a line describing an area
 **/
static void parse_location(char *l)
{
aname_s	*n;
ttype_s	*t;
char	*s;
char	*e;
int	v, h;
/*
 * Clip area name
 */
	current_area = 0;
	e = 0;
	for (s = l; *s; s++)
		if (*s == '(')
			break;
		else
			if (!isspace(*s))
				e = s;
	if (!e || !*s++)
		return;
	e[1] = 0;
	for (n = area_names; n; n = n->next)
		if (strcmp(n->name, l) == 0)
			break;
/*
 * Uh oh! A new province!
 */
	if (!n) {
		n = (aname_s *)malloc(sizeof(*n));
		n->next = area_names;
		area_names = n;
		n->name = strdup(l);
	}
/*
 * Location is?
 */
	v = atoi(s);
	while (*s)
		if (*s++ == ',')
			break;
	if (!*s)
		return;
	h = atoi(s);
	while (*s)
		if (*s++ == ')')
			break;
	if (*s++ != ',')
		return;
/*
 * Figure out the area type
 */
	s = type_of_area(s, &t);
/*
 * Now, we point to the correct area
 */
	if (v < 1)
		v = 1;
	if (v > MAX_MAP)
		v = MAX_MAP;
	if (h < 1)
		h = 1;
	if (h > MAX_MAP)
		h = MAX_MAP;
	current_area = &lorenai_map[h][v];
	current_area->type = t;
	current_area->name = n;
/*
 * Do we have a complementary information?
 */
	while (isspace(*s))
		s++;
	if (strncmp(s, "population ", 11))
		return;
	s += 11;
	current_area->population = atoi(s);
	while (isdigit(*s))
		s++;
	while (isspace(*s))
		s++;
	current_area->inhabitants = item_kind(&s, current_area->population);
	current_area->inhabitants->man = 1;
	if ((s = strchr(s, '$')) != 0)
		current_area->income = atoi(s+1);
}


/**
 ** REMOVABLE_STRUCTURE
 **	Structure is no longer associated with *that* building
 **/
static void movable_structure(build_s *b)
{
area_s	*a;
build_s	*p, *n;
/*
 * Protect against unsafe handler
 */
	a = b->area;
/*
 * Unlink from linked list
 */
	if ((n = a->structures) == b)
		a->structures = b->next;
	else
		for (p = 0; n; n = n->next)
			if (n == b) {
				p->next = b->next;
				break;
			} else
				p = n;
	b->area = 0;
	b->next = 0;
}


/**
 ** FREE_LOCATION_DETAILS
 **	New info about a location is coming in
 **/
static void free_location_details(void)
{
	free_items(current_area->sold);
	free_items(current_area->wanted);
	free_items(current_area->products);
	current_area->sold = 0;
	current_area->wanted = 0;
	current_area->products = 0;
}


/**
 ** STRUCTURE_IN_AREA
 **	A structure was found in that area
 **/
static int structure_in_area(void)
{
build_s	*b;
area_s	*a;
char	*s, *e;
int	i;
int	l;
/*
 * Verification again
 */
	if (work[0] != '+')
		return 0;
	for (s = work+1 ; isspace(*s); s++)
		;
	if ((e = strrchr(s, '[')) == 0)
		return 0;
/*
 * Code found
 */
	i = atoi(e+1);
/*
 * Any structure with that number?
 */
	for (b = all_buildings; b; b = b->list)
		if (b->number == i)
			break;
	if (!b) {
		b = (build_s *)malloc(sizeof(*b));
		memset(b, 0, sizeof(*b));
		b->list = all_buildings;
		b->number = i;
	}
/*
 * Was that structure spotted somewhere else?
 */
	if ((a = b->area) != 0 && a != current_area) {
		movable_structure(b);
		b->area = 0;
		a = 0;
	}
	if (a == 0) {
		b->next = current_area->structures;
		current_area->structures = b;
		b->area = current_area;
	}
/*
 * Any name?
 */
	*e++ = 0;
	for (l = strlen(s) - 1; l >= 0; l--)
		if (isspace(s[l]))
			s[l] = 0;
		else
			break;
	if (b->name && strcmp(b->name, s) != 0) {
		free(b->name);
		b->name = 0;
	}
	if (!b->name)
		b->name = strdup(s);
/*
 * The type!
 */
	s = strchr(e, ',');
	(void)type_of_area(s+1, &b->type);
	return i;
}


/**
 ** FOUND_REPORT_LOCATION
 **	We're at a line looking like "Name (xx,xx),...", must be a city
 **/
static void found_report_location(void)
{
area_s	*area;
char	*s;
int	i;
/*
 * We're beginning with a location
 */
	parse_location(work);
	file_gets(parsed_file);
	if (work[0] != '-')
		return;
	free_location_details();
/*
 * Wages, Taxes, And so on...
 */
	file_gets(parsed_file);
	parse_locational_data();
	if (!current_area)
		return;
/*
 * We checked this area at that date
 */
	current_area->month_checked   = month;
	current_area->year_checked    = year;
	current_area->faction_checked = my_faction_number;
/*
 * Search for exits
 */
	file_gets(parsed_file);
	if (strncmp(work, "Exits:", 6))
		return;
	for (file_gets(parsed_file); work[0]; file_gets(parsed_file))
		if ((s = strchr(work, ':')) != 0) {
			s++;
			while (isspace(*s))
				s++;
			if (is_location_candidate(s)) {
				area = current_area;
				parse_location(s);
/*
 * Who last reported that unknown hex name and type?
 */
				if (!current_area->population) {
					current_area->month_checked   = month;
					current_area->year_checked    = year;
					current_area->faction_checked = my_faction_number;
				}
				current_area = area;
			}
		}
/*
 * Found an empty line... We must have the local units now...
 */
	file_gets(parsed_file);
	no_units_in_area();
	while (work[0]) {
		unit_present_in_area(0);
		file_gets(parsed_file);
	}
/*
 * The list of units is done. Do we have structures inside?
 */
	file_gets(parsed_file);
	while (current_area->structures)
		movable_structure(current_area->structures);
	while (work[0] == '+') {
		i = structure_in_area();
		for (file_gets(parsed_file); work[0]; file_gets(parsed_file))
			unit_present_in_area(i);
		file_gets(parsed_file);
	}
}


/**
 ** READ_REPORT
 **	We're positioned at the exact beginning of a report
 **/
static void read_report(void)
{
char	*s;
long	report_index;
/*
 * Report starts with: Faction (number)
 */
	report_index = ftell(parsed_file);
	file_gets(parsed_file);
	if ((s = strrchr(work, '(')) == 0)
		return;
	my_faction_number = atoi(s+1);
	*s-- = 0;
	while (isspace(*s))
		*s-- = 0;
	if (my_faction_name)
		free(my_faction_name);
	my_faction_name = strdup(work);
/*
 * 2nd line of report is Month, Year
 */
	file_gets(parsed_file);
	if (strncmp(work, "Month ", 6))
		return;
	month = atoi(work+6);
	for (s = work + 6; *s; s++)
		if (*s == 'Y')
			break;
	if (strncmp(s, "Year ", 5))
		return;
	year = atoi(s + 5);
/*
 * Now all our units are unpresent...
 */
/*
 * Now searching in the report for locations
 */
	file_gets(parsed_file);
	while (1) {
		if (strncmp(work, "From ", 5) == 0)
			break;
		if (strncmp(work, "Lorenai Report For", 18) == 0)
			read_report();
		if (!is_location_candidate(work)) {
			if (!file_gets(parsed_file))
				break;
			continue;
		}
		found_report_location();
	}
}


/**
 ** EXTRACT_CHECK_INFO
 **	Figure out who and when was that hex checked
 **/
static void extract_check_info(area_s *area, char *s)
{
/*
 * Month of check
 */
	area->month_checked = atoi(s);
	while (*s)
		if (*s++ == '/')
			break;
	if (!*s)
		return;
/*
 * Year of check
 */
	area->year_checked = atoi(s);
	while (*s)
		if (*s++ == ':')
			break;
	if (!*s)
		return;
/*
 * Faction that reported on that hex
 */
	area->faction_checked = atoi(s);
}


/**
 ** READ_CACHE_ENTRY
 **	Read the cache entry from the file
 **/
static void read_cache_entry(void)
{
	while (1) {
/*
 * Skip to next separator
 */
		while (work[0] != '-' && work[1] != '-')
			if (!file_gets(parsed_file))
				return;
		if (!file_gets(parsed_file))
			return;
		if (is_location_candidate(work)) {
			parse_location(work);
			file_gets(parsed_file);
			if (current_area && work[0] == '(') {
				extract_check_info(current_area, work+1);
				file_gets(parsed_file);
			}
			if (current_area && work[0] != '-') {
				free_location_details();
				parse_locational_data();
				while (current_area->structures)
					movable_structure(current_area->structures);
				while (work[0] == '+') {
					(void)structure_in_area();
					file_gets(parsed_file);
				}
			}
		}
	}
}


/**
 ** READ_MAP
 **	Read a map file (from the Guild)
 **/
void read_map(char *filename)
{
/*
 * Various checks
 */
	if (!filename)
		return;
	parsed_file = fopen(filename, "r");
	if (!parsed_file) {
		perror(filename);
		return;
	}
/*
 * Now start readin' the reports
 */
	work[0] = 0;
	read_cache_entry();
	fclose(parsed_file);
}


/**
 ** READ_BOX
 **	Read the mailbox containing the various turns
 **/
void read_box(char *filename)
{
/*
 * Various checks
 */
	if (!filename)
		return;
	parsed_file = fopen(filename, "r");
	if (!parsed_file) {
		perror(filename);
		return;
	}
/*
 * Now start readin' the reports
 */
	while (file_gets(parsed_file))
		if (strncmp(work, "Lorenai Report For", 18) == 0)
			read_report();
	fclose(parsed_file);
}
