/****************************************************************************
    Copyright (C) 1987-2005 by Jeffery P. Hansen

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
****************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "gsim.h"

/*
  To transmit a character (tty receive):

     Unassert DSR
     Wait for DTR to be high
     Place data on RD
     Assert DSR (trigger on posedge)

  To receive a character (tty transmit):

    Unassert CTS
    Wait for RTS to be high
    Read data from TD
    Assert CTS (trigger on posedge)
 */

#define TTY_BUFSIZE	512		/* Number of characters to buffer */

#define TTY_TD	0
#define TTY_RD	1
#define TTY_RTS	2
#define TTY_CTS	3
#define TTY_DSR	4
#define TTY_DTR	5

#define TTY_EV_TRDONE		0	/* Transmit is complete */
#define TTY_EV_RECVDONE		1	/* Receive is complete */

#define TTY_DELAY_TRANSMIT	0	/* Time to put data on TD */
#define TTY_DELAY_RTS_UP	1	/* Time to transmit a character and assert RTS */
#define TTY_DELAY_RTS_DN	2	/* Time to transmit a character and assert RTS */
#define TTY_DELAY_RECEIVE	3	/* Time to receive a character */
#define TTY_DELAY_DTR_UP	4	/* */
#define TTY_DELAY_DTR_DN	5	/* */

struct tty_data {
  char	buffer[TTY_BUFSIZE];		/* Characters typed at keyboard */
  int	first;				/* First buffered character */
  int	last;				/* One after last buffered character */

  int	tr_sendwait;			/* Waiting for char to be transmitted  */
  int	tr_ackwait;			/* Waiting for acknowledge  */

  int	recv_ok;			/* Receiver is ready */
};

static void Tty_processEvent(SGate*,EvQueue*,SEvent*);
static int Tty_checkGate(SGate*);
static void Tty_initGate(EvQueue*,SGate*);
static void Tty_command(EvQueue *,SGate*,const char*);

static SGateInfo tty_info = {
  0,
  "tty",0x0,
  6,{{"TD",GIO_OUT,PF_CUT},		/* Transmitted data */
     {"RD",GIO_IN,PF_CUT},		/* Receive data */
     {"RTS",GIO_OUT,PF_CUT},		/* Request to send */
     {"CTS",GIO_IN,PF_CUT},		/* Clear to send */
     {"DSR",GIO_IN,PF_CUT},		/* Set set ready */
     {"DTR",GIO_OUT,PF_CUT}},		/* Data terminal ready */

  {{"TR",0,-1},
   {"RTS_UP",0,-1},
   {"RTS_DN",0,-1},
   {"RD",0,-1},
   {"DTR_UP",0,-1},
   {"DTR_DN",0,-1},
   {0}},

  Generic_copyGate,
  Tty_processEvent,
  Tty_checkGate,
  Tty_initGate,
  0,
  Tty_command
};

static void tty_rts(EvQueue *Q,SGate *g,int value)
{
  SState *S = alloc_SState();
  SPort *RTS = g->g_ports.port[TTY_RTS];
  int delay;

  SState_reinit(S,1);
  SState_convertFromInt(S,value);

  if (value)
    delay = g->g_delayParms[TTY_DELAY_RTS_UP];
  else
    delay = g->g_delayParms[TTY_DELAY_RTS_DN];

  EvQueue_setPort(Q,RTS,S,delay);

  free_SState(S);
}

static void tty_tryTransmit(EvQueue *Q,SGate *g)
{
  struct tty_data *d = (struct tty_data*) g->g_data;
  SPort *TD = g->g_ports.port[TTY_TD];
  SState *S;
  int c;

  if (d->first == d->last) return;		/* Nothing in buffer */
  if (d->tr_sendwait || d->tr_ackwait) return;	/* Not ready to send yet */

  S = alloc_SState();

  c = d->buffer[d->first];
  d->first = (d->first + 1) % TTY_BUFSIZE;

  d->tr_sendwait = 1;

  /*
   * Put character on output
   */
  SState_reinit(S,8);
  SState_convertFromInt(S,c);
  EvQueue_setPort(Q,TD,S,g->g_delayParms[TTY_DELAY_TRANSMIT]);

  /*
   * Send wakeup to self
   */
  EvQueue_qGateEv(Q,g,TTY_EV_TRDONE,0,g->g_delayParms[TTY_DELAY_TRANSMIT]);

  free_SState(S);
}

static void tty_receive(EvQueue *Q,SGate *g)
{
  SState *RD = SGate_allocPortState(g,TTY_RD);
  SPort *DTR = g->g_ports.port[TTY_DTR];
  SState *S = alloc_SState();

  SState_reinit(S,1);
  SState_convertFromInt(S,0);
  EvQueue_setPort(Q,DTR,S,g->g_delayParms[TTY_DELAY_DTR_DN]);

  /*
   * Send wakeup to self
   */
  EvQueue_qGateEv(Q,g,TTY_EV_RECVDONE,(void*)(RD->one[0] & 0xff),
		  g->g_delayParms[TTY_DELAY_RECEIVE]);

  free_SState(RD);
  free_SState(S);
}

static void tty_processGateEvent(SGate *g,EvQueue *Q,SEvent *E)
{
  struct tty_data *d = (struct tty_data*) g->g_data;

  switch (E->evgate.type) {
  case TTY_EV_TRDONE :
    {
#if 0
      SState *CTS = SGate_allocPortState(g,TTY_CTS);
      int cts = SState_getBitSym(CTS,0);
      free_SState(CTS);
#endif
      tty_rts(Q,g,1);
      d->tr_sendwait = 0;
      d->tr_ackwait = 1;
    }
    break;
  case TTY_EV_RECVDONE :
    {
      SPort *DTR = g->g_ports.port[TTY_DTR];
      SState *S = alloc_SState();
      size_t c = (size_t) E->evgate.gdata;

      sendMsg("tty_char %s %d",g->g_name,c);

      SState_reinit(S,1);
      SState_convertFromInt(S,1);
      EvQueue_setPort(Q,DTR,S,g->g_delayParms[TTY_DELAY_DTR_UP]);

      d->recv_ok = 1;

      free_SState(S);
    }
    break;
  }
}

static void Tty_processEvent(SGate *g,EvQueue *Q,SEvent *E)
{
  struct tty_data *d = (struct tty_data*) g->g_data;

  if (E->evclass == EV_GATE) {	/* Internal state change */
    tty_processGateEvent(g,Q,E);
  } else {			/* An input changed */
    if (!d->tr_sendwait) {
      if (IsChangeOn(E,g,TTY_CTS)) {
	SState *CTS = SGate_allocPortState(g,TTY_CTS);
	int cts = SState_getBitSym(CTS,0);
	free_SState(CTS);

	if (cts != SYM_ZERO) { 
	  d->tr_ackwait = 0;
	  tty_tryTransmit(Q,g);
	  tty_rts(Q,g,0);
	}
      }
    }

    if (d->recv_ok) {
      if (IsChangeOn(E,g,TTY_DSR)) {
	SState *DSR = SGate_allocPortState(g,TTY_DSR);
	int dsr = SState_getBitSym(DSR,0);
	free_SState(DSR);

	if (dsr != SYM_ZERO) 
	  tty_receive(Q,g);
      }
    }
  }
}

static int Tty_checkGate(SGate *g)
{
  SPort *TD = g->g_ports.port[TTY_TD];
  SPort *RD = g->g_ports.port[TTY_RD];
  SPort *RTS = g->g_ports.port[TTY_RTS];
  SPort *CTS = g->g_ports.port[TTY_CTS];
  SPort *DSR = g->g_ports.port[TTY_DSR];
  SPort *DTR = g->g_ports.port[TTY_DTR];

  if (TD->p_state.nbits != 8 || RD->p_state.nbits != 8) {
    errorGate(g->g_name,"TD and RD ports on tty must be 8 bits.");
    return -1;
  }
  if (RTS->p_state.nbits != 1 || CTS->p_state.nbits != 1 
      || DSR->p_state.nbits != 1 || DTR->p_state.nbits != 1) {
    errorGate(g->g_name,"RTS, CTS, DSR and DTR ports on tty must be 1 bit.");
    return -1;
  }

  return 0;
}

static void Tty_initGate(EvQueue *Q,SGate *g)
{
  struct tty_data *d = (struct tty_data*) malloc(sizeof(struct tty_data));
  SState *S = alloc_SState();
  SPort *RTS = g->g_ports.port[TTY_RTS];
  SPort *DTR = g->g_ports.port[TTY_DTR];

  g->g_data = d;
  d->first = d->last = 0;
  d->tr_sendwait = 0;
  d->tr_ackwait = 0;
  d->recv_ok = 1;

  SState_reinit(S,1);

  sendMsg("mktty %s",g->g_name);

  SState_zero(S);
  EvQueue_setPort(Q,RTS,S,0);

  SState_one(S);
  EvQueue_setPort(Q,DTR,S,0);

  free_SState(S);
}

static void Tty_command(EvQueue *Q,SGate *g,const char *cmd)
{
  struct tty_data *d = (struct tty_data*) g->g_data;
  int c;

  if (sscanf(cmd," key %d",&c) == 1) {
    if ( ((d->last+1) % TTY_BUFSIZE) != d->first) {
      d->buffer[d->last] = c;
      d->last = (d->last + 1) % TTY_BUFSIZE;
    }
  }

  tty_tryTransmit(Q,g);
}

void init_tty()
{
  SGateInfo_register(&tty_info,0);
}
