/* TODO HYPE: status on ti's screen! */

/*  TilEm, TI-Linux Emulator
 *  Copyright (C) 2001 Solignac Julien <x1cygnus@xcalc.org>
 *  Portions copyright (C) 2004 Benjamin Moody <benjamin@ecg.mit.edu>
 *
 *  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "../../../../config.h"

#include<string.h>
#include<stdlib.h>
#include<stdio.h>
#include<glib.h>

	/* >>> ExtLink */
#ifdef extlink
#include<ticables.h>
#endif
	/* ExtLink <<< */


/* >>> dependencies */
#include "../Z80.h"
/* dependencies <<< */

#include "dbus.h"

#include "link.h"
/* >>> Imported */
#include "inter.h"
#include "../../io.h"
#include "../../gui/files.h"
extern struct hardware *hw;
/* Imported <<< */

/* >>> Globals */
byte pclnk;
byte dbusstat=0x80;
byte dbusread; /* Data the calculator has received */
byte dbuswrite; /* Data received from the calculator */
/* Globals <<< */

/* >>> Private */
#define maxtime 0x00FF

/* d_puts for high-level protocol debugging,
   d_printf for low-level send and receive */

#define d_puts(tex) /*puts(tex)*/

#define d_printf(fmt,n) /*printf(fmt,n)*/


#define tok_varh	0x06
#define tok_wait	0x09
#define tok_data	0x15
#define tok_refused	0x36
#define tok_ok		0x56
#define tok_chk		0x5A
#define tok_probe	0x68
#define tok_cont	0x78
#define tok_eot		0x92
#define tok_reqsend	0xC9


#define cFlag_wData	1 << 0
#define cFlag_wCont	1 << 1
#define cFlag_wOk	1 << 2
#define cFlag_eProbe	1 << 3
#define cFlag_eVarh	1 << 4
#define cFlag_eEot	1 << 5
#define cFlag_wWait	1 << 6


#define sFlag_di	1 << 12

typedef char (*afunc)(void);

struct responses {
	byte id;
	afunc action;
};

static char p_ok(void);
static char p_wait(void);
static char p_varh(void);
static char p_data(void);
static char p_probe(void);
/* Private <<< */


/* >>> Resources */
static struct responses tokens[] = {
	{tok_data, p_data},
	{tok_cont, NULL},
	{tok_ok, p_ok},
	{tok_probe, p_probe},
	{tok_varh, p_varh},
	{tok_eot, p_probe},
	{tok_wait, p_wait}
};
#define num_tokens sizeof(tokens) / sizeof(struct responses)

static struct linfo {
	byte **list;
	byte *flow, *flowed;
	word flags;
	word queued, recvd, max, timeout;
	word cooldown;
	byte backup_mode;
} ik;
/* Resources <<< */


/* >>> ExtLink */
#ifdef extlink
static CableHandle* cable_handle = NULL;
static byte lc_avail = 0;
CableOptions elinkp = { CABLE_TIE, PORT_1, DFLT_TIMEOUT, DFLT_DELAY };
byte elink = 0;
#endif
/* ExtLink >>> */


/* >>> ExtLink */
#ifdef extlink
static void print_lc_error(int errnum)
{
	char *msg;

	ticables_error_get(errnum, &msg);
	fprintf(stderr, "Link cable error %i: %s\n", errnum, msg);
}


void ti68k_open_linkport()
{
	int err;

	if (!elink) return;

	/* set cable */
	cable_handle = ticables_handle_new(elinkp.model, elinkp.port);
	if (cable_handle == NULL) {
		elink = 0;
		fprintf(stderr, "Can't set cable");
		return;
	}

	ticables_options_set_timeout(cable_handle, elinkp.timeout);
	ticables_options_set_delay(cable_handle, elinkp.delay);

	/* and open */
	err = ticables_cable_open(cable_handle);
	if (err) {
		print_lc_error(err);
		elink = 0;
	}
}


void ti68k_close_linkport()
{
	int err;

	if (!elink) return;

	/* close cable */
	err = ticables_cable_close(cable_handle);
	if (err)
		print_lc_error(err);

	/* and delete handle */
	ticables_handle_del(cable_handle);

	cable_handle = NULL;
}
#endif
/* ExtLink >>> */


/* >>> static */
	/* >>> low level */
static char sendbyte(byte line, byte tosend)
{
	extern byte pclnk;

	static byte status, count;

	if ((count == 0x08) || (line == 0xFF)) {
		count = status = 0;
		pclnk = 0x03;
		return(0);
	}

	if (!(dbusstat & DBUS_DISABLED)) {
		count = status = 0;
		if (dbusstat & DBUS_READY) {
			d_printf("[Snd] 0x%02X\n", tosend);
			dbusread = tosend;
			dbusstat &= ~DBUS_READY;
			dbusstat |= DBUS_DATA;
			pclnk = 1; /* get calculator's attention */
			return 1;
		}
		else if (dbusstat & DBUS_DATA) {
			dbusstat |= DBUS_OVERFLOW;
			return 0;
		}
		else {
			/* collision, eek! */
			printf("** dbus collision! stat=%02X**\n",dbusstat);
			dbusstat |= DBUS_ERROR;
			return 0;
		}
	}


	switch (status) {
		case 0:
			if ((line != 0x03) && (line)) {
				pclnk = 0x03;
				break;
			}
			pclnk = 1 + ((~tosend >> count) & 0x01);
			status++;
			break;

		case 1:
			if (!line) {
				pclnk = 0x03;
				status++;
			}
			break;

		case 2:
			if (line == 0x03) {
				count++;
				status = 0;
			}
	}

	if (count == 0x08)
		d_printf("[Snd] 0x%02X\n", tosend);
#ifdef extlink
	if (count & 0x08)
		lc_avail = 0;
#endif
	return ((count & 0x08) ? 1 : 0);
}


static int recbyte(byte line)
{
	extern byte pclnk;

	static byte status, rec, count, ls;

	if ((count == 0x08) || (line == 0xFF)) {
		count = status = rec = 0;
		return(-1);
	}

	if (!(dbusstat & DBUS_DISABLED)) {
		if (dbusstat & DBUS_WRITE) {
			dbusstat = DBUS_READY;
			return dbuswrite;
		}
	}

	switch (status) {
		case 0:
			if (line == 0x03)
				break;
			rec = rec | ((line & 0x01) << count);
			pclnk = ls = (~line) & 0x03;
			status++;
			break;

		case 1:
			if (line != ls)
				break;
			pclnk = 0x03;
			status++;
			break;

		case 2:
			if (line != 0x03)
				break;
			status = 0;
			count++;
	}

	return ((count & 0x08) ? rec : -1);
}
	/* low level <<< */


static void mk_token(byte id)
{
	if (ik.queued > 96) {
		fprintf(stderr, "link overflow, Terminating\n");
		exit(-1);
	}

	*(ik.flow + ik.queued++) = hw->magic;/* | 0x80; */
	*(ik.flow + ik.queued++) = id;
	*(ik.flow + ik.queued++) = 0x00;
	*(ik.flow + ik.queued++) = 0x00;

	ik.timeout = maxtime;
}


static word c_len(byte *flow)
{
	return(*flow + (*(flow + 1) << 8));
}


static word c_chk(byte *flow, word len)
{
	word chk;

	chk = *(flow + len) + (*(flow + len + 1) << 8);
	while (len) {
		len--;
		chk -= *(flow + len);
	}

	return(chk);
}


static char p_wait(void)
{
	word hsize;
	d_puts("		[Wait");

	hsize = 2 + *(ik.flow + ik.max) + (*(ik.flow + ik.max + 1) << 8) + 2;
	memmove(ik.flow + 6, ik.flow + ik.max, hsize);

	mk_token(tok_ok);

	ik.max = 66;
	ik.queued += 2 + hsize;
	*(ik.flow + 4) = hw->magic;
	*(ik.flow + 4 + 1) = tok_data;

	ik.flags = cFlag_wOk;

	d_puts("		Wait]");
	return(1);
}


static char p_ok(void)
{
	extern byte pclnk;

	int i;

	d_puts("		[Ok");

	if (!(ik.list) || (ik.flags & cFlag_wWait))
		return(0);

	i = 0;
	while (*(ik.list + i + 1))
		i++;

	d_puts("			Intern");
	ik.flags = cFlag_wOk;
	if (!(*ik.list)) {
	d_puts("			Finish");
		free(ik.list);
		ik.list = NULL;
		mk_token(tok_eot);
		ik.flags |= cFlag_eVarh | cFlag_eProbe;
		ik.backup_mode = 0;
		return(1);
	}
	d_puts("			Prepare");

	if (ik.max == 66) {
		/* pclnk = 0; */
		ik.cooldown = 300;
	}

	ik.flags |= cFlag_wWait;

	free(ik.flow);
	ik.flow = *(ik.list + i);
	*(ik.list + i) = 0;

	ik.queued = 4 + *(ik.flow + 2) + (*(ik.flow + 3) << 8) + 2;

	ik.max = ik.queued;
	*(ik.flow) = hw->model == '7' ? 0x07 : hw->magic & 0x0F;

	if (ik.backup_mode) {
		ik.flags = cFlag_wOk;
		*(ik.flow + 1) = tok_data;
	}
	else {
		if (((hw->model == '2' || hw->model == '6')
		     && (*(ik.flow + 6) == 0x0f))
		    || ((hw->model == '5') && (*(ik.flow + 6) == 0x1d))
		    || (*(ik.flow + 6) == 0x13)) {
			
			ik.backup_mode = 1;
		}

		if (hw->model == '2' || hw->model == '5') {
			*(ik.flow + 1) = tok_varh;
		}
		else {
			*(ik.flow + 1) = tok_reqsend;
		}
	}
	ik.timeout = maxtime;

	return(1);
}


static char p_probe(void)
{
	d_puts("		[Probe");
	if ((ik.recvd) || (ik.queued)) {
		printf("(slow diplomatics) ");
		resetlink();
	} else
		mk_token(tok_ok);

	d_puts("		Probe]");
	return(1);
}


static char p_data(void)
{
	/* extern Z80_Regs R; */

	word hsize;

	d_puts("		[Data");
	hsize = c_len(ik.flowed + 2);
	ik.recvd -= hsize + 2;

	if (ik.recvd)
		return(resetlink());
	ik.flowed += 4;

	if (c_chk(ik.flowed, hsize)) {
		puts("Invalid chksum!");
		mk_token(tok_chk);
		ik.flags = cFlag_wData;
		return(1);
	}

	create_savedlg(ik.flowed, ik.flow + ik.max);
	resetkeyb();
	ik.flags = cFlag_eEot | cFlag_eVarh;
	mk_token(tok_ok);
	/* R.IFF1 = R.IFF2 = 1; */

	d_puts("		Data]");
	return(1);
}


static char p_varh(void)
{
	word hsize, vsize;

	d_puts("		[Varh");

	vsize = c_len(ik.flowed + 2);
	ik.recvd -= vsize + 2;

	if (ik.recvd)
		return(resetlink());
	ik.flowed += 4;

	if (c_chk(ik.flowed, vsize)) {
		puts("Invalid chksum!");
		mk_token(tok_chk);
		ik.flags = cFlag_eVarh;
		return(1);
	}

/*	if ((*(ik.flowed + 2) != 0x05) && (*(ik.flowed + 2) != 0x06)) {
		printf("Variable type not implemented: %02X\n", *(ik.flowed + 2));
		mk_token(tok_refused);
		return(1);
	}*/

#define varh_cp 0x0D + 2
	if (vsize > varh_cp - 2) {
		fprintf(stderr, "unexpected: varh size > 0x0D, aborting\n");
		return(resetlink());
	}

	hsize = c_len(ik.flowed);

	/* | <ok> <data> | data | chk | varh | */
	if (hsize + 10 + varh_cp > ik.max) {
		ik.flow = realloc(ik.flow, hsize + 10 + varh_cp);
		ik.max = hsize + 10;
	} else
		ik.max -= varh_cp;

	/* | type | file name | version for 83+ | */
	memcpy(ik.flow + ik.max, ik.flowed - 2, varh_cp);
	ik.flags = cFlag_wOk | cFlag_wData | sFlag_di;
	mk_token(tok_ok);
	mk_token(tok_wait);

	d_puts("		Varh]");
	return(1);
}


static afunc itokyo(byte token)
{
	byte i;

	d_puts("	[iTokyo");
	i = 0;
	while ((token != tokens[i].id) && (i < num_tokens))
		i++;

	if ((i == num_tokens) || (!(ik.flags & (1 << i)))) {
		printf("(unlawful actions) ");
		return(resetlink);
	}

	d_puts("	iTokyo]");
	ik.flags ^= 1 << i;
	return(tokens[i].action);
}


static void tokenize(void)
{
	afunc retval;

	d_puts("[Tokenize");

	ik.flowed = ik.flow;
	while (ik.recvd >= 4) {
		if ((*ik.flowed ^ hw->magic) & 0x0F) {
			printf("(unlawful hardware) ");
			resetlink();
			return;
		}

		ik.recvd -= 4;
		retval = itokyo(*(ik.flowed + 1));
		if ((retval != NULL) && (retval()))
			return;

		ik.flowed += 4;
	}

	if (ik.recvd) {
		printf("(silence is virtue) ");
		resetlink();
	}

	d_puts("Tokenize]");
}
/* Static <<< */


/* >>> Exported */
char resetlink(void)
{
	d_puts("**[Link Reset]**");
	printf("\n");

#ifdef extlink
	ti68k_close_linkport();
	ti68k_open_linkport();
#endif

	if (ik.list) {
		unravel(ik.list);
		ik.list = NULL;
		ik.flow = NULL;
	}

	ik.flow = realloc(ik.flow, 100);
	ik.flowed = ik.flow;

	ik.max = 100;
	ik.queued = ik.recvd = 0;
	ik.timeout = 0;

	sendbyte(0xFF, 0);
	recbyte(0xFF);

	ik.flags = cFlag_eProbe | cFlag_eVarh;
	ik.backup_mode = 0;
	return(1);
}


void activate_send(byte **list)
{
	if (ik.list) {
		fprintf(stderr, "(Previous link attempt aborted)\n");
		/* return; */
	}

	resetlink();
	ik.flags = cFlag_wOk;
	ik.list = list;

	if (hw->model != '3' && hw->model != '2')
		mk_token(tok_probe);
	else {
		mk_token(tok_ok);
		ik.timeout = 0;
		ik.queued = 0;
		ik.recvd = 4;
	}
}


void handlelink(byte line)
{
	extern Z80_Regs R;

	short i;
#ifdef extlink
	int lc_err, lc_sts;
	static byte lc_byte;
#endif

#ifdef extlink
	if (elink) {

		if (!lc_avail) {
			if ((lc_err = ticables_cable_check(cable_handle,
							   &lc_sts))) {
				print_lc_error(lc_err);
				resetlink();
				return;
			}
			else if (lc_sts & STATUS_RX) {
				if ((lc_err = ticables_cable_get(cable_handle,
								 &lc_byte))) {
					print_lc_error(lc_err);
					resetlink();
					return;
				}
				d_printf("elinkget %02X\n",lc_byte);

				lc_avail = 1;
				ik.timeout = maxtime;
			}
		}

		if (lc_avail) {
			if (ik.timeout) ik.timeout--;

			if (ik.timeout) {
				if (sendbyte(line, lc_byte)) {
					lc_avail = 0;
					/* ik.timeout = 0; */
				}
			}
			else {
				lc_avail = 0;
				fprintf(stdout, "(letterbox stuck) ");
				resetlink();
			}
		}
		else if (line != 3 || (dbusstat & DBUS_WRITE) || ik.timeout) {
			if (!ik.timeout) {
				ik.timeout = maxtime;
			}
			else
				ik.timeout--;
			
			if (ik.timeout) {
				i = recbyte(line);
				if (i != -1) {
					d_printf("elinkput %02X\n",i);

					if ((lc_err = ticables_cable_put(cable_handle, i))) {
						print_lc_error(lc_err);
						resetlink();
						return;
					}
					ik.timeout = 0;
				}
			}
			else {
				fprintf(stdout, "(postman late) ");
				resetlink();
			}
		}

		return;
	}
#endif

	if (ik.cooldown) {
		ik.cooldown--;
		return;
	}

/*	if (ik.flags & sFlag_di)
		R.IFF1 = R.IFF2 = 0; */

/*	if ((pclnk == 0x03) && (line == 0x00)) {
		fprintf(stdout, "(forceful request) \n");
		pclnk = 0;
		resetlink();
		return;
	}*/

	if (ik.timeout) ik.timeout--; 

	if (ik.queued) {
	/* force send */
		if (ik.timeout) {
			if (sendbyte(line, *(ik.flowed))) {
				ik.timeout = maxtime;
				if (--ik.queued)
					sendbyte(0x03, *(++ik.flowed));
			}
		} else {
			fprintf(stdout, "(letterbox stuck) ");
			resetlink();
		}


	} else if (((ik.timeout) || (line != 0x03) || (dbusstat & DBUS_WRITE))
		&& ((ik.recvd != 4) || (!(ik.flags & (cFlag_wOk | cFlag_wWait))))) {

		if (!(ik.timeout)) {
			fprintf(stdout, "(postman late) ");
			resetlink();
			ik.timeout = maxtime;
		}

		i = recbyte(line);
		if (i != -1) {
			d_printf("[Rcv] 0x%02X\n", i);
			if (ik.recvd <= ik.max) {
				ik.timeout = maxtime;
				*(ik.flow + ik.recvd++) = i;


			} else {
				fprintf(stdout, "(keyless) ");
				resetlink();
			}
		}

	} else if (ik.recvd) {
	/* tokenize */
		tokenize();
		ik.flowed = ik.flow;
	}

}
/* Exported <<< */


/*#ifdef extlink
	if (!lc_avail) {
		if ((lc_err = lc.check(&lc_sts))) 
			print_lc_error(lc_err);

		if (lc_sts & STATUS_RX) {
			if ((lc_err = lc.get(&lc_byte))) {
				print_lc_error(lc_err);
			}
			lc_avail = 1;
			setsend(1, &lc_byte, 0);
		}
	}
#endif*/


/*		#ifdef extlink
			if ((line != 0x03) && elink) {
				setrecv(1, xsignal, 1);
			}
		#endif
		#ifdef extlink
			} 
				if ((lc_err = lc.put(i))) {
					print_lc_error(lc_err);
					return;
				}
			}
		#endif
	}

*/
