/*
 *
 *  AT chat library with GLib integration
 *
 *  Copyright (C) 2008-2011  Intel Corporation. All rights reserved.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <termios.h>

#include <glib.h>

#include "gattty.h"

static gboolean set_baud(const char *baud, struct termios *ti)
{
	speed_t speed;

	if (g_str_equal(baud, "300"))
		speed = B300;
	else if (g_str_equal(baud, "1200"))
		speed = B1200;
	else if (g_str_equal(baud, "2400"))
		speed = B2400;
	else if (g_str_equal(baud, "4800"))
		speed = B4800;
	else if (g_str_equal(baud, "9600"))
		speed = B9600;
	else if (g_str_equal(baud, "19200"))
		speed = B19200;
	else if (g_str_equal(baud, "38400"))
		speed = B38400;
	else if (g_str_equal(baud, "57600"))
		speed = B57600;
	else if (g_str_equal(baud, "115200"))
		speed = B115200;
	else if (g_str_equal(baud, "230400"))
		speed = B230400;
	else if (g_str_equal(baud, "460800"))
		speed = B460800;
	else if (g_str_equal(baud, "500000"))
		speed = B500000;
	else if (g_str_equal(baud, "576000"))
		speed = B576000;
	else if (g_str_equal(baud, "921600"))
		speed = B921600;
	else if (g_str_equal(baud, "1000000"))
		speed = B1000000;
	else if (g_str_equal(baud, "1152000"))
		speed = B1152000;
	else if (g_str_equal(baud, "1500000"))
		speed = B1500000;
	else if (g_str_equal(baud, "2000000"))
		speed = B2000000;
#ifdef B2500000
	else if (g_str_equal(baud, "2500000"))
		speed = B2500000;
#endif
#ifdef B3000000
	else if (g_str_equal(baud, "3000000"))
		speed = B3000000;
#endif
#ifdef B3500000
	else if (g_str_equal(baud, "3500000"))
		speed = B3500000;
#endif
#ifdef B4000000
	else if (g_str_equal(baud, "4000000"))
		speed = B4000000;
#endif
	else
		return FALSE;

	cfsetospeed(ti, speed);
	cfsetispeed(ti, speed);

	return TRUE;
}

static gboolean set_read(const char *bits, struct termios *ti)
{
	if (g_str_equal(bits, "off"))
		ti->c_cflag &= ~(CREAD);
	else if (g_str_equal(bits, "on"))
		ti->c_cflag |= CREAD;
	else
		return FALSE;

	return TRUE;
}

static gboolean set_stop_bits(const char *bits, struct termios *ti)
{
	if (g_str_equal(bits, "1"))
		ti->c_cflag &= ~(CSTOPB);
	else if (g_str_equal(bits, "2"))
		ti->c_cflag |= CSTOPB;
	else
		return FALSE;

	return TRUE;
}

static gboolean set_data_bits(const char *bits, struct termios *ti)
{
	if (g_str_equal(bits, "7")) {
		ti->c_cflag &= ~(CSIZE);
		ti->c_cflag |= CS7;
	} else if (g_str_equal(bits, "8")) {
		ti->c_cflag &= ~(CSIZE);
		ti->c_cflag |= CS8;
	} else
		return FALSE;

	return TRUE;
}

static gboolean set_parity(const char *parity, struct termios *ti)
{
	if (g_str_equal(parity, "none"))
		ti->c_cflag &= ~(PARENB);
	else if (g_str_equal(parity, "even")) {
		ti->c_cflag |= PARENB;
		ti->c_cflag &= ~(PARODD);
	} else if (g_str_equal(parity, "odd")) {
		ti->c_cflag |= PARENB;
		ti->c_cflag |= PARODD;
	} else
		return FALSE;

	return TRUE;
}

static gboolean set_xonxoff(const char *xonxoff, struct termios *ti)
{
	if (g_str_equal(xonxoff, "on")) {
		ti->c_iflag |= (IXON | IXOFF | IXANY);
		ti->c_cc[VSTART] = 17;
		ti->c_cc[VSTOP] = 19;
	} else if (g_str_equal(xonxoff, "off"))
		ti->c_iflag &= ~(IXON | IXOFF | IXANY);
	else
		return FALSE;

	return TRUE;
}

static gboolean set_rtscts(const char *rtscts, struct termios *ti)
{
	if (g_str_equal(rtscts, "on"))
		ti->c_cflag |= CRTSCTS;
	else if (g_str_equal(rtscts, "off"))
		ti->c_cflag &= ~(CRTSCTS);
	else
		return FALSE;

	return TRUE;
}

static gboolean set_local(const char *local, struct termios *ti)
{
	if (g_str_equal(local, "on"))
		ti->c_cflag |= CLOCAL;
	else if (g_str_equal(local, "off"))
		ti->c_cflag &= ~(CLOCAL);
	else
		return FALSE;

	return TRUE;
}

static int open_device(const char *tty, GHashTable *options)
{
	struct termios ti;
	int fd;

	/* Switch TTY to raw mode */
	memset(&ti, 0, sizeof(ti));
	cfmakeraw(&ti);

	if (options) {
		GHashTableIter iter;
		const char *key;
		const char *value;

		g_hash_table_iter_init (&iter, options);
		while (g_hash_table_iter_next(&iter, (void *) &key,
							(void *) &value)) {
			gboolean ok = FALSE;

			if (g_str_equal(key, "Baud"))
				ok = set_baud(value, &ti);
			else if (g_str_equal(key, "StopBits"))
				ok = set_stop_bits(value, &ti);
			else if (g_str_equal(key, "DataBits"))
				ok = set_data_bits(value, &ti);
			else if (g_str_equal(key, "Parity"))
				ok = set_parity(value, &ti);
			else if (g_str_equal(key, "XonXoff"))
				ok = set_xonxoff(value, &ti);
			else if (g_str_equal(key, "RtsCts"))
				ok = set_rtscts(value, &ti);
			else if (g_str_equal(key, "Local"))
				ok = set_local(value, &ti);
			else if (g_str_equal(key, "Read"))
				ok = set_read(value, &ti);

			if (ok == FALSE)
				return -1;
		}
	}

	fd = open(tty, O_RDWR | O_NOCTTY | O_NONBLOCK);
	if (fd < 0)
		return -1;

	tcflush(fd, TCIOFLUSH);
	tcsetattr(fd, TCSANOW, &ti);

	return fd;
}

GIOChannel *g_at_tty_open(const char *tty, GHashTable *options)
{
	GIOChannel *channel;
	int fd;

	fd = open_device(tty, options);
	if (fd < 0)
		return NULL;

	channel = g_io_channel_unix_new(fd);
	if (channel == NULL) {
		close(fd);
		return NULL;
	}

	g_io_channel_set_close_on_unref(channel, TRUE);

	return channel;
}

GIOChannel *g_at_tty_open_qcdm(const char *tty)
{
	GIOChannel *channel;
	struct termios ti;
	int fd;

        fd = open(tty, O_RDWR | O_NOCTTY | O_NONBLOCK);
        if (fd < 0)
                return NULL;

	/* Switch TTY to raw mode */
	memset(&ti, 0, sizeof(ti));
	cfmakeraw(&ti);

	/* No parity, 1 stop bit */
	ti.c_cflag &= ~(CSIZE | CSTOPB | PARENB);
	ti.c_cflag |= (B115200 | CS8);

	if (tcsetattr(fd, TCSANOW, &ti) < 0) {
		close(fd);
		return NULL;
	}

	channel = g_io_channel_unix_new(fd);
	if (channel == NULL) {
		close(fd);
		return NULL;
	}

	g_io_channel_set_close_on_unref(channel, TRUE);

	return channel;
}
