/*
 * AT Command Utilities (and terminal stuff for now)
 *
 * Copyright (C) 2010 by Multi-Tech Systems
 *
 * Author: James Maki <jmaki@multitech.com>
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 */

#define _GNU_SOURCE

#define __ATCMD_C	1

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <errno.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <string.h>

#include "global.h"
#include "log.h"
#include "utils.h"
#include "atcmd.h"
#include "sms_utils.h"

static const struct baud_map __baud_map[] = {
	{B300, 300},
	{B600, 600},
	{B1200, 1200},
	{B1800, 1800},
	{B2400, 2400},
	{B4800, 4800},
	{B9600, 9600},
	{B19200, 19200},
	{B38400, 38400},
	{B57600, 57600},
	{B115200, 115200},
	{B230400, 230400},
	{B460800, 460800},
	{B921600, 921600},
};

speed_t value_to_baud(speed_t value)
{
	int n = ARRAY_SIZE(__baud_map);
	int i;

	for (i = 0; i < n; ++i) {
		if (__baud_map[i].value == value) {
			return __baud_map[i].baud;
		}
	}

	log_warning("baud rate not valid: %lu", (unsigned long) value);

	return (speed_t) -1;
}

int rts_get(int fd)
{
	int err;
	int status;

	err = ioctl(fd, TIOCMGET, &status);
	if (err < 0) {
		return -1;
	}
	return status & TIOCM_RTS ? 1 : 0;
}

int dtr_get(int fd)
{
	int err;
	int status;

	err = ioctl(fd, TIOCMGET, &status);
	if (err < 0) {
		return -1;
	}
	return status & TIOCM_DTR ? 1 : 0;
}

int cts_get(int fd)
{
	int err;
	int status;

	err = ioctl(fd, TIOCMGET, &status);
	if (err < 0) {
		return -1;
	}
	return status & TIOCM_CTS ? 1 : 0;
}

int dsr_get(int fd)
{
	int err;
	int status;

	err = ioctl(fd, TIOCMGET, &status);
	if (err < 0) {
		return -1;
	}
	return status & TIOCM_DSR ? 1 : 0;
}

int cd_get(int fd)
{
	int err;
	int status;

	err = ioctl(fd, TIOCMGET, &status);
	if (err < 0) {
		return -1;
	}
	return status & TIOCM_CD ? 1 : 0;
}

int ri_get(int fd)
{
	int err;
	int status;

	err = ioctl(fd, TIOCMGET, &status);
	if (err < 0) {
		return -1;
	}
	return status & TIOCM_RI ? 1 : 0;
}

int line_signal_set(int fd, int signal, int value)
{
	int err;
	int status;

	err = ioctl(fd, TIOCMGET, &status);
	if (err < 0) {
		return -1;
	}

	if (value) {
		status &= ~signal;
	} else {
		status |= signal;
	}

	err = ioctl(fd, TIOCMSET, &status);
	if (err < 0) {
		return -1;
	}

	return status;
}

int rts_set(int fd, int value)
{
	return line_signal_set(fd, TIOCM_RTS, value);
}

int dtr_set(int fd, int value)
{
	return line_signal_set(fd, TIOCM_DTR, value);
}

int tty_configure(int fd, speed_t baud_rate)
{
	int err;
	struct termios params;

	err = tcgetattr(fd, &params);
	if (err < 0) {
		log_error("tcgetattr failed: %m");
		return -1;
	}

	cfmakeraw(&params);
	cfsetspeed(&params, baud_rate);

	err = tcsetattr(fd, TCSANOW, &params);
	if (err < 0) {
		log_error("tcsetattr failed: %m");
		return -1;
	}

	return 0;
}

int tty_open(const char *dev, speed_t baud_rate)
{
	int fd;

	fd = open(dev, O_RDWR | O_NOCTTY);
	if (fd < 0) {
		log_error("failed to open %s: %m", dev);
		return fd;
	}

	if (!isatty(fd)) {
		log_error("%s is not a tty device", dev);
		close(fd);
		return -1;
	}

	tty_configure(fd, baud_rate);
	tcflush(fd, TCIOFLUSH);

	log_debug("rts: %d", rts_get(fd));
	log_debug("dtr: %d", dtr_get(fd));
	log_debug("cts: %d", cts_get(fd));
	log_debug("dsr: %d", dsr_get(fd));
	log_debug("cd: %d", cd_get(fd));
	log_debug("ri: %d", ri_get(fd));

	return fd;
}

int tty_set_read_timeout(int fd, int timeout)
{
	int err;
	struct termios params;

	err = tcgetattr(fd, &params);
	if (err < 0) {
		log_error("tcgetattr failed: %m");
		return -1;
	}

	if (timeout < 0) {
		params.c_cc[VTIME] = 0;
		params.c_cc[VMIN] = 0;
	} else if (timeout == 0) {
		params.c_cc[VTIME] = 0;
		params.c_cc[VMIN] = 1;
	} else {
		params.c_cc[VTIME] = (timeout + 50) / 100;
		params.c_cc[VMIN] = 0;
	}

	err = tcsetattr(fd, TCSANOW, &params);
	if (err < 0) {
		log_error("tcsetattr failed: %m");
		return -1;
	}

	return 0;
}

int tty_close(int fd)
{
	if (fd < 0) {
		return -1;
	}

	tcflush(fd, TCIOFLUSH);
	return close(fd);
}

int atcmd_read(int fd, char *buf, size_t len)
{
	int err;

	err = read(fd, buf, len);
	if (!err) {
		log_notice("read timeout");
	} else if (err < 0) {
		log_error("read failed: %m");
	}

	return err;
}

int atcmd_read_until(int fd, char *buf, size_t len, const char *stop)
{
	int err;
	char c;
	ssize_t total = 0;
	size_t stop_len = strlen(stop);

	if (!stop_len) {
		BUG("specify a stop string");
	}

	while (1) {
		err = atcmd_read(fd, &c, 1);
		if (err <= 0) {
			buf[total] = '\0';
			return err;
		}

		if (total < len - 1) {
			buf[total++] = c;
		}

		if (stop_len <= total && c == stop[stop_len - 1] &&
			!memcmp(buf + (total - stop_len), stop, stop_len)) {
			buf[total] = '\0';
			break;
		}

		if (total >= len - 1) {
			buf[total] = '\0';
			log_notice("buffer exceeded before finding stop sequence");
			log_notice("buffer so far: %s", buf);
			return -1;
		}
	}

	debug_buffer("read:", buf, strlen(buf));

	return total;
}

int atcmd_readline(int fd, char *buf, size_t len)
{
	int tmp;

	tmp = atcmd_read_until(fd, buf, len, ATCMD_RESPONSE_EOL);
	if (tmp <= 0) {
		log_debug("atcmd_read_until failed with %d", tmp);
		return tmp;
	}

	buf = strpbrk(buf, ATCMD_RESPONSE_EOL);
	if (buf) {
		*buf = '\0';
	}

	return tmp;
}

int atcmd_expect_line(int fd, char *buf, size_t len, const char *expect)
{
	int tmp;

	while (1) {
		tmp = atcmd_readline(fd, buf, len);
		if (tmp <= 0) {
			*buf = '\0';
			log_debug("atcmd_readline failed");
			return tmp;
		}

		if (strstr(buf, expect)) {
			return tmp;
		} else if (!strcmp(buf, "OK")) {
			log_error("expected %s but got %s", expect, buf);
			return -1;
		} else if (!strcmp(buf, "ERROR")) {
			log_error("expected %s but got %s", expect, buf);
			return -1;
		} else if (!strncmp(buf, "+CMS ERROR: ", sizeof("+CMS ERROR: ") - 1) ||
				!strncmp(buf, "+CME ERROR: ", sizeof("+CME ERROR: ") - 1)) {
			log_error("expected %s but got %s", expect, buf);
			return -1;
		}
	}

	return -1;
}

int atcmd_write(int fd, const char *buf, size_t len)
{
	int err;

	debug_buffer("writing:", buf, len);

	err = full_write(fd, buf, len);
	if (err < 0) {
		log_error("full_write failed: %m");
	}
	return err;
}

int atcmd_write_str(int fd, const char *buf)
{
	return atcmd_write(fd, buf, strlen(buf));
}

int atcmd_vprintf(int fd, char *fmt, va_list ap)
{
	char *buf;
	int err;

	err = vasprintf(&buf, fmt, ap);
	if (err < 0) {
		log_error("out of memory");
		return err;
	}

	err = atcmd_write_str(fd, buf);
	if (err < 0) {
		log_debug("atcmd_write failed");
	}
	free(buf);

	return err;
}

int atcmd_printf(int fd, char *fmt, ...)
{
	va_list ap;
	int err;

	va_start(ap, fmt);
	err = atcmd_vprintf(fd, fmt, ap);
	if (err < 0) {
		log_debug("atcmd_write failed");
	}
	va_end(ap);

	return err;
}

int atcmd_writeline(int fd, char *fmt, ...)
{
	va_list ap;
	int err;
	int total = 0;

	va_start(ap, fmt);
	err = atcmd_vprintf(fd, fmt, ap);
	va_end(ap);
	if (err < 0) {
		log_debug("atcmd_vprintf failed");
		return err;
	}
	total += err;

	err = atcmd_write_str(fd, ATCMD_EOL);
	if (err < 0) {
		log_debug("atcmd_write_str failed");
		return err;
	}
	total += err;

	return total;
}

/**
 * atcmd_value_tok - Tokenize an AT compound value.
 * @str: The compound value to tokenize.
 *
 * Return: A pointer to the next token is returned and @str is updated for the next
 * call. Returns NULL on error or when there are no more tokens.
 *
 */
char *atcmd_value_tok(char **str)
{
	char *next;
	char *begin;

	begin = *str;
	if (!begin || !*begin) {
		return NULL;
	}

	begin += strspn(begin, " \t");

	switch (*begin) {
	case '\"':
		next = ++begin;

		next = strchr(next, '\"');
		if (!next) {
			log_notice("unterminated string");
			return NULL;
		}
		*next++ = '\0';

		next = strchr(next, ',');
		if (next) {
			*next++ = '\0';
		}

		break;
	case '(':
		next = ++begin;

		int count = 0;

		while (1) {
			switch (*next) {
			case ')':
				if (!count) {
					goto found;
				}
				count--;

				break;
			case '(':
				count++;

				break;
			case '\0':
				log_notice("unterminated group");
				return NULL;
			}

			next++;
		}

		found:

		*next++ = '\0';

		next = strchr(next, ',');
		if (next) {
			*next++ = '\0';
		}

		break;
	default:
		next = begin;

		next = strchr(next, ',');
		if (next) {
			*next++ = '\0';
		}

		strrstrip(begin);
	}

	*str = next;

	return begin;
}

/**
 * atcmd_response_brk - Break an AT command response info line in half.
 * @str: The response info line to break in half
 *
 * expected format: "<key>: <value>". <value> can be the empty string.
 *
 * Return: A pointer to <key> is returned. @str is set to <value>.
 *
 */
char *atcmd_response_brk(char **str)
{
	char *next;
	char *begin;

	begin = *str;
	if (!begin || !*begin) {
		return NULL;
	}

	begin += strspn(begin, " \t");

	next = begin;

	next = strstr(next, ": ");
	if (!next) {
		log_notice("separator \": \" not found");
		return NULL;
	}
	*next++ = '\0';
	next += strspn(next, " \t");

	*str = next;

	return begin;
}

int atcmd_e_write(int fd, int mode)
{
	char buf[ATCMD_LINE_SIZE];
	int tmp;

	atcmd_writeline(fd, "ATE%d", mode);
	tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK");
	if (tmp <= 0) {
		log_debug("expected OK but it was not received");
		return -1;
	}

	return 0;
}

int atcmd_v_write(int fd, int mode)
{
	char buf[ATCMD_LINE_SIZE];
	int tmp;

	atcmd_writeline(fd, "ATV%d", mode);
	tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK");
	if (tmp <= 0) {
		log_debug("expected OK but it was not received");
		return -1;
	}

	return 0;
}

int atcmd_q_write(int fd, int mode)
{
	char buf[ATCMD_LINE_SIZE];
	int tmp;

	atcmd_writeline(fd, "ATQ%d", mode);
	tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK");
	if (tmp <= 0) {
		log_debug("expected OK but it was not received");
		return -1;
	}

	return 0;
}

int atcmd_plus_cmgf_write(int fd, int mode)
{
	char buf[ATCMD_LINE_SIZE];
	int tmp;

	atcmd_writeline(fd, "AT+CMGF=%d", mode);
	tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK");
	if (tmp <= 0) {
		log_debug("expected OK but it was not received");
		return -1;
	}

	return 0;
}

int atcmd_plus_cmgw_write(int fd, const char *msg, size_t msg_len)
{
	char buf[ATCMD_LINE_SIZE];
	int tmp;
	int mem_index;

	atcmd_writeline(fd, "AT+CMGW=%d", msg_len);
	tmp = atcmd_read_until(fd, buf, sizeof(buf), "> ");
	if (tmp <= 0) {
		log_debug("expected > start sequence but it was not received");
		return -1;
	}

	tmp = atcmd_write(fd, msg, strlen(msg));
	tmp = atcmd_write_str(fd, CONTROL_Z_STR);

	tmp = atcmd_expect_line(fd, buf, sizeof(buf), "+CMGW: ");
	if (tmp <= 0) {
		log_debug("expected +CMGW: but it was not received");
		return -1;
	}

	mem_index = atoi(buf + strlen("+CMGW: "));

	tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK");
	if (tmp <= 0) {
		log_debug("expected OK but it was not received");
		return -1;
	}

	return mem_index;
}

int atcmd_plus_cmgw_write_text(int fd, const char *addr, int addr_type,
	const char *status, const char *msg, size_t msg_len)
{
	char buf[ATCMD_LINE_SIZE];
	int tmp;
	int mem_index;

	atcmd_printf(fd, "AT+CMGW");
	if (addr) {
		atcmd_printf(fd, "=\"%s\"", addr);
		if (addr_type != SMS_ADDR_UNSPEC) {
			atcmd_printf(fd, ",%d", addr_type);
			if (status) {
				atcmd_printf(fd, ",\"%s\"", status);
			}
		}
	}
	atcmd_write_str(fd, ATCMD_EOL);

	tmp = atcmd_read_until(fd, buf, sizeof(buf), "> ");
	if (tmp <= 0) {
		log_debug("expected > start sequence but it was not received");
		return -1;
	}

	tmp = atcmd_write(fd, msg, strlen(msg));
	tmp = atcmd_write_str(fd, CONTROL_Z_STR);

	tmp = atcmd_expect_line(fd, buf, sizeof(buf), "+CMGW: ");
	if (tmp <= 0) {
		log_debug("expected +CMGW: but it was not received");
		return -1;
	}

	mem_index = atoi(buf + strlen("+CMGW: "));

	tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK");
	if (tmp <= 0) {
		log_debug("expected OK but it was not received");
		return -1;
	}

	return mem_index;
}

int atcmd_plus_cmgs_write(int fd, const char *msg, size_t msg_len)
{
	char buf[ATCMD_LINE_SIZE];
	int tmp;
	int msg_ref;

	atcmd_writeline(fd, "AT+CMGS=%d", msg_len);
	tmp = atcmd_read_until(fd, buf, sizeof(buf), "> ");
	if (tmp <= 0) {
		log_debug("expected > start sequence but it was not received");
		return -1;
	}

	tmp = atcmd_write(fd, msg, strlen(msg));
	tmp = atcmd_write_str(fd, CONTROL_Z_STR);

	tmp = atcmd_expect_line(fd, buf, sizeof(buf), "+CMGS: ");
	if (tmp <= 0) {
		log_debug("expected +CMGS: but it was not received");
		return -1;
	}
	msg_ref = atoi(buf + strlen("+CMGS: "));

	tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK");
	if (tmp <= 0) {
		log_debug("expected OK but it was not received");
		return -1;
	}

	return msg_ref;
}

int atcmd_plus_cmgs_write_text(int fd, const char *addr, const char *msg, size_t msg_len)
{
	char buf[ATCMD_LINE_SIZE];
	int tmp;
	int msg_ref;

	atcmd_writeline(fd, "AT+CMGS=\"%s\"", addr);
	tmp = atcmd_read_until(fd, buf, sizeof(buf), "> ");
	if (tmp <= 0) {
		log_debug("expected > start sequence but it was not received");
		return -1;
	}

	tmp = atcmd_write(fd, msg, strlen(msg));
	tmp = atcmd_write_str(fd, CONTROL_Z_STR);

	tmp = atcmd_expect_line(fd, buf, sizeof(buf), "+CMGS: ");
	if (tmp <= 0) {
		log_debug("expected +CMGS: but it was not received");
		return -1;
	}
	msg_ref = atoi(buf + strlen("+CMGS: "));

	tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK");
	if (tmp <= 0) {
		log_debug("expected OK but it was not received");
		return -1;
	}

	return msg_ref;
}

int atcmd_plus_cmss_write(int fd, int index, const char *addr, int addr_type)
{
	char buf[ATCMD_LINE_SIZE];
	int tmp;
	int msg_ref;

	atcmd_printf(fd, "AT+CMSS=%d", index);
	if (addr) {
		atcmd_printf(fd, ",\"%s\"", addr);
		if (addr_type != SMS_ADDR_UNSPEC) {
			atcmd_printf(fd, ",%d", addr_type);
		}
	}
	atcmd_write_str(fd, ATCMD_EOL);

	tmp = atcmd_expect_line(fd, buf, sizeof(buf), "+CMSS: ");
	if (tmp <= 0) {
		log_debug("expected +CMSS: but it was not received");
		return -1;
	}

	msg_ref = atoi(buf + strlen("+CMSS: "));

	tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK");
	if (tmp <= 0) {
		log_debug("expected OK but it was not received");
		return -1;
	}

	return msg_ref;
}

int atcmd_plus_cmgd_write(int fd, int index)
{
	char buf[ATCMD_LINE_SIZE];
	int tmp;

	atcmd_writeline(fd, "AT+CMGD=%d", index);
	tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK");
	if (tmp <= 0) {
		log_debug("expected OK but it was not received");
		return -1;
	}

	return 0;
}

int atcmd_plus_qcfg_write(int fd, int sms_format)
{
	char buf[ATCMD_LINE_SIZE];
	int tmp;

	atcmd_writeline(fd, "AT+QCFG=\"ltesms/format\",%d", sms_format);
	tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK");
	if (tmp <= 0) {
		log_debug("expected OK but it was not received");
		return -1;
	}

	return 0;
}

int atcmd_response_foreach_line(int fd, atcmd_response_callback_t call, void *prv)
{
	char buf[ATCMD_LINE_SIZE];
	int tmp;

	while (1) {
		tmp = atcmd_readline(fd, buf, sizeof(buf));
		if (tmp <= 0) {
			return -1;
		}

		tmp = call(buf, strlen(buf), prv);
		if (tmp) {
			return tmp;
		}
	}

	return -1;
}

int atcmd_plus_cpms_read(int fd, struct data_store *read_store,
	struct data_store *send_store, struct data_store *new_store, const char *model)
{
	char buf[ATCMD_LINE_SIZE];
	char *save;
	char *token;
	int tmp;
	int i;
  int data_stores_size;

	struct data_store *store;
  struct data_store *data_stores[] = {read_store, send_store, new_store};

  if (isCdmaTypeModel()) {
    data_stores_size = 2;
  }
  else {
    data_stores_size = 3;
  }

	atcmd_writeline(fd, "AT+CPMS?");
	tmp = atcmd_expect_line(fd, buf, sizeof(buf), "+CPMS: ");
	if (tmp <= 0) {
		log_debug("expected +CPMS: but it was not received");
		return -1;
	}

	save = buf;
	token = atcmd_response_brk(&save);
	if (!token) {
		log_debug("atcmd_response_brk failed");
		return -1;
	}

	for (i = 0; i < data_stores_size; i++) {
		store = data_stores[i];

		token = atcmd_value_tok(&save);
		if (!token) {
			return -1;
		}
		strncpy(store->name, token, STORE_NAME_LEN);

		token = atcmd_value_tok(&save);
		if (!token) {
			return -1;
		}
		store->used = atoi(token);

		token = atcmd_value_tok(&save);
		if (!token) {
			return -1;
		}
		store->max = atoi(token);

		log_debug("name[%d]: %s", i, store->name);
		log_debug("used[%d]: %d", i, store->used);
		log_debug("max[%d]: %d", i, store->max);
	}

	tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK");
	if (tmp <= 0) {
		log_debug("expected OK but it was not received");
		return -1;
	}

	return 0;
}

int atcmd_plus_cpms_test(int fd, struct store_locations *read_loc,
	struct store_locations *send_loc, struct store_locations *new_loc)
{
	char buf[ATCMD_LINE_SIZE];
	char *save;
	char *token;
	char *choices;
	int tmp;
	int i, j;

  struct store_locations *locations[] = {read_loc, send_loc, new_loc};
	struct store_locations *loc;

	atcmd_writeline(fd, "AT+CPMS=?");
	tmp = atcmd_expect_line(fd, buf, sizeof(buf), "+CPMS: ");
	if (tmp <= 0) {
		log_debug("expected +CPMS: but it was not received");
		return -1;
	}

	save = buf;
	token = atcmd_response_brk(&save);
	if (!token) {
		log_debug("atcmd_response_brk failed");
		return -1;
	}

	for (i = 0; i < ARRAY_SIZE(locations); i++) {
		loc = locations[i];

		token = atcmd_value_tok(&save);
		if (!token) {
			break;
		}
		if (i == 0 && *token == '(') {
			save = token;
			token = atcmd_value_tok(&save);
			if (!token) {
				break;
			}
		}

		choices = token;
		for (j = 0; j < STORE_LOCATIONS_MAX; j++) {
			token = atcmd_value_tok(&choices);
			if (!token) {
				break;
			}

			strncpy(loc->names[j], token, STORE_NAME_LEN);

			log_debug("loc[%d] choice[%d]: %s", i, j, token);

			loc->nr_locations++;
		}
	}

	tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK");
	if (tmp <= 0) {
		log_debug("expected OK but it was not received");
		return -1;
	}

	return 0;
}

int atcmd_plus_cpms_write(int fd, const char *read_name,
		const char *send_name, const char *new_name, const char *model)
{
	char buf[ATCMD_LINE_SIZE];
	int tmp;

  if (isCdmaTypeModel()) {
    atcmd_writeline(fd, "AT+CPMS=\"%s\",\"%s\"", read_name, send_name);
  }
  else
  {
    atcmd_writeline(fd, "AT+CPMS=\"%s\",\"%s\",\"%s\"",
          read_name, send_name, new_name);
  }
	tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK");
	if (tmp <= 0) {
		log_debug("expected OK but it was not received");
		return -1;
	}

	return 0;
}

int atcmd_plus_cpbs_read(int fd, struct data_store *store)
{
	char buf[ATCMD_LINE_SIZE];
	char *save;
	char *token;
	int tmp;

	atcmd_writeline(fd, "AT+CPBS?");
	tmp = atcmd_expect_line(fd, buf, sizeof(buf), "+CPBS: ");
	if (tmp <= 0) {
		log_debug("expected +CPBS: but it was not received");
		return -1;
	}

	save = buf;
	token = atcmd_response_brk(&save);
	if (!token) {
		log_debug("atcmd_response_brk failed");
		return -1;
	}

	token = atcmd_value_tok(&save);
	if (!token) {
		log_debug("atcmd_value_tok name");
		return -1;
	}
	strncpy(store->name, token, STORE_NAME_LEN);

	token = atcmd_value_tok(&save);
	if (!token) {
		log_debug("atcmd_value_tok used");
		return -1;
	}
	store->used = atoi(token);

	token = atcmd_value_tok(&save);
	if (!token) {
		log_debug("atcmd_value_tok max");
		return -1;
	}
	store->max = atoi(token);

	log_debug("name: %s", store->name);
	log_debug("used: %d", store->used);
	log_debug("max: %d", store->max);

	tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK");
	if (tmp <= 0) {
		log_debug("expected OK but it was not received");
		return -1;
	}

	return 0;
}

int atcmd_plus_cpbs_test(int fd, struct store_locations *loc)
{
	char buf[ATCMD_LINE_SIZE];
	char *save;
	char *token;
	char *choices;
	int tmp;
	int i;

	atcmd_writeline(fd, "AT+CPBS=?");
	tmp = atcmd_expect_line(fd, buf, sizeof(buf), "+CPBS: ");
	if (tmp <= 0) {
		log_debug("expected +CPBS: but it was not received");
		return -1;
	}

	save = buf;
	token = atcmd_response_brk(&save);
	if (!token) {
		log_debug("atcmd_response_brk failed");
		return -1;
	}

	token = atcmd_value_tok(&save);
	if (!token) {
		log_debug("atcmd_value_tok group");
		return -1;
	}

	choices = token;
	for (i = 0; i < STORE_LOCATIONS_MAX; i++) {
		token = atcmd_value_tok(&choices);
		if (!token) {
			break;
		}

		strncpy(loc->names[i], token, STORE_NAME_LEN);

		log_debug("choice[%d]: %s", i, token);

		loc->nr_locations++;
	}

	tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK");
	if (tmp <= 0) {
		log_debug("expected OK but it was not received");
		return -1;
	}

	return 0;
}

int atcmd_plus_cpbs_write(int fd, const char *name)
{
	char buf[ATCMD_LINE_SIZE];
	int tmp;

	atcmd_writeline(fd, "AT+CPBS=\"%s\"", name);
	tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK");
	if (tmp <= 0) {
		log_debug("expected OK but it was not received");
		return -1;
	}

	return 0;
}

int atcmd_plus_cpbr_test(int fd, struct phonebook_store *store)
{
	char buf[ATCMD_LINE_SIZE];
	char *save;
	char *range;
	char *token;
	int tmp;

	atcmd_writeline(fd, "AT+CPBR=?");
	tmp = atcmd_expect_line(fd, buf, sizeof(buf), "+CPBR: ");
	if (tmp <= 0) {
		log_debug("expected +CPBR: but it was not received");
		return -1;
	}

	save = buf;
	token = atcmd_response_brk(&save);
	if (!token) {
		log_debug("atcmd_response_brk failed");
		return -1;
	}

	token = atcmd_value_tok(&save);
	if (!token) {
		log_debug("atcmd_value_tok start");
		return -1;
	}

	range = token;
	token = atcmd_value_tok(&range);
	if (!token) {
		log_debug("atcmd_value_tok range");
		return -1;
	}

	range = strchr(token, '-');
	if (!range) {
		log_debug("break range");
		return -1;
	}
	store->index_min = atoi(token);
	store->index_max = atoi(range + 1);

	token = atcmd_value_tok(&save);
	if (!token) {
		log_debug("atcmd_value_tok addr_max");
		return -1;
	}
	store->addr_max = atoi(token);

	token = atcmd_value_tok(&save);
	if (!token) {
		log_debug("atcmd_value_tok name_max");
		return -1;
	}
	store->name_max = atoi(token);

	log_debug("index_min: %d", store->index_min);
	log_debug("index_max: %d", store->index_max);
	log_debug("addr_max: %d", store->addr_max);
	log_debug("name_max: %d", store->name_max);

	tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK");
	if (tmp <= 0) {
		log_debug("expected OK but it was not received");
		return -1;
	}

	return 0;
}

int atcmd_plus_gmm_read(int fd)
{
	char buf[ATCMD_LINE_SIZE];
	char *save;
	char *token;
	int tmp;

	atcmd_writeline(fd, "AT+GMM");
  //Swallow extra "\r\n"
  tmp = atcmd_readline(fd, buf, sizeof(buf));
	if (tmp <= 0) {
		log_debug("expected \\r\\n but it was not received");
		return -1;
	}

  //Read model string
  tmp = atcmd_readline(fd, buf, sizeof(buf));
	if (tmp <= 0) {
		log_debug("expected model but it was not received");
		return -1;
	}

	save = buf;
	token = atcmd_value_tok(&save);
	if (!token) {
		log_debug("atcmd_value_tok model");
		return -1;
	}
	strncpy(Global.core.model, token, MODEL_LEN);

	log_debug("model: %s", Global.core.model);

	tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK");
	if (tmp <= 0) {
		log_debug("expected OK but it was not received");
		return -1;
	}

	return 0;
}

/* Manufacturer identification is needed to pick proper manufacturer-specific commands.
 * For example, in atcmd_plus_iccid_read() function. */
int atcmd_plus_gmi_read(int fd)
{
	char buf[ATCMD_LINE_SIZE];
	char *save;
	char *token;
	int tmp;

	atcmd_writeline(fd, "AT+GMI");
	//Swallow extra "\r\n"
	tmp = atcmd_readline(fd, buf, sizeof(buf));
	if (tmp <= 0) {
		log_debug("expected \\r\\n but it was not received");
		return -1;
	}

	//Read manufacturer string
	tmp = atcmd_readline(fd, buf, sizeof(buf));
	if (tmp <= 0) {
		log_debug("expected manufacturer but it was not received");
		return -1;
	}

	save = buf;
	token = atcmd_value_tok(&save);
	if (!token) {
		log_debug("atcmd_value_tok manufacturer");
		return -1;
	}
	strncpy(Global.core.manufacturer, token, MANUFACTURER_LEN);

	log_debug("manufacturer: %s", Global.core.manufacturer);

	tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK");
	if (tmp <= 0) {
		log_debug("expected OK but it was not received");
		return -1;
	}

	return 0;
}

/* ICCID needed for LNA3 to determine carrier */
int atcmd_plus_iccid_read(int fd)
{
	char buf[ATCMD_LINE_SIZE];
	char *save;
	char *token;
	int tmp;
	char *tmp_buf;
	char* command;

	if (is_telit_model()) {
		command = "AT#CCID";
	} else if (is_quectel_model()) {
		command = "AT+QCCID";
	} else {
		log_error("unsupported modem manufacturer, unable to detect ICCID");
		return -1;
	}

	atcmd_writeline(fd, command);
    //Swallow extra "\r\n"
    tmp = atcmd_readline(fd, buf, sizeof(buf));
	if (tmp <= 0) {
		log_debug("expected \\r\\n but it was not received");
		return -1;
	}

    //Read iccid string
    tmp = atcmd_readline(fd, buf, sizeof(buf));
	if (tmp <= 0) {
		log_debug("expected ICCID but it was not received");
                /* Currently only LNA3 models will need the ICCID */
		return -1;
	}

	save = buf;
	log_debug("buf=%s for %d", buf,strlen(buf));
	token = atcmd_value_tok(&save);
	if (!token) {
		log_debug("atcmd_value_tok model");
		return -1;
	} else {
		log_debug("token is %s",token);
	}
    tmp_buf = strrchr(token,' ');
	if (tmp_buf) {
		token = ++tmp_buf;
		log_debug("Found blank, incrementing tmp");
	} else {
		tmp_buf = strrchr(token,'\t');
		log_debug("Found tab, incrementing tmp");
		if(tmp_buf)
			token = ++tmp_buf;
	}
	log_debug("token[0]=%2.2x token[1]=%2.2x",token[0],token[1]);
	strncpy(Global.core.iccid, token, ICCID_LEN);

	log_debug("iccid: %s", Global.core.iccid);

	tmp = atcmd_expect_line(fd, buf, sizeof(buf), "OK");
	if (tmp <= 0) {
		log_debug("expected OK but it was not received");
		return -1;
	}

	return 0;
}

int atcmd_init(int fd, int read_timeout)
{
	int tmp;

	tmp = atcmd_q_write(fd, 0);
	if (tmp < 0) {
		return tmp;
	}

	tmp = atcmd_v_write(fd, 1);
	if (tmp < 0) {
		return tmp;
	}

	tmp = atcmd_e_write(fd, 0);
	if (tmp < 0) {
		return tmp;
	}

	return 0;
}

static int msg_store_choice(struct msg_store *store, const char *name)
{
	int i;

	for (i = 0; i < store->choices.nr_locations; i++) {
		if(!strcmp(store->choices.names[i], name)) {
			return true;
		}
	}

	return false;
}

static int sms_atcmd_init(int fd)
{
	int tmp;

	struct msg_store read_store;
	struct msg_store send_store;
	struct msg_store new_store;

	tmp = atcmd_init(fd, Global.core.read_timeout);
	if (tmp < 0) {
		return -1;
	}

	tmp = atcmd_plus_gmm_read(fd);
	if (tmp < 0) {
    log_error("failed to read radio model");
		return -1;
	}

	tmp = atcmd_plus_gmi_read(fd);
	if (tmp < 0) {
	log_error("failed to read radio manufacturer");
		return -1;
	}

    tmp = atcmd_plus_iccid_read(fd);
	if (tmp < 0) {
    log_error("failed to read SIM ICCID");
		return -1;
	}

	tmp = atcmd_plus_cmgf_write(fd, SMS_PDU_MODE);
	if (tmp < 0) {
		return -1;
	}

	memset(&read_store, 0, sizeof(read_store));
	memset(&send_store, 0, sizeof(send_store));
	memset(&new_store, 0, sizeof(new_store));

  if (!strcmp(Global.core.model, "CE910-DUAL") || !strcmp(Global.core.model, "DE910-DUAL")) {
    log_info("Changing message storage locations to ME for CE/DE910-DUAL");
    free(Global.core.msg_store_read);
    free(Global.core.msg_store_send);
    free(Global.core.pb_store);
    Global.core.msg_store_read = strdup("ME");
    Global.core.msg_store_send = strdup("ME");
    Global.core.pb_store = strdup("ME");
  }

	atcmd_plus_cpms_test(fd, &read_store.choices,
			&send_store.choices, &new_store.choices);
	atcmd_plus_cpms_read(fd, &read_store.selected,
			&send_store.selected, &new_store.selected, Global.core.model);
	if (Global.core.msg_store_read && Global.core.msg_store_send &&
		Global.core.msg_store_new) {

		if (!msg_store_choice(&read_store, Global.core.msg_store_read)) {
			log_error("message storage location %s is not a choice",
					Global.core.msg_store_read);
			return -1;
		}
		if (!msg_store_choice(&send_store, Global.core.msg_store_send)) {
			log_error("message storage location %s is not a choice",
					Global.core.msg_store_send);
			return -1;
		}

    //Doesn't apply to CDMA type radios
		if (!isCdmaTypeModel() && !msg_store_choice(&new_store, Global.core.msg_store_new)) {
			log_error("message storage location %s is not a choice",
					Global.core.msg_store_new);
			return -1;
		}

		tmp = atcmd_plus_cpms_write(fd, Global.core.msg_store_read,
				Global.core.msg_store_send, Global.core.msg_store_new, Global.core.model);
		if (tmp < 0) {
			return -1;
		}
	}

	return 0;
}

static int lock_file;

int sms_device_close(int fd)
{
	int ret;

	ret = tty_close(fd);

	lock_file = device_unlock(lock_file);

	return ret;
}

int sms_device_open(void)
{
	int fd = -1;
	int tmp;

	if (!Global.core.device) {
		log_error("device is not set");
		return -1;
	}

	lock_file = device_lock(Global.core.device);
	if (lock_file < 0) {
		sms_device_close(fd);
		return -1;
	}

	fd = tty_open(Global.core.device, Global.core.baud_rate);
	if (fd < 0) {
		sms_device_close(fd);
		return -1;
	}

	tmp = tty_set_read_timeout(fd, Global.core.read_timeout);
	if (tmp < 0) {
		sms_device_close(fd);
		return tmp;
	}

	if (Global.core.sms_init) {
		tmp = sms_atcmd_init(fd);
		if (tmp < 0) {
			sms_device_close(fd);
			return tmp;
		}
	}

	return fd;
}

int is_vzw_lte(void)
{
  if (!strcmp(Global.core.model, "LE910-NA1")) {
        log_debug("Found LE910-NA1");
        /* Verizon Wireless SIM */
  	if (strncmp(Global.core.iccid,"891480",6) == 0) {
                log_debug("Found VZW SIM");
      		return 1;
        }
  }

  return (!strncmp(Global.core.model, "LE910-SVG", MODEL_LEN) ||
          !strncmp(Global.core.model, "LE910-SV1", MODEL_LEN));
}

/* Detect LNA7/L4G1 radio with Verizon SIM */
int is_quectel_dual_sms_format(void)
{
	if (!strncmp(Global.core.model, "EG95", MODEL_LEN) ||
	    !strncmp(Global.core.model, "EG25", MODEL_LEN))
	{
		log_debug("Found Quectel radio with dual SMS format support");
		/* Verizon Wireless SIM */
		if (strncmp(Global.core.iccid,"891480",6) == 0) {
			log_debug("Found VZW SIM");
			return 1;
		}
	}
	return 0;
}

int isCdmaTypeModel()
{
  /* Test for possible dual firmware model */
  if (is_vzw_lte())
  	return 1;

  return (!strncmp(Global.core.model, "DE910-DUAL", MODEL_LEN) ||
          !strncmp(Global.core.model, "CE910-DUAL", MODEL_LEN));
}

int is_telit_model()
{
	if (!strncmp(Global.core.manufacturer, "Telit", MANUFACTURER_LEN)) {
		log_debug("Found Telit model");
		return 1;
	}

	return 0;
}

int is_quectel_model()
{
	if (!strncmp(Global.core.manufacturer, "Quectel", MANUFACTURER_LEN)) {
		log_debug("Found Quectel model");
		return 1;
	}

	return 0;
}