/*
 * 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)
{
	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)) {
			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");

	if (*begin == '\"') {
		next = ++begin;

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

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

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

		next = strchr(next, ',');
		if (next) {
			*next++ = '\0';
		}
	} else {
		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_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_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_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_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)
{
	char buf[ATCMD_LINE_SIZE];
	char *save;
	char *token;
	int tmp;
	int i;

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

	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(data_stores); 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;
		}

		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)
{
	char buf[ATCMD_LINE_SIZE];
	int tmp;

	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_init(int fd, int read_timeout)
{
	int 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 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_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));

	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);
	if (Global.core.msg_store_read && Global.core.msg_store_send &&
		Global.core.msg_store_new) {
		tmp = atcmd_plus_cpms_write(fd, Global.core.msg_store_read,
				Global.core.msg_store_send, Global.core.msg_store_new);
		if (tmp < 0) {
			return -1;
		}
	}

	return 0;
}

int sms_device_open(void)
{
	int fd;
	int tmp;

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

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

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

	return fd;
}