#include <stdio.h>
#include "global.h"
#include "mbuf.h"
#include "ax25.h"
#include "timer.h"
#include "iface.h"
#include "lapb.h"
#include "cmdparse.h"
#include "session.h"
#include "st.h"			/* DG2KK */

extern struct tcb;
#define NULLTCB (struct tcb *)0

#ifdef FLOW
extern  int ttyflow;		/* DG2KK (output flowcontrol) */
#endif

char *ax25states[] = {
	"Disconnected",
	"Conn pending",
	"Disc pending",
	"Connected",
	"Frame Reject",
};

int domycall(),dodigipeat(),doaxstat(),dot1(),dot2(),dot3(),domaxframe(),
	doaxwindow(),dopaclen(),don2(),doaxreset(),doconok();	/* DG2KK */

static struct cmds axcmds[] = {
	"conok",	doconok,	0, NULLCHAR,	NULLCHAR, /* DG2KK */
	"digipeat",	dodigipeat,	0, NULLCHAR,	NULLCHAR,
	"maxframe",	domaxframe,	0, NULLCHAR,	NULLCHAR,
	"mycall",	domycall,	0, NULLCHAR,	NULLCHAR,
	"paclen",	dopaclen,	0, NULLCHAR,	NULLCHAR,
	"reset",	doaxreset,	2, "ax25 reset <axcb>", NULLCHAR,
	"retry",	don2,		0, NULLCHAR,	NULLCHAR,
	"status",	doaxstat,	0, NULLCHAR,	NULLCHAR,
	"t1",		dot1,		0, NULLCHAR,	NULLCHAR,
	"t2",		dot2,		0, NULLCHAR,	NULLCHAR,
	"t3",		dot3,		0, NULLCHAR,	NULLCHAR,
	"window",	doaxwindow,	0, NULLCHAR,	NULLCHAR,
	NULLCHAR,	NULLFP,		0, "ax25 subcommands: digipeat maxframe mycall paclen reset retry status\n\tt1 t2 t3 window",	NULLCHAR,
};
/* Multiplexer for top-level ax25 command */
doax25(argc,argv)
int argc;
char *argv[];
{
	return subcmd(axcmds,argc,argv);
}

static
doaxreset(argc,argv)
int argc;
char *argv[];
{
	struct ax25_cb *axp;
	extern char notval[];
	long htol();

	axp = (struct ax25_cb *)htol(argv[1]);
	if(!ax25val(axp)){
		printf(notval);
		return 1;
	}
	reset_ax25(axp);
	return 0;
}

/* Display AX.25 link level control blocks */
static
doaxstat(argc,argv)
int argc;
char *argv[];
{
	register int i;
	register struct ax25_cb *axp;
	char tmp[10];
	extern char notval[];
	long htol();

	if(argc < 2){
		printf("    &AXB IF   Snd-Q   Rcv-Q   Remote    State\n");
		for(i=0;i<NHASH;i++){
			for(axp = ax25_cb[i];axp != NULLAX25; axp = axp->next){
				pax25(tmp,&axp->addr.dest);
				printf("%8lx %-5s%-8d%-8d%-10s%s\n",
					(long)axp,axp->interface->name,
					len_q(axp->txq),len_mbuf(axp->rxq),
					tmp,ax25states[axp->state]);
			}
		}
		return 0;
	}
	axp = (struct ax25_cb *)htol(argv[1]);
	if(!ax25val(axp)){
		printf(notval);
		return 1;
	}
	dumpstat(axp);
	return 0;
}
/* Dump one control block */
static
dumpstat(axp)
register struct ax25_cb *axp;
{
	char tmp[10];
	int i;

	if(axp == NULLAX25 || axp->interface == NULLIF)
		return;
	/* DG2KK: changed "&AXB IF..." to " &AXB IF..." (Atari has 5 digit addr. */
	printf(" &AXB IF   Remote   RBW V(S) V(R) Unack P Retry   T1    T2    T3  State\n");
	pax25(tmp,&axp->addr.dest);
	printf("%4x %-5s%-9s",(int)axp,axp->interface->name,tmp);
	putchar(axp->rejsent ? 'R' : ' ');
	putchar(axp->remotebusy ? 'B' : ' ');
	putchar(axp->waitack ? 'W' : ' ');
	printf(" %4d %4d",axp->vs,axp->vr);
	printf(" %02d/%02d %d",axp->unack,axp->maxframe,axp->proto);
	printf(" %02d/%02d",axp->retries,axp->n2);
	if(run_timer(&axp->t1))
		printf(" %02d/%02d",axp->t1.start - axp->t1.count,
		 axp->t1.start);
	else
		printf("   /%02d",axp->t1.start);

	if(run_timer(&axp->t2))
		printf(" %02d/%02d",axp->t2.start - axp->t2.count,
		 axp->t2.start);
	else
		printf("   /%02d",axp->t2.start);

	if(run_timer(&axp->t3))
		printf(" %02d/%02d",axp->t3.start - axp->t3.count,
		 axp->t3.start);
	else
		printf("   /%02d",axp->t3.start);

	printf(" %s\n",ax25states[axp->state]);
	if(axp->addr.ndigis == 0)
		return;
	printf("Digipeaters:");
	for(i=0;i<axp->addr.ndigis;i++){
		pax25(tmp,&axp->addr.digis[i]);
		printf(" %s",tmp);
	}
	printf("\n");
}

/* Display or change our AX.25 address */
static
domycall(argc,argv)
int argc;
char *argv[];
{
	char buf[15];

	if(argc < 2){
		pax25(buf,&mycall);
		printf("%s\n",buf);
		return 0;
	}
	if(setcall(&mycall,argv[1]) == -1)
		return -1;
	mycall.ssid |= E;
	return 0;
}

/* Control AX.25 digipeating */
static
dodigipeat(argc,argv)
int argc;
char *argv[];
{
	extern int digipeat;

	if(argc == 1) {
		printf("digipeat %s\n",digipeat ? "on" : "off");
	} else {
		if(strcmp(argv[1],"on") == 0)
			digipeat = 1;
		else
			digipeat = 0;
	}
}

/* Set retransmission timer */
static
dot1(argc,argv)
int argc;
char *argv[];
{
	extern int16 t1init;

	if(argc == 1) {
		printf("T1 %d\n",t1init);
	} else {
		t1init = atoi(argv[1]);
	}
}

/* Set acknowledgement delay timer */
static
dot2(argc,argv)
int argc;
char *argv[];
{
	extern int16 t2init;

	if(argc == 1) {
		printf("T2 %d\n",t2init);
	} else {
		t2init = atoi(argv[1]);
	}
}

/* Set idle timer */
static
dot3(argc,argv)
int argc;
char *argv[];
{
	extern int16 t3init;

	if(argc == 1) {
		printf("T3 %d\n",t3init);
	} else {
		t3init = atoi(argv[1]);
	}
}

/* Set retry limit count */
static
don2(argc,argv)
int argc;
char *argv[];
{
	extern int16 n2;

	if(argc == 1) {
		printf("Retry %d\n",n2);
	} else {
		n2 = atoi(argv[1]);
	}
}

/* Set maximum number of frames that will be allowed in flight */
static
domaxframe(argc,argv)
int argc;
char *argv[];
{
	extern int16 maxframe;

	if(argc == 1) {
		printf("Maxframe %d\n",maxframe);
	} else {
		maxframe = atoi(argv[1]);
	}
}

/* Set maximum length of I-frame data field */
static
dopaclen(argc,argv)
int argc;
char *argv[];
{
	extern int16 paclen;

	if(argc == 1) {
		printf("Paclen %d\n",paclen);
	} else {
		paclen = atoi(argv[1]);
	}
}

/* Set high water mark on receive queue that triggers RNR */
static
doaxwindow(argc,argv)
int argc;
char *argv[];
{
	extern int16 axwindow;

	if(argc == 1) {
		printf("Axwindow %d\n",axwindow);
	} else {
		axwindow = atoi(argv[1]);
	}
}
/* End of ax25 subcommands */

/* Initiate interactive AX.25 connect to remote station */
doconnect(argc,argv)
int argc;
char *argv[];
 {
	void ax_rx(),ax_tx(),ax_state();
	int ax_parse();
	struct ax25_addr dest;
	struct ax25 addr;
	struct ax25_cb *open_ax25();
	struct interface *ifp;
	struct session *s;
	extern int16 axwindow;
	int i;

	for(ifp = ifaces; ifp != NULLIF; ifp = ifp->next)
		if(strcmp(argv[1],ifp->name) == 0)
			break;

	if(ifp == NULLIF){
		printf("Interface %s unknown\n",argv[1]);
		return 1;
	}
	setcall(&dest,argv[2]);
	/* See if a session already exists */
	for(s = sessions; s < &sessions[nsessions]; s++){
		if(s->type == AX25TNC
		 && addreq(&s->cb.ax25_cb->addr.dest,&dest)){
#if ( MAC || AMIGA )
			printf("Session %lu to %s already exists\n",
#else
			printf("Session %u to %s already exists\n",
#endif
				s - sessions,argv[2]);
			return 1;
		}
	}
	/* Allocate a session descriptor */
	if((s = newsession()) == NULLSESSION){
		printf("Too many sessions\n");
		return 1;
	}
	if((s->name = malloc((unsigned)strlen(argv[2])+1)) != NULLCHAR)
		strcpy(s->name,argv[2]);
	s->type = AX25TNC;
	s->parse = ax_parse;
	current = s;
	ASSIGN(addr.source,mycall);	/* DG2KK: should be changed */
	setcall(&addr.dest,argv[2]);
	for(i=3; i < argc; i++)
		setcall(&addr.digis[i-3],argv[i]);

	addr.ndigis = i - 3;
	s->cb.ax25_cb = open_ax25(&addr,axwindow,ax_rx,ax_tx,ax_state,ifp,(char *)s);
	go();
	return 0;
}


/* Display changes in AX.25 state */
void
ax_state(axp,old,new)
struct ax25_cb *axp;
int old,new;
{
	struct session *s;
	char remote[10];		/* DG2KK */

	s = (struct session *)axp->user;

	if(current != NULLSESSION && current->type == AX25TNC && current == s){
		printf("%s\n",ax25states[new]);

		/* added to enable a 'bell' when state is connected */
/* ---- DG2KK: AX25 logging ---- */
		if(new == CONNECTED) {
			printf("\007");
			pax25(remote,&axp->addr.dest);
			log(NULLTCB,"Connected to %s",remote);
		}

		if(new == DISCONNECTED) {
			pax25(remote,&axp->addr.dest);
			log(NULLTCB,"Disconnected from %s",remote);
			cmdmode();
		}
/* ----- */
		fflush(stdout);
	}
	if(new == DISCONNECTED){
		axp->user = NULLCHAR;
		freesession(s);
	}
}
/* Handle typed characters on AX.25 connection */
int
ax_parse(buf,cnt)
char *buf;
int16 cnt;
{
	struct mbuf *bp;
	register char *cp;
	char c;

	if(current == NULLSESSION || current->type != AX25TNC)
		return;	/* "can't happen" */

	/* If recording is on, record outgoing stuff too */
	if(current->record != NULLFILE)
		fwrite(buf,1,cnt,current->record);

	/* Allocate buffer and start it with the PID */
	bp = alloc_mbuf(cnt+1);
	*bp->data = PID_FIRST | PID_LAST | PID_NO_L3;
	bp->cnt++;

	/* Copy keyboard buffer to output, stripping line feeds */
	cp = bp->data + 1;
	while(cnt-- != 0){
		c = *buf++;
		if(c != '\n'){
			*cp++ = c;
			bp->cnt++;
		}
	}
	send_ax25(current->cb.ax25_cb,bp);
}

/* This is the default receive upcall function, used when
 * someone else connects to us.
 */
void
ax_incom(axp,cnt)
register struct ax25_cb *axp;
int16 cnt;
{
	/* temporary hack - replace with a switch */
	mbx_incom(axp,cnt);
	return;
}

/* This function sets up an ax25 chat session.
 * (Formerly ax_incom())!
 * Handle new incoming terminal sessions
 * This is the default receive upcall function, used when
 * someone else connects to us
 */
void
ax_session(axp,cnt)		/* DG2KK: was ax_income() */
register struct ax25_cb *axp;
int16 cnt;
{
	struct session *s;
	char remote[10];
	void ax_rx(),ax_state();

	pax25(remote,&axp->addr.dest);
	if((s = newsession()) == NULLSESSION){
		/* Out of sessions */
		disc_ax25(axp);
		return;
	}
	s->type = AX25TNC;
	s->name = malloc((int16)strlen(remote)+1);
	s->cb.ax25_cb = axp;
	s->parse = ax_parse;
	strcpy(s->name,remote);
	axp->r_upcall = ax_rx;
	axp->s_upcall = ax_state;
	axp->user = (char *)s;
#if ( MAC || AMIGA ) /* was: (defined(MAC) || defined(AMIGA)) DG2KK */
	printf("\007Incoming AX25 session %lu from %s\n",s - sessions,remote);
#else
	printf("\007Incoming AX25 session %u from %s\n",s - sessions,remote);
#endif
	fflush(stdout);
}

/* Handle incoming terminal traffic */
void
ax_rx(axp,cnt)
struct ax25_cb *axp;
int16 cnt;
{
	register struct mbuf *bp;
	struct mbuf *recv_ax25();
	char c;

	/* Hold output if we're not the current session */
	if(mode != CONV_MODE || current == NULLSESSION
	 || current->type != AX25TNC || current->cb.ax25_cb != axp)
		return;

#ifdef FLOW
	if (ttyflow == 0)	/* DG2KK */
		return;
#endif FLOW

	if((bp = recv_ax25(axp,cnt)) == NULLBUF)
		return;

	/* Display received characters, translating CR's to CR/LF */
	while(bp != NULLBUF){
		while(bp->cnt-- != 0){
			c = *bp->data++;
			putc(c,stdout);
			if(current->record){
				fputc(c,current->record);
				if(c == '\r')
					fputc('\n',current->record);
			}
			if(c == '\r')
				putc('\n',stdout);
		}
		bp = free_mbuf(bp);
	}
	if(current->record)
		fflush(current->record);
	fflush(stdout);
}
/* Handle transmit upcalls. Used only for file uploading */
void
ax_tx(axp,cnt)
struct ax25_cb *axp;
int16 cnt;
{
	register char *cp;
	struct session *s;
	register struct mbuf *bp;
	int16 size;
	int c;

	if((s = (struct session *)axp->user) == NULLSESSION
	 || s->upload == NULLFILE)
		return;
	while(cnt != 0){
		size = min(cnt,axp->paclen+1);
		if((bp = alloc_mbuf(size)) == NULLBUF)
			break;
		cp = bp->data;
		/* Start with the PID */
		*cp++ = PID_FIRST | PID_LAST | PID_NO_L3;
		bp->cnt++;

		/* Now send data characters, translating between local
		 * keyboard end-of-line sequences and the (unwritten)
		 * AX.25 convention, which is carriage-return only
		 */
		while(bp->cnt < size){
			if((c = getc(s->upload)) == EOF)
				break;
#ifdef	MSDOS
			/* MS-DOS gives cr-lf */
			if(c == '\n')
				continue;
#endif
#if (UNIX || MAC || AMIGA) /*(defined(UNIX) || defined(MAC) || defined(AMIGA))*/
			/* These give lf only */
			if(c == '\n')
				c = '\r';
#endif
			*cp++ = c;
			bp->cnt++;
		}	
		if(bp->cnt > 1) {
			send_ax25(axp,bp);
		} else {
			/* Empty frame, don't bother sending */
			free_p(bp);
			break;
		}
		cnt -= bp->cnt;
	}
	if(cnt != 0){
		/* Error or end-of-file */
		fclose(s->upload);
		s->upload = NULLFILE;
		free(s->ufile);
		s->ufile = NULLCHAR;
	}
}

/* Control AX.25 connects (DG2KK) */
static
doconok(argc,argv)
int argc;
char *argv[];
{
	extern int conok;

	if(argc == 1) {
		printf("conok %s\n",conok ? "on" : "off");
	} else {
		if(strcmp(argv[1],"on") == 0)
			conok = 1;
		else
			conok = 0;
	}
}

