#include <string.h>
#include <errno.h>
#include <netosIo.h>

#include <serParam.h>

#include <ddi.h>
#include "assert.h"

#include "termios.h"

/* Take from netos_serl.h so we don't depend on it */
#define inCSIZE       0xc /* bits 3 and 4 encode the character size */
extern int netos_serlioctl (int, int, char *);
extern int netos_serlread (int, void *, int, int *);
extern int netos_serlwrite (int, void *, int, int *);

extern int serl_port_index(int);
static void echo (int arg, int channel, void *buffer, int len);

static struct serial_cntr_t baseline_counters[SERIAL_MAX];
static int shimecho[SERIAL_MAX];

/*
 * Function: oldapi_serlioctl()
 *
 * Description: Wrapper to provide old API if needed
 * returns
 *              EINVAL when channel is not valid
 *              EBADF when channel is not opened
 *              err_code returned from onccIoctl
 */

int 
oldapi_serlioctl (int channel, int request, char *args)
{
    int rc = 0;
    struct termios tios_set;
    struct termios tios_check;
    struct serial_status_t ser_status;
    struct serial_cntr_t ser_counters;
    int fd;

    int idx = serl_port_index(channel);
    if ( idx == -1 )
    {
        return EBADF;
    }

    /* Recover file descriptor for tc calls */
    for (fd = 0 ; fd < MAX_DEV ; fd++) {
	if ( (deviceTable[fd].channel == channel) && 
	     (deviceTable[fd].ioctl ==&oldapi_serlioctl) ) 
	   break;
    }
    if (fd == MAX_DEV) {
	return EBADF;
    }

    fd |= DDI_BIT;

    switch(request) {
    case SIO_BAUD_SET:
	rc = tcgetattr(fd, &tios_set);
	if (rc < 0) 
	{
	    return EIO;
	}

	cfsetospeed(&tios_set, *(int*)args);

	rc = tcsetattr(fd, TCSANOW, &tios_set);
	if (rc < 0) 
	{
	    return EIO;
	}
	tcgetattr(fd, &tios_check);
	if (rc < 0) 
	{
	    return EIO;
	}
	if (memcmp(&tios_set, &tios_check, sizeof(struct termios)) != 0) 
	{
	    return EIO;
	}
	return 0;
	/* break; */
    case SIO_BAUD_GET:
	rc = tcgetattr(fd, &tios_set);
	if (rc < 0) 
	{
	    return EIO;
	}

	 *(int*)args = cfgetospeed(&tios_set);
	return 0;
	/* break; */
    case SIO_MODE_SET:
	/* Set any mode you like, the driver does its own thing */
	return 0;
	/* break; */
    case SIO_MODE_GET:
	/* Always tell people we're mode INT */
	*(int*)args = SIO_MODE_INT;
	return 0;
	/* break; */
    case SIO_TERM_SEND_CR:
	rc = tcgetattr(fd, &tios_set);
	if (rc < 0) 
	{
	    return EIO;
	}

	if (*(int*)args) 
	{
	    tios_set.c_oflag |= ONLCR;
	} else
	{
	    tios_set.c_oflag &= ~ONLCR;
	}

	rc = tcsetattr(fd, TCSANOW, &tios_set);
	if (rc < 0) 
	{
	    return EIO;
	}
	tcgetattr(fd, &tios_check);
	if (rc < 0) 
	{
	    return EIO;
	}
	if (memcmp(&tios_set, &tios_check, sizeof(struct termios)) != 0) 
	{
	    return EIO;
	}
	return 0;
	/* break; */
    case SIO_GET_STATUS_DCD:
	tcgetstats(fd, &ser_status);
	if (rc < 0) 
	{
	    return EIO;
	}

	*(int*)args = (ser_status.mstat & MS_DCD) ? 1 : 0;

	return 0;
	/* break; */
    case SIO_GET_STATUS_CTS:
	tcgetstats(fd, &ser_status);
	if (rc < 0) 
	{
	    return EIO;
	}

	*(int*)args = (ser_status.mstat & MS_CTS) ? 1 : 0;

	return 0;
	/* break; */
    case SIO_GET_STATUS_DSR:
	tcgetstats(fd, &ser_status);
	if (rc < 0) 
	{
	    return EIO;
	}

	*(int*)args = (ser_status.mstat & MS_DSR) ? 1 : 0;

	return 0;
	/* break; */
    case SIO_SET_STATE_DTR:
	tcgetstats(fd, &ser_status);
	if (rc < 0) 
	{
	    return EIO;
	}

	if (*(int*)args) 
	{	
	    ser_status.mstat |= MS_DTR;
	} else
	{
	    ser_status.mstat &= ~MS_DTR;
	}
	tcsetsignals(fd, ser_status.mstat);
	if (rc < 0) 
	{
	    return EIO;
	}
	return 0;
	/* break; */
    case SIO_SET_STATE_RTS:
	tcgetstats(fd, &ser_status);
	if (rc < 0) 
	{
	    return EIO;
	}

	if (*(int*)args) 
	{	
	    ser_status.mstat |= MS_DTR;
	} else
	{
	    ser_status.mstat &= ~MS_DTR;
	}
	tcsetsignals(fd, ser_status.mstat);
	if (rc < 0) 
	{
	    return EIO;
	}
	return 0;
	/* break; */
    case SIO_HW_OPTS_SET:
	rc = tcgetattr(fd, &tios_set);
	if (rc < 0) 
	{
	    return EIO;
	}

	tios_set.c_cflag &= ~(CSIZE | CSTOPB | PARENB | PARODD | CRTSCTS);
	tios_set.c_iflag &= ~(IXON | IXOFF);
	tios_set.c_xflag &= ~(XPAR);

	switch ( (*(int*)args)
	         & (SIO_SW_HANDSHAKING | SIO_HW_RTS_CTS_HANDSHAKING) )
	{
	case SIO_NO_HANDSHAKING:
	    break;
	case SIO_SW_HANDSHAKING:
	    tios_set.c_iflag |= (IXON | IXOFF);
	    break;
	case SIO_HW_RTS_CTS_HANDSHAKING:
	default:
	    tios_set.c_cflag |= CRTSCTS;
	    break;
	};

	switch ( (*(int*)args) & inCSIZE )
	{
	case SIO_FIVE_BIT_DATA_WIDTH:
	    tios_set.c_cflag |= CS5;
	    break;
	case SIO_SIX_BIT_DATA_WIDTH:
	    tios_set.c_cflag |= CS6;
	    break;
	case SIO_SEVEN_BIT_DATA_WIDTH:
	    tios_set.c_cflag |= CS7;
	    break;
	default:
	case SIO_EIGHT_BIT_DATA_WIDTH:
	    tios_set.c_cflag |= CS8;
	    break;
	};

	switch ( (*(int*)args) & (SIO_ONE_STOP_BIT | SIO_TWO_STOP_BITS) )
	{
	case SIO_TWO_STOP_BITS:
	    tios_set.c_cflag |= CSTOPB;
	    break;
	case SIO_ONE_STOP_BIT:
	default:
	    break;
	};

	switch ( (*(int*)args) & (SIO_EVEN_PARITY | SIO_ODD_PARITY) )
	{
	case SIO_EVEN_PARITY:
	    tios_set.c_cflag |= PARENB;
	    break;
	case SIO_ODD_PARITY:
	    tios_set.c_cflag |= PARENB | PARODD;
	    break;
	case SIO_NO_PARITY:
	default:
	    break;
	};
	
	rc = tcsetattr(fd, TCSANOW, &tios_set);
	if (rc < 0) 
	{
	    return EIO;
	}
	tcgetattr(fd, &tios_check);
	if (rc < 0) 
	{
	    return EIO;
	}
	if (memcmp(&tios_set, &tios_check, sizeof(struct termios)) != 0) 
	{
	    return EIO;
	}
	return 0;
	/* break; */
    case SIO_HW_OPTS_GET:
	rc = tcgetattr(fd, &tios_set);
	if (rc < 0) 
	{
	    return EIO;
	}

        *(int*)args = 0;
        /* do SIO_HW_OPTS_SET if necessary */
        switch (tios_set.c_cflag & CSIZE) {
        case CS5:
	    *(int*)args |= SIO_FIVE_BIT_DATA_WIDTH;
    	    break;
        case CS6:
    	    *(int*)args |= SIO_SIX_BIT_DATA_WIDTH;
    	    break;
        case CS7:
    	    *(int*)args |= SIO_SEVEN_BIT_DATA_WIDTH;
    	    break;
        case CS8:
    	    *(int*)args |= SIO_EIGHT_BIT_DATA_WIDTH;
    	    break;
        }
        
        if (tios_set.c_cflag & CSTOPB) {
    	    *(int*)args |= SIO_ONE_STOP_BIT;
        } else
	{
    	    *(int*)args |= SIO_TWO_STOP_BITS;
	}

        if (tios_set.c_cflag & PARENB) {
    	    if (tios_set.c_cflag & PARODD) {
    	        *(int*)args |= SIO_ODD_PARITY;
    	    } else
	    {
	        *(int*)args |= SIO_EVEN_PARITY;
	    }
        } else 
	{
	    *(int*)args |= SIO_NO_PARITY;
	}
    
        /* Partial and arbitrary flow control is not allowed in xk, only 
         * RTS/CTS, or full software or nothing */
        if ( (tios_set.c_cflag & CRTSCTS) == CRTSCTS ) {
    	    *(int*)args |= SIO_HW_RTS_CTS_HANDSHAKING;
        }  
        
        if ( (tios_set.c_iflag & (IXON | IXOFF)) == (IXON | IXOFF) ) {
    	    *(int*)args |= SIO_SW_HANDSHAKING;
        } else if ( !((tios_set.c_cflag & CRTSCTS) == CRTSCTS) ) {
    	    *(int*)args |= SIO_NO_HANDSHAKING;
        }

	return 0;
	/* break; */
    case SIO_GET_STATUS_ERROR:
	*(int *)args = 0;
	tcgetcounters(fd, &ser_counters);
	if (rc < 0) 
	{
	    return EIO;
	}

        if (baseline_counters[idx].norun != ser_counters.norun)
        {
            *(int *)args |= SIO_ERROR_OVERRUN;
        }

        if (baseline_counters[idx].nframe != ser_counters.nframe)
        {
            *(int *)args |= SIO_ERROR_FRAMING;
        }

        if (baseline_counters[idx].nparity != ser_counters.nparity)
        {
            *(int *)args |= SIO_ERROR_PARITY;
        }

	return 0;        
	/* break; */
    case SIO_CLEAR_STATUS_ERROR:
	tcgetcounters(fd, &ser_counters);
	if (rc < 0) 
	{
	    return EIO;
	}
        if (*(int *)args & SIO_ERROR_OVERRUN)
        {
	    baseline_counters[idx].norun = ser_counters.norun;
        }

        if (*(int *)args & SIO_ERROR_FRAMING)
        {
	    baseline_counters[idx].nframe = ser_counters.nframe;
        }

        if (*(int *)args & SIO_ERROR_PARITY)
        {
	    baseline_counters[idx].nframe = ser_counters.nframe;
        }

	return 0;
	/* break; */
    case SIO_TERM_ECHO:
	shimecho[idx] = *(int *)args;
	return 0;
    case SIO_BAUD_ERROR_SET:
	// Pass into new API for old API real layer
    case SIO_BAUD_ERROR_GET:
	// Pass into new API for old API real layer
    default:
	// Any unrecognized ioctls might be us recursively calling the DDI 
	// ioctl through the tc* calls.  Pass them on to the real driver
	return netos_serlioctl(channel, request, args);
	/* break; */  
    };

/*    return EIO; commented out to avoid compiler warning */
}

int 
oldapi_serlread (int channel, void *buffer, int length, int *bytesRead)
{
    int ret = netos_serlread(channel, buffer, length, bytesRead);
    int idx = serl_port_index(channel);
    
    if (shimecho[idx]) {
	echo( shimecho[idx], channel, buffer, *bytesRead );
    }
    return ret;
}


/*
 * Function: echo()
 *
 * Description: whenever a byte is receive, write it back.
 *
 * Input : arg - TERM_ECHO_ON or TERM_ECHO_OFF, TERM_ECHO_TRANSLATE
 *         channel - 0 for port com1, 1 for port com2
 *         buffer - data to be sent
 *         length - size of data
 *
 * Return: None
 *
 */

static void 
echo (int arg, int channel, void *buffer, int len)
{
    int dontcare;
    int pos, i;
    char *ptr;
    static int backslashRstate = 0;

    if (arg == TERM_ECHO_TRANSLATE)
    {
        ptr = (char*) buffer;
        pos = 0;
        i   = 0;

        if ((len > 0) && (backslashRstate == 1))
        {
            backslashRstate = 0;

            if (*ptr != '\n')
            {
                /*  send the \r */
                netos_serlwrite (channel, "\r", 1, &dontcare);
            }
        }

        while (i < len)
        {
            i = pos;

            while (i < (len - 1))
            {
                /*  point to an instance of "\r\n"  */
                if ((ptr[i] == '\r') && (ptr[i + 1] == '\n'))
                {
                    break;
                }
                else
                {
                    i++;
                }
            }
            
            if (i < (len - 1))
            {
                if ((i - pos) > 0)
                {
                    /* i points to an instance of "\r\n" -- echo
                        before the i-th character */
                    netos_serlwrite (channel, ptr + pos, (i - pos), &dontcare);
                }

                /*  jump in front of the /r character */
                i++;
                pos = i;
            }
            else 
            {
                if (ptr[i] == '\r')
                {
                    /* the last character is a \r -- save this state
                        to detect a \n */
                    backslashRstate = 1;

                    /*  echo all but the \r */
                    netos_serlwrite (channel, ptr+pos, (i - pos), &dontcare);
                }
                else
                {
                    /*  this is the last one    */
                    netos_serlwrite (channel, ptr+pos, 
                                     (i - pos) + 1, 
                                     &dontcare);
                }
                return;
            }
        }
    }
    else
    {
        netos_serlwrite (channel, buffer, len, &dontcare);
    }
}
