/*
 * Copyright (c) 2004 NONAKA Kimihiro
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "compiler.h"

#include "pccore.h"
#include "np2.h"
#include "commng.h"

#include <sys/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <termios.h>


typedef struct {
	int		hdl;

	struct termios	tio;
} _CMSER, *CMSER;

const UINT32 cmserial_speed[10] = {
	110, 300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200
};


static UINT
serialread(COMMNG self, UINT8 *data)
{
	CMSER serial = (CMSER)(self + 1);
	size_t size;
	int bytes;
	int rv;

	rv = ioctl(serial->hdl, FIONREAD, &bytes);
	if (rv == 0 && bytes > 0) {
		VERBOSE(("serialread: bytes = %d", bytes));
		size = read(serial->hdl, data, 1);
		if (size == 1) {
			VERBOSE(("serialread: data = %02x", *data));
			return 1;
		}
		VERBOSE(("serialread: read failure (%s)", strerror(errno)));
	}
	return 0;
}

static UINT
serialwrite(COMMNG self, UINT8 data)
{
	CMSER serial = (CMSER)(self + 1);
	size_t size;

	size = write(serial->hdl, &data, 1);
	if (size == 1) {
		VERBOSE(("serialwrite: data = %02x", data));
		return 1;
	}
	VERBOSE(("serialwrite: write failure (%s)", strerror(errno)));
	return 0;
}

static UINT8
serialgetstat(COMMNG self)
{
	CMSER serial = (CMSER)(self + 1);
	int status;
	int rv;

	rv = ioctl(serial->hdl, TIOCMGET, &status);
	if (rv < 0) {
		VERBOSE(("serialgetstat: ioctl: %s", strerror(errno)));
		return 0x20;
	}
	if (!(status & TIOCM_DSR)) {
		VERBOSE(("serialgetstat: DSR is disable"));
		return 0x20;
	}
	VERBOSE(("serialgetstat: DSR is enable"));
	return 0x00;
}

static INTPTR
serialmsg(COMMNG self, UINT msg, INTPTR param)
{

	(void)self;
	(void)msg;
	(void)param;

	return 0;
}

static void
serialrelease(COMMNG self)
{
	CMSER serial = (CMSER)(self + 1);

	tcsetattr(serial->hdl, TCSANOW, &serial->tio);
	close(serial->hdl);
	_MFREE(self);
}


/* ---- interface */
#if defined(SERIAL_DEBUG)
static void
print_status(const struct termios *tio)
{
	char *csstr;
	int cs;
	speed_t ispeed = cfgetispeed(tio);
	speed_t ospeed = cfgetospeed(tio);

	g_printerr(" ispeed %d", ispeed);
	g_printerr(" ospeed %d", ospeed);
	g_printerr("%s", "\r\n");
	g_printerr(" %cIGNBRK", (tio->c_iflag & IGNBRK) ? '+' : '-');
	g_printerr(" %cBRKINT", (tio->c_iflag & BRKINT) ? '+' : '-');
	g_printerr(" %cIGNPAR", (tio->c_iflag & IGNPAR) ? '+' : '-');
	g_printerr(" %cPARMRK", (tio->c_iflag & PARMRK) ? '+' : '-');
	g_printerr(" %cINPCK", (tio->c_iflag & INPCK) ? '+' : '-');
	g_printerr(" %cISTRIP", (tio->c_iflag & ISTRIP) ? '+' : '-');
	g_printerr(" %cINLCR", (tio->c_iflag & INLCR) ? '+' : '-');
	g_printerr(" %cIGNCR", (tio->c_iflag & IGNCR) ? '+' : '-');
	g_printerr(" %cICRNL", (tio->c_iflag & ICRNL) ? '+' : '-');
	g_printerr(" %cIXON", (tio->c_iflag & IXON) ? '+' : '-');
	g_printerr(" %cIXOFF", (tio->c_iflag & IXOFF) ? '+' : '-');
	g_printerr(" %cIXANY", (tio->c_iflag & IXANY) ? '+' : '-');
	g_printerr(" %cIMAXBEL", (tio->c_iflag & IMAXBEL) ? '+' : '-');
	g_printerr("%s", "\r\n");
	g_printerr(" %cOPOST", (tio->c_oflag & OPOST) ? '+' : '-');
	g_printerr(" %cONLCR", (tio->c_oflag & ONLCR) ? '+' : '-');
#ifdef OXTABS
	g_printerr(" %cOXTABS", (tio->c_oflag & OXTABS) ? '+' : '-');
#endif
#ifdef TABDLY
	g_printerr(" %cTABDLY", (tio->c_oflag & TABDLY) == XTABS ? '+' : '-');
#endif
#ifdef ONOEOT
	g_printerr(" %cONOEOT", (tio->c_oflag & ONOEOT) ? '+' : '-');
#endif
	g_printerr("%s", "\r\n");

	cs = tio->c_cflag & CSIZE;
	switch (cs) {
	case CS5:
		csstr = "5";
		break;

	case CS6:
		csstr = "6";
		break;

	case CS7:
		csstr = "7";
		break;

	case CS8:
		csstr = "8";
		break;

	default:
		csstr = "?";
		break;
	}
	g_printerr(" cs%s", csstr);
	g_printerr(" %cCSTOPB", (tio->c_cflag & CSTOPB) ? '+' : '-');
	g_printerr(" %cCREAD", (tio->c_cflag & CREAD) ? '+' : '-');
	g_printerr(" %cPARENB", (tio->c_cflag & PARENB) ? '+' : '-');
	g_printerr(" %cPARODD", (tio->c_cflag & PARODD) ? '+' : '-');
	g_printerr(" %cHUPCL", (tio->c_cflag & HUPCL) ? '+' : '-');
	g_printerr(" %cCLOCAL", (tio->c_cflag & CLOCAL) ? '+' : '-');
#ifdef CCTS_OFLOW
	g_printerr(" %cCCTS_OFLOW", (tio->c_cflag & CCTS_OFLOW) ? '+' : '-');
#endif
	g_printerr(" %cCRTSCTS", (tio->c_cflag & CRTSCTS) ? '+' : '-');
#ifdef CRTS_IFLOW
	g_printerr(" %cCRTS_IFLOW", (tio->c_cflag & CRTS_IFLOW) ? '+' : '-');
#endif
#ifdef MDMBUF
	g_printerr(" %cMDMBUF", (tio->c_cflag & MDMBUF) ? '+' : '-');
#endif
	g_printerr(" %cECHOKE", (tio->c_lflag & ECHOKE) ? '+' : '-');
	g_printerr(" %cECHOE", (tio->c_lflag & ECHOE) ? '+' : '-');
	g_printerr(" %cECHO", (tio->c_lflag & ECHO) ? '+' : '-');
	g_printerr(" %cECHONL", (tio->c_lflag & ECHONL) ? '+' : '-');
	g_printerr(" %cECHOPRT", (tio->c_lflag & ECHOPRT) ? '+' : '-');
	g_printerr(" %cECHOCTL", (tio->c_lflag & ECHOCTL) ? '+' : '-');
	g_printerr(" %cISIG", (tio->c_lflag & ISIG) ? '+' : '-');
	g_printerr(" %cICANON", (tio->c_lflag & ICANON) ? '+' : '-');
#ifdef ALTWERASE
	g_printerr(" %cALTWERASE", (tio->c_lflag & ALTWERASE) ? '+' : '-');
#endif
	g_printerr(" %cIEXTEN", (tio->c_lflag & IEXTEN) ? '+' : '-');
	g_printerr("%s", "\r\n");
#ifdef EXTPROC
	g_printerr(" %cEXTPROC", (tio->c_lflag & EXTPROC) ? '+' : '-');
#endif
	g_printerr(" %cTOSTOP", (tio->c_lflag & TOSTOP) ? '+' : '-');
	g_printerr(" %cFLUSHO", (tio->c_lflag & FLUSHO) ? '+' : '-');
#ifdef NOKERNINFO
	g_printerr(" %cNOKERNINFO", (tio->c_lflag & NOKERNINFO) ? '+' : '-');
#endif
	g_printerr(" %cPENDIN", (tio->c_lflag & PENDIN) ? '+' : '-');
	g_printerr(" %cNOFLSH", (tio->c_lflag & NOFLSH) ? '+' : '-');
	g_printerr("%s", "\r\n");
}
#endif

convert_np2tocm(UINT port, UINT8* param, UINT32* speed) {
	static const int cmserial_pc98_ch1_speed[] = {
		0, 75, 150, 300, 600, 1200, 2400, 4800, 9600
	};
	static const int cmserial_pc98_ch23_speed[] = {
		75, 150, 300, 600, 1200, 2400, 4800, 9600, 19200
	};

	switch(port) {
	case 0:
		*speed = cmserial_pc98_ch1_speed[np2cfg.memsw[1] & 0xF];
		break;
	case 1:
		if(!(np2cfg.pc9861sw[0] & 0x2)) {	// Sync
			*speed = cmserial_pc98_ch23_speed[7 - ((np2cfg.pc9861sw[0] >> 2) & 0x7) + 1];
		} else {	// Async
			*speed = cmserial_pc98_ch23_speed[8 - (((np2cfg.pc9861sw[0] >> 2) & 0xF) - 4)];
		}
		break;
	case 2:
		if(!(np2cfg.pc9861sw[2] & 0x2)) {	// Sync
			*speed = cmserial_pc98_ch23_speed[7 - ((np2cfg.pc9861sw[2] >> 2) & 0x7) + 1];
		} else {	// Async
			*speed = cmserial_pc98_ch23_speed[8 - (((np2cfg.pc9861sw[2] >> 2) & 0xF) - 4)];
		}
		break;
	}

	*param = 0;
	switch(port) {
	case 0:
		*param |= np2cfg.memsw[0] & 0xC;
		switch((np2cfg.memsw[0] & 0x30) >> 4) {
		case 1:
			*param |= 0x10;
			break;
		case 3:
			*param |= 0x30;
			break;
		default:
			break;
		}
		switch((np2cfg.memsw[0] & 0xC0) >> 6) {
		case 2:
			*param |= 0x80;
			break;
		case 3:
			*param |= 0xC0;
			break;
		default:
			break;
		}
		break;
	case 1:
	case 2:
		*param |= 0xC;
		break;
	}
}

COMMNG
cmserial_create(UINT port, UINT8 param, UINT32 speed)
{
	static const int cmserial_cflag[] = {
		B75, B110, B300, B600, B1200, B2400, B4800,
		B9600, B19200, B38400, B57600, B115200
	};
	static const int csize[] = { CS5, CS6, CS7, CS8 };
	struct termios options, origopt;
	COMMNG ret;
	CMSER serial;
	int hdl;
	UINT i;

	if(np2oscfg.com[port].direct) {
		convert_np2tocm(port, &param, &speed);
	}

	VERBOSE(("cmserial_create: port = %d, param = %02x, speed = %d", port, param, speed));

	if (port == 0 || port > MAX_SERIAL_PORT_NUM) {
		VERBOSE(("cmserial_create: port is invalid"));
		goto cscre_failure;
	}

	port--;
	if (np2oscfg.com[port].mout[0] == '\0') {
		VERBOSE(("cmserial_create: com device file is disable"));
		goto cscre_failure;
	}

	hdl = open(np2oscfg.com[port].mout, O_RDWR | O_NOCTTY | O_NDELAY);
	if (hdl == -1) {
		VERBOSE(("cmserial_create: open failure %s, %s", np2oscfg.com[port].mout, strerror(errno)));
		goto cscre_failure;
	}

	if (!isatty(hdl)) {
		VERBOSE(("cmserial_create: not terminal file descriptor (%s)", strerror(errno)));
		goto cscre_close;
	}

	/* get current options for the port */
	tcgetattr(hdl, &options);
	origopt = options;

	/* baud rates */
	for (i = 0; i < NELEMENTS(cmserial_speed); i++) {
		if (cmserial_speed[i] >= speed) {
			VERBOSE(("cmserial_create: spped = %d", cmserial_speed[i]));
			break;
		}
	}
	if (i >= NELEMENTS(cmserial_speed)) {
		VERBOSE(("cmserial_create: speed is invaild"));
		goto cscre_close;
	}
	cfsetispeed(&options, cmserial_cflag[i]);
	cfsetospeed(&options, cmserial_cflag[i]);

	/* character size bits */
	options.c_cflag &= ~CSIZE;
	options.c_cflag |= csize[(param >> 2) & 3];
	VERBOSE(("cmserial_create: charactor size = %d", csize[(param >> 2) & 3]));

	/* parity check */
	switch (param & 0x30) {
	case 0x10:
		VERBOSE(("cmserial_create: odd parity"));
		options.c_cflag |= PARENB | PARODD;
		options.c_iflag |= INPCK | ISTRIP;
		break;

	case 0x30:
		VERBOSE(("cmserial_create: even parity"));
		options.c_cflag |= PARENB;
		options.c_cflag &= ~PARODD;
		options.c_iflag |= INPCK | ISTRIP;
		break;

	default:
		VERBOSE(("cmserial_create: non parity"));
		options.c_cflag &= ~PARENB;
		options.c_iflag &= ~(INPCK | ISTRIP);
		break;
	}

	/* stop bits */
	switch (param & 0xc0) {
	case 0x80:
		VERBOSE(("cmserial_create: stop bits: 1.5"));
		break;

	case 0xc0:
		VERBOSE(("cmserial_create: stop bits: 2"));
		options.c_cflag |= CSTOPB;
		break;

	default:
		VERBOSE(("cmserial_create: stop bits: 1"));
		options.c_cflag &= ~CSTOPB;
		break;
	}

	/* set misc flag */
	cfmakeraw(&options);
	options.c_cflag |= CLOCAL | CREAD;
	options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

#if defined(SERIAL_DEBUG)
	print_status(&options);
#endif

	ret = (COMMNG)_MALLOC(sizeof(_COMMNG) + sizeof(_CMSER), "SERIAL");
	if (ret == NULL) {
		VERBOSE(("cmserial_create: memory alloc failure"));
		goto cscre_close;
	}

	/* set the new options for the port */
	tcsetattr(hdl, TCSANOW, &options);

#if 1
	ret->connect = COMCONNECT_MIDI;
#else
	ret->connect = COMCONNECT_SERIAL;
#endif
	ret->read = serialread;
	ret->write = serialwrite;
	ret->getstat = serialgetstat;
	ret->msg = serialmsg;
	ret->release = serialrelease;
	serial = (CMSER)(ret + 1);
	serial->hdl = hdl;
	serial->tio = origopt;
	return ret;

cscre_close:
	close(hdl);
cscre_failure:
	return NULL;
}
