/*
 * Phonebook
 *
 * 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 <string.h>

#include "global.h"
#include "cmd_options.h"
#include "phonebook.h"
#include "utils.h"
#include "atcmd.h"

void phonebook_entry_free(struct phonebook_entry *entry)
{
	free(entry);
}

struct phonebook_entry *phonebook_entry_alloc(void)
{
	return malloc(sizeof(struct phonebook_entry));
}

struct phonebook_entry *phonebook_find_by_name(struct list_head *head,
		const char *name)
{
	struct phonebook_entry *entry;

	list_for_each_entry(entry, head, list) {
		if (!strcmp(entry->name, name)) {
			return entry;
		}
	}

	return NULL;
}

struct phonebook_entry *phonebook_search_names_i(struct list_head *head,
		const char *name)
{
	int tmp;
	struct phonebook_entry *entry;
	int matches_found = 0;

	list_for_each_entry(entry, head, list) {
		if (strstr(entry->name, name)) {
			matches_found++;

			tmp = user_yesno(USER_YESNO_YES, "Select contact '%s' (%s)",
					entry->name, entry->addr);
			if (tmp == USER_YESNO_YES) {
				return entry;
			}
		}
	}

	if (!matches_found) {
		log_notice("no matches for %s found in phonebook", name);
	}

	return NULL;
}

#define CPBR_HEADER_START "+CPBR: "

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

	struct phonebook_entry *entry;
	int index;
	char *addr;
	int type;
	char *name;

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

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

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

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

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

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

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

	entry->index = index;
	snprintf(entry->addr, sizeof(entry->addr), "%s", addr);
	entry->type = type;
	snprintf(entry->name, sizeof(entry->name), "%s", name);

	list_add_tail(&entry->list, &list_info->entry_list);

	list_info->nr_entries++;

	return 0;
}

static int print_list_yaml(struct phonebook_list_info *list_info)
{
	struct phonebook_entry *entry;
	int indent;

	indent = 0;

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

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

	list_for_each_entry(entry, &list_info->entry_list, list) {
		indent = YAML_INDENT;

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

		indentf(indent, "addr: %s\n", entry->addr);
		indentf(indent, "type: 0x%02X\n", entry->type);
		indentf(indent, "name: %s\n", entry->name);
	}

	indent = 0;

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

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

		indentf(indent, "total: %d\n", list_info->nr_entries);
		indentf(indent, "\n");
	}

	indent = 0;

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

	return 1;
}

enum {
	LIST_STATE_BLANK,
	LIST_STATE_OPEN,
	LIST_STATE_CLOSE,
};

static int list_info_callback(char *buf, size_t len, void *data)
{
	struct phonebook_list_info *list_info = (struct phonebook_list_info *) data;

	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, CPBR_HEADER_START, strlen(CPBR_HEADER_START))) {
			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;
		}

	default:
		return -1;
	}

	return -1;
}

void phonebook_list_free(struct phonebook_list_info *list_info)
{
	struct phonebook_entry *entry;
	struct phonebook_entry *entry_save;

	list_for_each_entry_safe(entry, entry_save, &list_info->entry_list, list) {
		list_del(&entry->list);
		phonebook_entry_free(entry);
	}
}

int phonebook_list_get(int fd, struct phonebook_list_info *list_info)
{
	int tmp;

	INIT_LIST_HEAD(&list_info->entry_list);

	if (*list_info->store_select) {
		tmp = atcmd_plus_cpbs_write(fd, list_info->store_select);
		if (tmp < 0) {
			log_error("failed to get selected store info");
			return -1;
		}
	}
	tmp = atcmd_plus_cpbs_test(fd, &list_info->store.choices);
	if (tmp < 0) {
		log_error("failed to get selected store info");
		return -1;
	}
	tmp = atcmd_plus_cpbs_read(fd, &list_info->store.selected);
	if (tmp < 0) {
		log_error("failed to get selected store info");
		return -1;
	}
	tmp = atcmd_plus_cpbr_test(fd, &list_info->store);
	if (tmp < 0) {
		log_error("failed to get selected store info");
		return -1;
	}

	atcmd_writeline(fd, "AT+CPBR=%d,%d",
			list_info->store.index_min, list_info->store.index_max);
	tmp = atcmd_response_foreach_line(fd, list_info_callback, list_info);
  //CE910 reports an error if empty. Don't report error in this case
	if ((tmp < 0) && strcmp(Global.core.model, "CE910-DUAL")) {
		log_error("listing failed");
		phonebook_list_free(list_info);

		return tmp;
	}

	return 0;
}

int print_phonebook_list(int fd, const char *store)
{
	int tmp;
	struct phonebook_list_info list_info;

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

	if (store) {
		strncpy(list_info.store_select, store, STORE_NAME_LEN);
	}

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

	print_list_yaml(&list_info);

	phonebook_list_free(&list_info);

	return true;
}