/*
 * SMS List Messages
 *
 * 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
 *
 */

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

#include "global.h"
#include "cmd_options.h"
#include "sms_utils.h"
#include "list.h"
#include "sms_list.h"
#include "pdu_decode.h"
#include "utils.h"
#include "atcmd.h"

#define CMGL_HEADER_START "+CMGL: "
#define CMGR_HEADER_START "+CMGR: "

static int process_header(struct msg_list_info *list_info, char *buf, size_t len)
{
	char *save = buf;
	char *token;

	struct sms_msg *msg;
	int index;
	int status;
	char *name;
	int msg_len;

	token = atcmd_response_brk(&save);
	if (!token) {
		log_debug("response tokenizing failed at start");
		return -1;
	}

	switch (list_info->cmd_type) {
	case CMD_TYPE_CMGL:
		token = atcmd_value_tok(&save);
		if (!token) {
			log_debug("response tokenizing failed at index");
			return -2;
		}
		index = atoi(token);

		break;
	case CMD_TYPE_CMGR:
		index = list_info->index;
		break;
	default:
		return false;
	}

	token = atcmd_value_tok(&save);
	if (!token) {
		log_debug("response tokenizing failed at msg-status");
		return -3;
	}
	status = atoi(token);

	token = atcmd_value_tok(&save);
	if (!token) {
		log_debug("response tokenizing failed at name");
		return -4;
	}
	name = token;

	token = atcmd_value_tok(&save);
	if (!token) {
		log_debug("response tokenizing failed at msg-len");
		return -5;
	}
	msg_len = atoi(token);

	switch (status) {
	case SMS_MSG_REC_UNREAD:
		list_info->nr_unread++;
		break;
	case SMS_MSG_REC_READ:
		list_info->nr_read++;
		break;
	case SMS_MSG_STO_UNSENT:
		list_info->nr_unsent++;
		break;
	case SMS_MSG_STO_SENT:
		list_info->nr_sent++;
		break;
	default:
		log_warning("msg-status unknown");
		list_info->nr_unknown++;
	}

	msg = sms_msg_alloc();
	if (!msg) {
		log_error("out of memory");
		return -6;
	}

	memset(msg, 0, sizeof(*msg));

	msg->index = index;
	msg->status = status;
	snprintf(msg->name, sizeof(msg->name), "%s", name);
	msg->len = msg_len;

	list_add_tail(&msg->list, &list_info->msg_list);

	list_info->nr_msgs++;

	return 0;
}

static int print_list_yaml(struct msg_list_info *list_info)
{
	struct sms_msg *msg;
	char buf[64];
	int indent;

	indent = 0;

	indentf(indent, "---\n");
	indentf(indent, "\n");

	indentf(indent, "messages:\n");

	list_for_each_entry(msg, &list_info->msg_list, list) {
		indent = YAML_INDENT;

		indentf(indent, "- index: %d\n", msg->index);
		indent += YAML_INDENT;

		indentf(indent, "message-status: %d\n", msg->status);
		indentf(indent, "name: %s\n", msg->name);
		if (Global.core.verbose) {
			indentf(indent, "length: %d\n", msg->len);
		}

		indentf(indent, "pdu:\n");
		indent += YAML_INDENT;

		if (Global.core.verbose) {
			indentf(indent, "message-length: %d\n", msg->pdu.msg_len);
		}

		if (Global.core.verbose) {
			indentf(indent, "smsc-addr-length: 0x%02X\n", msg->pdu.smsc_addr.len);
		}
		indentf(indent, "smsc-addr-type: 0x%02X\n", msg->pdu.smsc_addr.type);
		indentf(indent, "smsc-addr: %s\n", msg->pdu.smsc_addr.addr);

		indentf(indent, "type: 0x%02X\n", msg->pdu.type.type);
		indentf(indent, "message-reference: 0x%02X\n", msg->pdu.msg_reference);
		indentf(indent, "protocol-id: 0x%02X\n", msg->pdu.protocol_id);

		if (Global.core.verbose) {
			indentf(indent, "addr-length: 0x%02X\n", msg->pdu.addr.len);
		}
		indentf(indent, "addr-type: 0x%02X\n", msg->pdu.addr.type);
		indentf(indent, "addr: %s\n", msg->pdu.addr.addr);

		pdu_format_vp(&msg->pdu, buf, sizeof(buf));
		indentf(indent, "validity-period: %s\n", buf);
		pdu_format_timestamp(&msg->pdu, buf, sizeof(buf), "%Y-%m-%d %T %z");
		indentf(indent, "timestamp: %s\n", buf);

		if (Global.core.verbose) {
			indentf(indent, "user-data-length: 0x%02X\n", msg->pdu.user_data_len);
		}
		indentf(indent, "user-data: \"%.*J\"\n", msg->pdu.user_data_len, msg->pdu.user_data);
		indentf(indent, "\n");
	}

	indent = 0;

	if (Global.core.verbose) {
		indentf(indent, "\n");

		indentf(indent, "statistics:\n");
		indent += YAML_INDENT;

		indentf(indent, "unread: %d\n", list_info->nr_unread);
		indentf(indent, "read: %d\n", list_info->nr_read);
		indentf(indent, "unsent: %d\n", list_info->nr_unsent);
		indentf(indent, "sent: %d\n", list_info->nr_sent);
		indentf(indent, "unknown: %d\n", list_info->nr_unknown);
		indentf(indent, "total: %d\n", list_info->nr_msgs);
		indentf(indent, "\n");
	}

	indent = 0;

	indentf(indent, "...\n");

	return 1;
}

enum {
	LIST_STATE_BLANK,
	LIST_STATE_OPEN,
	LIST_STATE_PDU,
	LIST_STATE_CLOSE,
};

static int list_info_callback(char *buf, size_t len, void *data)
{
	int err;
	struct msg_list_info *list_info = (struct msg_list_info *) data;
	struct sms_msg *msg;

	list_info->nr_lines++;

	switch (list_info->state) {
	case LIST_STATE_BLANK:
		log_debug("state: LIST_STATE_BLANK");

		if (*buf) {
			list_info->state = LIST_STATE_CLOSE;
			log_debug("AT response error: %s", buf);
			return -1;
		}

		list_info->state = LIST_STATE_OPEN;

		return 0;

	case LIST_STATE_OPEN:
		log_debug("state: LIST_STATE_OPEN");

		if (!strncmp(buf, CMGL_HEADER_START, strlen(CMGL_HEADER_START)) ||
		    !strncmp(buf, CMGR_HEADER_START, strlen(CMGR_HEADER_START))) {
			list_info->state = LIST_STATE_PDU;

			return process_header(list_info, buf, len);
		} else if (!strcmp(buf, "OK")) {
			list_info->state = LIST_STATE_CLOSE;
			return 1;
		} else if (*buf) {
			list_info->state = LIST_STATE_CLOSE;

			log_debug("AT response error: %s", buf);

			return -1;
		} else {
			return 0;
		}

	case LIST_STATE_PDU:
		log_debug("state: LIST_STATE_PDU");

		if (list_empty(&list_info->msg_list)) {
			log_debug("empty list");
			return -1;
		}

		msg = list_entry(list_info->msg_list.prev, typeof(*msg), list);
		if (msg->len > 0) {
			err = pdu_decode(buf, &msg->pdu);
			if (err < 0) {
				log_warning("pdu decode failed: %d", err);
			}
		}

		list_info->state = LIST_STATE_OPEN;

		return 0;

	default:
		return -1;
	}

	return -1;
}

void sms_list_free(struct msg_list_info *list_info)
{
	struct sms_msg *msg;
	struct sms_msg *msg_save;

	list_for_each_entry_safe(msg, msg_save, &list_info->msg_list, list) {
		list_del(&msg->list);
		sms_msg_free(msg);
	}
}

int sms_list_get(int fd, struct msg_list_info *list_info)
{
	int tmp;

	INIT_LIST_HEAD(&list_info->msg_list);

	list_info->state = LIST_STATE_BLANK;
	list_info->nr_lines = 0;
	list_info->nr_unread = 0;
	list_info->nr_read = 0;
	list_info->nr_unsent = 0;
	list_info->nr_sent = 0;
	list_info->nr_unknown = 0;
	list_info->nr_msgs = 0;

	switch (list_info->cmd_type) {
	case CMD_TYPE_CMGL:
		atcmd_writeline(fd, "AT+CMGL=%d", list_info->status);
		break;

	case CMD_TYPE_CMGR:
		atcmd_writeline(fd, "AT+CMGR=%d", list_info->index);
		break;

	default:
		return -1;
	}

	tmp = atcmd_response_foreach_line(fd, list_info_callback, list_info);
	if (tmp < 0) {
		log_error("listing failed");
		sms_list_free(list_info);

		return tmp;
	}

	return 0;
}

static int do_list(int fd, int cmd_type, int argc, char **argv)
{
	int tmp;
	char *arg;
	struct msg_list_info list_info;

	memset(&list_info, 0, sizeof(list_info));

	if (argc < 1) {
		log_error("msg-status or index expected");
		return false;
	}
	arg = *argv;
	argc--; argv++;

	list_info.cmd_type = cmd_type;

	switch (list_info.cmd_type) {
	case CMD_TYPE_CMGL:
		if (!strcmp(arg, "unread")) {
			list_info.status = SMS_MSG_REC_UNREAD;
		} else if (!strcmp(arg, "read")) {
			list_info.status = SMS_MSG_REC_READ;
		} else if (!strcmp(arg, "unsent")) {
			list_info.status = SMS_MSG_STO_UNSENT;
		} else if (!strcmp(arg, "sent")) {
			list_info.status = SMS_MSG_STO_SENT;
		} else if (!strcmp(arg, "all")) {
			list_info.status = SMS_MSG_ALL;
		} else {
			log_error("invalid msg-status %s", arg);
			return false;
		}

		break;

	case CMD_TYPE_CMGR:
		list_info.index = atoi(arg);
		break;

	default:
		return false;
	}

	tmp = sms_list_get(fd, &list_info);
	if (tmp < 0) {
		log_error("failed to get msg list");
		return false;
	}

	print_list_yaml(&list_info);

	sms_list_free(&list_info);

	return true;
}

static char *short_options = "";
static struct option long_options[] = {
	{NULL, 0, NULL, 0},
};

void sms_list_help(FILE *out) {
	fprintf(out, "usage: list STATUS\n");
	fprintf(out, "where  STATUS := { \n");
	fprintf(out, "         unread\n");
	fprintf(out, "         read\n");
	fprintf(out, "         unsent\n");
	fprintf(out, "         sent\n");
	fprintf(out, "         all\n");
	fprintf(out, "       }\n");
	fprintf(out, "\n");
}

void sms_read_help(FILE *out) {
	fprintf(out, "usage: read <index>\n");
	fprintf(out, "\n");
}

static void help_select(FILE *out, int cmd_type)
{
	switch (cmd_type) {
	case CMD_TYPE_CMGL:
		sms_list_help(out);
		break;
	case CMD_TYPE_CMGR:
		sms_read_help(out);
		break;
	default:
		fprintf(out, "usage: ?\n");
	}
}

int sms_list(int argc, char **argv)
{
	int i;
	int option_index;
	int ret;
	int fd;

	if (argc < 1) {
		log_debug("should have received at least one argument");
		return false;
	}

	char *object = *argv;
	int cmd_type;

	if (!tokcmp(object, "list")) {
		cmd_type = CMD_TYPE_CMGL;
	} else if (!tokcmp(object, "read")) {
		cmd_type = CMD_TYPE_CMGR;
	} else {
		log_debug("object should be one of { list | read }");
		return false;
	}

	while ((i = getopt_long(argc, argv, short_options, long_options, &option_index)) >= 0) {
		switch (i) {
		case 0:
			break;

		default:
			help_select(stderr, cmd_type);
			return false;
		}
	}

	if (optind >= argc) {
		help_select(stderr, cmd_type);
		return false;
	}
	argc -= optind;
	argv += optind;

	fd = sms_device_open();
	if (fd < 0) {
		return false;
	}

	ret = do_list(fd, cmd_type, argc, argv);

	sms_device_close(fd);

	return ret;
}