/*
 * Copyright (C) 2008-2012 Daisuke Aoyama <aoyama@peach.ne.jp>.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "istgt.h"
#include "istgt_misc.h"
#include "istgt_conf.h"

//#define CF_DELIM " \t,;"
#define CF_DELIM " \t"

static void istgt_free_all_cf_section(CF_SECTION *sp);
static void istgt_free_all_cf_item(CF_ITEM *ip);
static void istgt_free_all_cf_value(CF_VALUE *vp);
static void istgt_append_cf_item(CF_SECTION *sp, CF_ITEM *ip);
static void istgt_append_cf_value(CF_ITEM *ip, CF_VALUE *vp);

CONFIG *
istgt_allocate_config(void)
{
	CONFIG *cp;

	cp = xmalloc(sizeof *cp);
	memset(cp, 0, sizeof *cp);
	cp->file = NULL;
	cp->section = NULL;

	return cp;
}

void
istgt_free_config(CONFIG *cp)
{
	if (cp == NULL)
		return;
	if (cp->section != NULL) {
		istgt_free_all_cf_section(cp->section);
	}
	xfree(cp->file);
	xfree(cp);
}

static CF_SECTION *
istgt_allocate_cf_section(void)
{
	CF_SECTION *sp;

	sp = xmalloc(sizeof *sp);
	memset(sp, 0, sizeof *sp);
	sp->next = NULL;
	sp->item = NULL;

	return sp;
}

static void
istgt_free_cf_section(CF_SECTION *sp)
{
	if (sp == NULL)
		return;
	if (sp->item) {
		istgt_free_all_cf_item(sp->item);
	}
	xfree(sp->name);
	xfree(sp);
}

static void
istgt_free_all_cf_section(CF_SECTION *sp)
{
	CF_SECTION *next;

	if (sp == NULL)
		return;
	while (sp != NULL) {
		next = sp->next;
		istgt_free_cf_section(sp);
		sp = next;
	}
}

static CF_ITEM *
istgt_allocate_cf_item(void)
{
	CF_ITEM *ip;

	ip = xmalloc(sizeof *ip);
	memset(ip, 0, sizeof *ip);
	ip->next = NULL;
	ip->key = NULL;
	ip->val = NULL;

	return ip;
}

static void
istgt_free_cf_item(CF_ITEM *ip)
{
	if (ip == NULL)
		return;
	if (ip->val != NULL) {
		istgt_free_all_cf_value(ip->val);
	}
	xfree(ip->key);
	xfree(ip);
}

static void
istgt_free_all_cf_item(CF_ITEM *ip)
{
	CF_ITEM *next;

	if (ip == NULL)
		return;
	while (ip != NULL) {
		next = ip->next;
		istgt_free_cf_item(ip);
		ip = next;
	}
}

static CF_VALUE *
istgt_allocate_cf_value(void)
{
	CF_VALUE *vp;

	vp = xmalloc(sizeof *vp);
	memset(vp, 0, sizeof *vp);
	vp->next = NULL;
	vp->value = NULL;

	return vp;
}

static void
istgt_free_cf_value(CF_VALUE *vp)
{
	if (vp == NULL)
		return;
	xfree(vp->value);
	xfree(vp);
}

static void
istgt_free_all_cf_value(CF_VALUE *vp)
{
	CF_VALUE *next;

	if (vp == NULL)
		return;
	while (vp != NULL) {
		next = vp->next;
		istgt_free_cf_value(vp);
		vp = next;
	}
}

void
istgt_copy_cf_item(CF_SECTION *sp_dst, CF_SECTION *sp_src)
{
	CF_ITEM *ip, *ip_old;
	CF_VALUE *vp, *vp_old;

	istgt_free_all_cf_item(sp_dst->item);
	sp_dst->item = NULL;

	ip_old = sp_src->item;
	while (ip_old != NULL) {
		ip = istgt_allocate_cf_item();
		istgt_append_cf_item(sp_dst, ip);
		ip->key = xstrdup(ip_old->key);
		ip->val = NULL;

		vp_old = ip_old->val;
		while (vp_old != NULL) {
			vp = istgt_allocate_cf_value();
			istgt_append_cf_value(ip, vp);
			vp->value = xstrdup(vp_old->value);

			vp_old = vp_old->next;
		}
		ip_old = ip_old->next;
	}
}

CF_SECTION *
istgt_find_cf_section(CONFIG *cp, const char *name)
{
	CF_SECTION *sp;

	if (name == NULL || name[0] == '\0')
		return NULL;

	for (sp = cp->section; sp != NULL; sp = sp->next) {
		if (sp->name != NULL && sp->name[0] == name[0]
			&& strcasecmp(sp->name, name) == 0) {
			return sp;
		}
	}

	return NULL;
}

static void
istgt_append_cf_section(CONFIG *cp, CF_SECTION *sp)
{
	CF_SECTION *last;

	if (cp == NULL)
		return;
	if (cp->section == NULL) {
		cp->section = sp;
		return;
	}
	for (last = cp->section; last->next != NULL; last = last->next)
		;
	last->next = sp;
}

CF_ITEM *
istgt_find_cf_nitem(CF_SECTION *sp, const char *key, int idx)
{
	CF_ITEM *ip;
	int i;

	if (key == NULL || key[0] == '\0')
		return NULL;

	i = 0;
	for (ip = sp->item; ip != NULL; ip = ip->next) {
		if (ip->key != NULL && ip->key[0] == key[0]
			&& strcasecmp(ip->key, key) == 0) {
			if (i == idx) {
				return ip;
			}
			i++;
		}
	}

	return NULL;
}

CF_ITEM *
istgt_find_cf_item(CF_SECTION *sp, const char *key)
{
	return istgt_find_cf_nitem(sp, key, 0);
}

static void
istgt_append_cf_item(CF_SECTION *sp, CF_ITEM *ip)
{
	CF_ITEM *last;

	if (sp == NULL)
		return;
	if (sp->item == NULL) {
		sp->item = ip;
		return;
	}
	for (last = sp->item; last->next != NULL; last = last->next)
		;
	last->next = ip;
}

static void
istgt_append_cf_value(CF_ITEM *ip, CF_VALUE *vp)
{
	CF_VALUE *last;

	if (ip == NULL)
		return;
	if (ip->val == NULL) {
		ip->val = vp;
		return;
	}
	for (last = ip->val; last->next != NULL; last = last->next)
		;
	last->next = vp;
}

static void
istgt_set_cf_section_type(CF_SECTION *sp)
{
	static struct cfst_table_t {
		const char *name;
		CF_SECTION_TYPE type;
	} cfst_table[] = {
		{ "Global", ST_GLOBAL },
		{ "UnitControl", ST_UNITCONTROL },
		{ "PortalGroup", ST_PORTALGROUP },
		{ "InitiatorGroup", ST_INITIATORGROUP },
		{ "LogicalUnit", ST_LOGICAL_UNIT },
		{ "AuthGroup", ST_AUTHGROUP },
		{ NULL, ST_INVALID },
	};
	int i;

	if (sp == NULL || sp->name == NULL)
		return;
	for (i = 0; cfst_table[i].name != NULL; i++) {
		if (sp->name[0] == cfst_table[i].name[0]
			&& strncasecmp(sp->name, cfst_table[i].name,
						   strlen(cfst_table[i].name)) == 0) {
			sp->type = cfst_table[i].type;
			return;
		}
	}
	sp->type = ST_NONE;
}

static int
parse_line(CONFIG *cp, char *lp)
{
	CF_SECTION *sp;
	CF_ITEM *ip;
	CF_VALUE *vp;
	char *arg;
	char *key;
	char *val;
	char *p;
	int num;

	arg = trim_string(lp);
	if (arg[0] == '[') {
		/* section */
		arg++;
		key = strsepq(&arg, "]");
		if (key == NULL || arg != NULL) {
			fprintf(stderr, "broken section\n");
			return -1;
		}
		/* determine section number */
		for (p = key; *p != '\0' && !isdigit((int) *p); p++)
			;
		if (*p != '\0') {
			num = (int)strtol(p, NULL, 10);
		} else {
			num = 0;
		}

		sp = istgt_find_cf_section(cp, key);
		if (sp == NULL) {
			sp = istgt_allocate_cf_section();
			istgt_append_cf_section(cp, sp);
		}
		cp->current_section = sp;
		sp->name = xstrdup(key);
		sp->num = num;
		istgt_set_cf_section_type(sp);
	} else {
		/* parameters */
		sp = cp->current_section;
		if (sp == NULL) {
			fprintf(stderr, "unknown section\n");
			return -1;
		}
		key = strsepq(&arg, CF_DELIM);
		if (key == NULL) {
			fprintf(stderr, "broken key\n");
			return -1;
		}

		ip = istgt_allocate_cf_item();
		istgt_append_cf_item(sp, ip);
		ip->key = xstrdup(key);
		ip->val = NULL;
		if (arg != NULL) {
			/* key has value(s) */
			while (arg != NULL) {
				val = strsepq(&arg, CF_DELIM);
				vp = istgt_allocate_cf_value();
				istgt_append_cf_value(ip, vp);
				vp->value = xstrdup(val);
			}
		}
	}

	return 0;
}

static char *
fgets_line (FILE *fp)
{
	char *dst, *p;
	size_t total, len;

	dst = p = xmalloc(MAX_TMPBUF);
	dst[0] = '\0';
	total = 0;

	while (fgets(p, MAX_TMPBUF, fp) != NULL) {
		len = strlen(p);
		total += len;
		if (len + 1 < MAX_TMPBUF || dst[total - 1] == '\n') {
			return xrealloc(dst, total + 1);
		}
		dst = xrealloc (dst, total + MAX_TMPBUF);
		p = dst + total;
	}

	if (feof(fp) && total != 0) {
		dst = xrealloc(dst, total + 2);
		dst[total] = '\n';
		dst[total + 1] = '\0';
		return dst;
	}

	xfree(dst);

	return NULL;
}

int
istgt_read_config(CONFIG *cp, const char *file)
{
	FILE *fp;
	char *lp, *p;
	char *lp2, *q;
	int line;
	int n, n2;

	if (file == NULL || file[0] == '\0')
		return -1;
	fp = fopen(file, "r");
	if (fp == NULL) {
		fprintf(stderr, "open error: %s\n", file);
		return -1;
	}
	cp->file = xstrdup(file);

	line = 1;
	while ((lp = fgets_line(fp)) != NULL) {
		/* skip spaces */
		for (p = lp; *p != '\0' && isspace((int) *p); p++)
			;
		/* skip comment, empty line */
		if (p[0] == '#' || p[0] == '\0')
			goto next_line;

		/* concatenate line end with '\' */
		n = strlen(p);
		while (n > 2 && p[n - 1] == '\n' && p[n - 2] == '\\') {
			n -= 2;
			lp2 = fgets_line(fp);
			if (lp2 == NULL)
				break;
			line++;
			n2 = strlen(lp2);
			q = xmalloc(n + n2 + 1);
			memcpy(q, p, n);
			memcpy(q + n, lp2, n2);
			q[n + n2] = '\0';
			xfree(lp2);
			xfree(lp);
			p = lp = q;
			n += n2;
		} 

		/* parse one line */
		if (parse_line(cp, p) < 0) {
			fprintf(stderr, "parse error at line %d of %s\n", line, cp->file);
		}
	next_line:
		line++;
		xfree(lp);
	}

	fclose(fp);
	return 0;
}

int
istgt_print_config(CONFIG *cp)
{
	CF_SECTION *sp;
	CF_ITEM *ip;
	CF_VALUE *vp;

	if (cp == NULL)
		return -1;

	/* empty config? */
	sp = cp->section;
	if (sp == NULL)
		return 0;

	while (sp != NULL) {
		printf("Section: %s\n", sp->name);
		ip = sp->item;
		while (ip != NULL) {
			printf("  Item: %s ", ip->key);
			vp = ip->val;
			while (vp != NULL) {
				printf("Val: %s ", vp->value);
				vp = vp->next;
			}
			printf("\n");
			ip = ip->next;
		}
		sp = sp->next;
	}

	return 0;
}
