#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <atari.h>
#include <conio.h>
#include <ctype.h>
#include <dirent.h>
#include <peekpoke.h>

#include "../src/config.h"
#include "../src/timers.h"
#include "os2ram.h"

#ifndef VERSION
#define VERSION "?????"
#endif

conf_t defaults = {
	"N:TCP://irc.libera.chat:6667",
	"FNChatTest",
	"FujiNetChat User",
	{ 0xc2, 0xce },
	{ "#fnchat", { "##atari" }, { "#atari8" }, { 0 }, { 0 } },
	{ 0 }, /* extra_channels */
	3, /*       alert_type, default = both */
	0, /*        show_ping, default = N */
	1, /*        hide_motd, default = Y */
	1, /*      atract_away, default = Y */
	0, /* disable_keyclick, default = N */
};

char *servers[] = {
	"irc.libera.chat",
	"irc.undernet.org",
	"irc.efnet.org",
	"irc.newnet.net",
	"irc.oftc.net",
	0
};

char have_dos = 0;
char filename[101] = "D:FNCHAT.CFG";
char buf[128];
char numbuf[4];
char server[101];
char port[6];
char use_xl_ram = 1;

unsigned int *bonus_addrs = (unsigned int *)0xd4; /* aka FR0 */

void setup_timers_and_exit(void) {
	timers.hz = (GTIA_READ.pal & 0x0e) ? 60 : 50;
	timers.one_tenth_sec = timers.hz / 10;
	timers.one_sec = timers.hz * 60;
	timers.net_ind_time = (timers.hz / 10) * 37;

	OS.color2 = config.colors[0]; /* text BG, user-selected */
	OS.color1 = config.colors[1];
	OS.color0 = 0x06; /* grey for inactive */
	OS.color3 = 0x46; /* red for highlight */
	OS.noclik = config.disable_keyclick;

	exit(0);
}

char lcgetc(void) {
	char c;

	c = cgetc();
	if(c == 0x03) setup_timers_and_exit();
	return c;
}

char *lgets(char *buf) {
	while(OS.ch == 0xff)
		/* NOP */;

	if(OS.ch == 0x92)
		lcgetc();

	return gets(buf);
}

void detect_dos(void) {
	char i, j, h, d;

	h = d = 0;
	for(i = 0; i < 11; i++) {
		j = OS.hatabs[i].id;
		if(j == 'D')
			d = 1;
		if(j == 'H')
			h = 1;
	}

	if(h && !d) filename[0] = 'H';
	if(h || d) have_dos = 1;
}

void print(const char *text) {
	fputs(text, stdout);
}

void prompt(const char *text, char *p, char limit) {
	print(text);
	print(" [");
	print(p);
	print("]? ");
	lgets(buf);
	if(buf[0]) strncpy(p, buf, limit);
}

char yn(const char *text, char dflt) {
	char c, bad;

	print(text);

	if(dflt)
		print(" [Y/n]? ");
	else
		print(" [y/N]? ");

	do {
		bad = 1;
		c = tolower(lcgetc());
		if(c == '\n')
			c = dflt ? 'y' : 'n';
		if(c == 'y' || c == 'n')
			bad = 0;
	} while(bad);

	putchar(c);
	putchar('\n');

	return (c == 'y');
}

void prompt_config_file(void) {
	OS.shflok = 0x40;
	prompt("Config File", filename, 100);
	OS.shflok = 0x00;

	if(filename[1] != ':' && filename[2] != ':') {
		memmove(filename + 2, filename, 98);
		filename[0] = 'D';
		filename[1] = ':';
	}

	if(!strchr(filename, '.'))
		strcat(filename, ".CFG");
}

void prompt_color(const char *text, char which) {
	itoa(config.colors[which], numbuf, 10);
	print(text);
	prompt(" color", numbuf, 3);
	config.colors[which] = atoi(numbuf);
}

void prompt_colors(void) {
	char bad;

	do {
		bad = 0;
		prompt_color("Text BG", 0);
		prompt_color("Text FG", 1);
		if((config.colors[0] & 0x0e) == (config.colors[1] & 0x0e)) {
			print("!! Unreadable, try again.\n");
			bad = 1;
		}
	} while(bad);

	OS.color2 = config.colors[0];
	OS.color1 = config.colors[1];
}

void prompt_alert_type(void) {
	char c, d, bad;

	print("  0:None, 1:Beep, 2:Flash, 3:Both\n");
	d = config.alert_type + '0';
	do {
		bad = 0;

		print("Alert type [");
		putchar(d);
		print("]? ");

		c = lcgetc();
		if(c == '\n') c = d;
		putchar(c);

		putchar('\n');
		if(c < '0' || c > '3') {
			bad = 1;
			print("!! Range is 0-3, try again.\n");
		}
	} while(bad);

	config.alert_type = c - '0';
}

void no_dos(void) {
	print("No DOS booted (using $0700 area for\nextra scrollback)\n");
	bonus_addrs[0] = 0x0700;
	bonus_addrs[1] = 0x0ae8;
	bonus_addrs[2] = 0x1000; /* start on a 4K boundary */
	bonus_addrs[3] = 0x13e8;
	bonus_addrs[4] = 0x17d0;
	bonus_addrs[5] = 0x1bb8;
	bonus_addrs[6] = 0;      /* screen #7 doesn't have this */
}

char want_overwrite() {
	int fh;

	if((fh = open(filename, O_RDONLY)) < 0)
		return 1;

	close(fh);

	return yn("File exists, overwrite", 0);
}

void save(void) {
	int fh, bad;

	if(!have_dos) {
		no_dos();
		return;
	}

	do {
		bad = 0;
		prompt_config_file();

		if(!want_overwrite()) {
			return;
		}

		if((fh = open(filename, O_WRONLY | O_CREAT)) < 0) {
			print("!! I/O error (open)\n");
			bad = 1;
		} else if((write(fh, &config, sizeof(conf_t))) != sizeof(conf_t)) {
			print("!! I/O error (write)\n");
			bad = 1;
		}

		if(fh >= 0) close(fh);

		if(bad) {
			if(!yn("Retry", 1)) break;
		} else {
			print("Saved config ");
			print(filename);
			putchar('\n');
		}
	} while(bad);
}

char load(char prompt) {
	int fh, loaded;

	if(!have_dos) {
		no_dos();
		return 0;
	}

	if(prompt) prompt_config_file();

	print("Loading config ");
	print(filename);
	putchar('\n');

	loaded = 1;
	if((fh = open(filename, O_RDONLY)) < 0) {
		print("Not found\n");
		loaded = 0;
	} else if((read(fh, &config, sizeof(conf_t))) != sizeof(conf_t)) {
		print("Invalid\n");
		loaded = 0;
	}

	if(fh >= 0) close(fh);

	return loaded;
}

void dir(char drive) {
	DIR *d;
	struct dirent *e;
	static char spec[10] = "D1:*.CFG";
	char found = 0;

	if(drive == 'h') {
		spec[0] = 'H';
		spec[1] = '1';
	} else {
		spec[0] = 'D';
		spec[1] = drive + '0';
	}
	// print(spec); putchar('\n');

	d = opendir(spec);
	if(!d) return; /* TODO: err msg */

	while( (e = readdir(d)) ) {
		found++;
		print(e->d_name);
		/*
		if(OS.colcrs < 20)
			OS.colcrs = 20;
		else
			*/
		putchar(' ');
	}

	if(found)
		putchar('\n');
	else
		print("No *.CFG files found\n");

	closedir(d);
}

void make_url(void) {
	config.url[8] = '\0';
	strcat(config.url, server);
	strcat(config.url, ":");
	strcat(config.url, port);
}

void parse_url(void) {
	char *p, *q;
	q = server;
	p = config.url + 8; /* skip N:TCP:// */
	while(*p) {
		if(*p == ':') break;
		*q++ = *p++;
	}

	*q = 0;
	p++;
	q = port;
	strcpy(port, p);
}

char prompt_main(void) {
	char bad, c;

	do {
		bad = 0;
		putchar('\n');
		if(have_dos)
			print("[L]oad [S]ave [1-9H]Directory\n");
		print("[E]dit [C]onnect [C]? ");

		c = tolower(lcgetc());
		if(c == 0x14) {
			strcpy(config.nick, "Urch800XL");
			config.channels[1][0] = config.channels[2][0] = 0;
			c = 'c';
		} else if(c == 0x15) {
			/* super-secret Urchlay mode... */
			strcpy(config.nick, "Urch800XL");
			c = 'c';
		}
		if(c == '\n') c = 'c';
		putchar(c);
		putchar('\n');

		if(c >= '1' && c <= '9') {
			dir(c - '0');
			bad = 1;
		} else switch(c) {
			case 'c':
				return 1;
			case 'l':
				if(have_dos) load(1);
				bad = 1;
				break;
			case 's':
				if(have_dos) save();
				bad = 1;
				break;
			case 'e':
				return 0;
			case 'h':
				dir(c);
				bad = 1;
				break;
			default:
				bad = 1;
				break;
		}
	} while(bad);
	return 0;
}

void keyclick(void) {
	OS.ch = 0;
	lcgetc();
}

void list_chans(void) {
	char i, *p;

	for(i = 0; i < 5; i++) {
		p = config.channels[i];
		print("  Screen #");
		putchar(i + '3');
		print(": ");
		print(*p ? p : "(none)");
		putchar('\n');
	}
}

void clear_chans(void) {
	char i;

	for(i = 0; i < 5; i++)
		config.channels[i][0] = 0;
}

void prompt_channels(void) {
	char c, d, ok;

	print("\nAutojoin channels in screens 3-7.\n\n");

	list_chans();
	putchar('\n');

	while(1) {
		print("3-7=change C=clear L=list Return=done\n");
		do {
			ok = 1;
			c = lcgetc();
			if(c == '\n')
				return;
			if(c == 'l')
				list_chans();
			if(c == 'c') {
				clear_chans();
				print("All channels cleared\n");
			}
			if(c < '3' || c > '7')
				ok = 0;
		} while(!ok);

		d = c;
		c -= '3';

		print("Enter channel or press Esc to clear:\n");

		while(OS.ch == KEY_NONE)
			/* NOP */;

		if(OS.ch == KEY_ESC) {
			config.channels[c][0] = 0;
			print("Channel ");
			putchar(d);
			print(" cleared.\n");
			keyclick();
		} else if(OS.ch == KEY_RETURN) {
			print("Channel ");
			putchar(d);
			print(" unchanged.\n");
			keyclick();
		} else {
			lgets(buf);
			if(*buf)
				strncpy(config.channels[c], buf, 25);
		}
	}
}

void prompt_extra_channels(void) {
	char r, done;

	print("\nAutojoin channels in screen 1.\n\n");
	print("Enter up to 3 lines of channel names.\n");
	print("Use normal Atari editing keys.\n");

	do {
		print("Channels: \n\n\n\n\x1c\x1c\x1c");
		r = OS.rowcrs;
		print(config.extra_channels);
		OS.rowcrs = r;
		OS.colcrs = 0;
		putchar(CH_CURS_RIGHT);
		putchar(CH_CURS_LEFT);

		lgets(buf);
		strncpy(config.extra_channels, buf, 128);

		done = yn("OK", 1);
	} while(!done);
}

void prompt_server_list(void) {
	char **p = servers;
	int c, count = '1';

	do {
		putchar(count | 0x80);
		putchar(':');
		print(*p);
		putchar('\n');
		count++;
		p++;
	} while(*p);

	print("Server [1]? ");
	do {
		c = lcgetc();
		if(c == '\n') c = '1';
	} while(!(c > '0' && c < count));
	c -= '1';
	strncpy(server, servers[c], 100);
	print(server);
	putchar('\n');
}

void prompt_server() {
	print("** Press Escape for server list.\n");
	print("Server [");
	print(server);
	print("]? ");
	while(OS.ch == KEY_NONE)
		/* NOP */;
	if(OS.ch == KEY_ESC) {
		putchar('\n');
		keyclick();
		prompt_server_list();
	} else {
		lgets(buf);
		if(*buf) strncpy(server, buf, 100);
	}
}

void detect_xl(void) {
	if(!is_xl()) {
		print("400/800 detected, no extra RAM\n");
		use_xl_ram = 0;
		return;
	}

	use_xl_ram = yn("XL/XE detected, use extra RAM", use_xl_ram);
	if(!use_xl_ram) return;

	os_to_ram();
}


void set_default_config(void) {
	memcpy(&config, &defaults, sizeof(conf_t));
}

void main(void) {
	char bad;

	/* disable Break key. don't have to do it again in the client! */
	POKE(0x10, PEEK(0x10) & 0x7f);
	POKE(0xd20e, PEEK(0x10));

	/* loading1.xex left the original DL address at $fe */
	if(PEEKW(0xfe)) POKEW(0x0230, PEEKW(0xfe));

	/* clear the bonus screen addresses in case something left junk there */
	memset(bonus_addrs, 0, 14);

	set_default_config();

	OS.color2 = config.colors[0];
	OS.color1 = config.colors[1];

	cursor(1);
	print("\x7d" "FujiNetChat Config\n");
	print("Version " VERSION "\n\n");

	detect_dos();
	// detect_xl(); // not yet

	if(!load(0))
		print("Using built-in default config.\n");

	while(1) {
		if(prompt_main())
			break;

		print("\nAt any time, press Ctrl-C to skip the\n");
		print("remaining questions.\n\n");

		if(memcmp(&defaults, &config, sizeof(defaults)) != 0)
			if(yn("Reset to defaults", 0))
				set_default_config();

		parse_url();

		prompt("Nick", config.nick, 25);

		prompt_server();
		make_url();

		do {
			bad = 0;
			prompt("Port  ", port, 5);
			if(!isdigit(port[0])) {
				print("!! Not a number, try again.\n");
				bad = 1;
			}
		} while(bad);

		prompt("Name  ", config.real_name, 25);
		prompt_colors();
		prompt_alert_type();
		// config.show_ping = yn("Show PING/PONG", config.show_ping);
		config.atract_away = yn("Set AWAY on ATRACT", config.atract_away);
		config.hide_motd = yn("Hide MOTD", config.hide_motd);
		config.disable_keyclick = yn("Disable keyclick", config.disable_keyclick);
		prompt_channels();
		prompt_extra_channels();

		print("\nURL: ");
		print(config.url);
		putchar('\n');
	}

	/* GRR! When the client loads, somehow, wherever the cursor is according
	   to the E: device, lgets a 0 byte written to its screen address.
	   Clearing the screen here band-aids it: a byte of the client's screen
	   memory is getting zeroed out, but it's a byte that was already zero... */
	print("\x7d");

	setup_timers_and_exit();
}
