/*
 * Copyright (c) International Business Machines Corp., 2006
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 * @file pfi.c
 *
 * @author Oliver Lohmann
 *	   Andreas Arnez
 *	   Joern Engel
 *	   Frank Haverkamp
 *
 * @brief libpfi holds all code to create and process pfi files.
 *
 * <oliloh@de.ibm.com> Wed Feb	8 11:38:22 CET 2006: Initial creation.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <syslog.h>
#include <stdarg.h>
#include <errno.h>

#include "pfi.h"

#define PFI_MAGIC     "PFI!\n"
#define PFI_DATA      "DATA\n" /* The same size as PFI_MAGIC */
#define PFI_MAGIC_LEN 5

static const char copyright [] __attribute__((unused)) =
	"Copyright (c) International Business Machines Corp., 2006";

enum key_id {
	/* version 1 */
	key_version,	      /* must be index position 0! */
	key_mode,
	key_size,
	key_crc,
	key_label,
	key_flags,
	key_ubi_ids,
	key_ubi_size,
	key_ubi_type,
	key_ubi_names,
	key_ubi_alignment,
	key_raw_starts,
	key_raw_total_size,
	num_keys,
};

struct pfi_header {
	char defined[num_keys];	 /* reserve all possible keys even if
				    version does not require this. */
	int mode_no;		 /* current mode no. -> can only increase */
	union {
		char *str;
		uint32_t num;
	} value[num_keys];
};


#define PFI_MANDATORY	    0x0001
#define PFI_STRING	    0x0002
#define PFI_LISTVALUE	    0x0004	/* comma seperated list of nums */
#define PFI_MANDATORY_UBI   0x0008
#define PFI_MANDATORY_RAW   0x0010

struct key_descriptor {
	enum key_id id;
	const char *name;
	uint32_t flags;
};

static const struct key_descriptor key_desc_v1[] = {
	{ key_version, "version", PFI_MANDATORY },
	{ key_mode, "mode", PFI_MANDATORY | PFI_STRING },
	{ key_size, "size", PFI_MANDATORY },
	{ key_crc, "crc", PFI_MANDATORY },
	{ key_label, "label", PFI_MANDATORY | PFI_STRING },
	{ key_flags, "flags", PFI_MANDATORY },
	{ key_ubi_ids, "ubi_ids", PFI_MANDATORY_UBI | PFI_STRING },
	{ key_ubi_size, "ubi_size", PFI_MANDATORY_UBI },
	{ key_ubi_type, "ubi_type", PFI_MANDATORY_UBI | PFI_STRING },
	{ key_ubi_names, "ubi_names", PFI_MANDATORY_UBI | PFI_STRING },
	{ key_ubi_alignment, "ubi_alignment", PFI_MANDATORY_UBI },
	{ key_raw_starts, "raw_starts", PFI_MANDATORY_RAW | PFI_STRING },
	{ key_raw_total_size, "raw_total_size", PFI_MANDATORY_RAW },
};

static const struct key_descriptor *key_descriptors[] = {
	NULL,
	key_desc_v1,					   /* version 1 */
};

static const int key_descriptors_max[] = {
	0,						   /* version 0 */
	sizeof(key_desc_v1)/sizeof(struct key_descriptor), /* version 1 */
};

#define ARRAY_SIZE(a)    (sizeof(a) / sizeof((a)[0]))

static const char* modes[] = {"raw", "ubi"}; /* order isn't arbitrary! */

/* latest version contains all possible keys */
static const struct key_descriptor *key_desc = key_desc_v1;

#define PFI_IS_UBI(mode) \
	(((mode) != NULL) && (strcmp("ubi", (mode)) == 0))

#define PFI_IS_RAW(mode) \
	(((mode) != NULL) && (strcmp("raw", (mode)) == 0))

/**
 * @return	 <0	On Error.
 *		>=0	Mode no.
 */
static int
get_mode_no(const char* mode)
{
	int i;

	for (i = 0; i < (int)ARRAY_SIZE(modes); i++)
		if (strcmp(mode, modes[i]) == 0)
			return i;
	return -1;
}

static int
find_key_by_name (const char *name)
{
	int i;

	for (i = 0; i < num_keys; i++) {
		if (strcmp(name, key_desc[i].name) == 0)
			return i;
	}
	return -1;
}

static int
check_valid (pfi_header head)
{
	int i;
	int max_keys;
	uint32_t version;
	const char *mode;
	const struct key_descriptor *desc;
	uint32_t to_check = PFI_MANDATORY;

	/*
	 * For the validity check the list of possible keys depends on
	 * the version of the PFI file used.
	 */
	version = head->value[key_version].num;
	if (version > PFI_HDRVERSION)
		return PFI_ENOHEADER;

	max_keys = key_descriptors_max[version];
	desc = key_descriptors[version];

	if (!desc)
		return PFI_ENOVERSION;

	mode = head->value[key_mode].str;
	if (PFI_IS_UBI(mode)) {
		to_check |= PFI_MANDATORY_UBI;
	}
	else if (PFI_IS_RAW(mode)) {
		to_check |= PFI_MANDATORY_RAW;
	}
	else { /* neither UBI nor RAW == ERR */
		return PFI_EINSUFF;
	}

	for (i = 0; i < max_keys; i++) {
		if ((desc[i].flags & to_check) && !head->defined[i]) {
			fprintf(stderr, "libpfi: %s missing\n", desc[i].name);
			return PFI_EINSUFF;
		}
	}

	return 0;
}

int pfi_header_init (pfi_header *head)
{
	int i;
	pfi_header self = (pfi_header) malloc(sizeof(*self));

	*head = self;
	if (self == NULL)
		return PFI_ENOMEM;

	/* initialize maximum number of possible keys */
	for (i = 0; i < num_keys; i++) {
		memset(self, 0, sizeof(*self));
		self->defined[i] = 0;
	}

	return 0;
}

int pfi_header_destroy (pfi_header *head)
{
	int i;
	pfi_header self = *head;

	for (i = 0; i < num_keys; i++) {
		if (self->defined[i] && (key_desc[i].flags & PFI_STRING) &&
		    self->value[i].str) {
			free(self->value[i].str);
		}
	}
	free(*head);
	*head = NULL;
	return 0;
}

int pfi_header_setnumber (pfi_header head,
			   const char *key, uint32_t value)
{
	int key_id = find_key_by_name(key);

	if (key_id < 0)
		return PFI_EUNDEF;

	if (key_desc[key_id].flags & PFI_STRING)
		return PFI_EBADTYPE;

	head->value[key_id].num = value;
	head->defined[key_id] = 1;
	return 0;
}

int pfi_header_setvalue (pfi_header head,
			  const char *key, const char *value)
{
	int key_id = find_key_by_name(key);

	if (value == NULL)
		return PFI_EINSUFF;

	if ((key_id < 0) || (key_id >= num_keys))
		return PFI_EUNDEF;

	if (key_desc[key_id].flags & PFI_STRING) {
		/*
		 * The value is a string. Copy to a newly allocated
		 * buffer. Delete the old value, if already set.
		 */
		size_t len = strlen(value) + 1;
		char *old_str = NULL;
		char *str;

		old_str = head->value[key_id].str;
		if (old_str != NULL)
			free(old_str);

		str = head->value[key_id].str = (char *) malloc(len);
		if (str == NULL)
			return PFI_ENOMEM;

		strcpy(str, value);
	} else {
		int len;
		int ret;
		/* FIXME: here we assume that the value is always
		   given in hex and starts with '0x'. */
		ret = sscanf(value, "0x%x%n", &head->value[key_id].num, &len);
		if (ret < 1 || value[len] != '\0')
			return PFI_EBADTYPE;
	}
	head->defined[key_id] = 1;
	return 0;
}

int pfi_header_getnumber (pfi_header head,
			   const char *key, uint32_t *value)
{
	int key_id = find_key_by_name(key);

	if (key_id < 0)
		return PFI_EUNDEF;

	if (key_desc[key_id].flags & PFI_STRING)
		return PFI_EBADTYPE;

	if (!head->defined[key_id])
		return PFI_EUNDEF;

	*value = head->value[key_id].num;
	return 0;
}

int pfi_header_getstring (pfi_header head,
			   const char *key, char *value, size_t size)
{
	int key_id = find_key_by_name(key);

	if (key_id < 0)
		return PFI_EUNDEF;

	if (!(key_desc[key_id].flags & PFI_STRING))
		return PFI_EBADTYPE;

	if (!head->defined[key_id])
		return PFI_EUNDEF;

	strncpy(value, head->value[key_id].str, size-1);
	value[size-1] = '\0';
	return 0;
}

int pfi_header_write (FILE *out, pfi_header head)
{
	int i;
	int ret;

	pfi_header_setnumber(head, "version", PFI_HDRVERSION);

	if ((ret = check_valid(head)) != 0)
		return ret;

	/* OK.	Now write the header. */

	ret = fwrite(PFI_MAGIC, 1, PFI_MAGIC_LEN, out);
	if (ret < PFI_MAGIC_LEN)
		return ret;


	for (i = 0; i < num_keys; i++) {
		if (!head->defined[i])
			continue;

		ret = fprintf(out, "%s=", key_desc[i].name);
		if (ret < 0)
			return PFI_EFILE;

		if (key_desc[i].flags & PFI_STRING) {
			ret = fprintf(out, "%s", head->value[i].str);
			if (ret < 0)
				return PFI_EFILE;
		} else {
			ret = fprintf(out, "0x%8x", head->value[i].num);
			if (ret < 0)
				return PFI_EFILE;

		}
		ret = fprintf(out, "\n");
		if (ret < 0)
			return PFI_EFILE;
	}
	ret = fprintf(out, "\n");
	if (ret < 0)
		return PFI_EFILE;

	ret = fflush(out);
	if (ret != 0)
		return PFI_EFILE;

	return 0;
}

int pfi_header_read (FILE *in, pfi_header head)
{
	char magic[PFI_MAGIC_LEN];
	char mode[PFI_KEYWORD_LEN];
	char buf[256];

	if (PFI_MAGIC_LEN != fread(magic, 1, PFI_MAGIC_LEN, in))
		return PFI_EFILE;
	if (memcmp(magic, PFI_MAGIC, PFI_MAGIC_LEN) != 0)  {
		if (memcmp(magic, PFI_DATA, PFI_MAGIC_LEN) == 0) {
			return PFI_DATA_START;
		}
		return PFI_ENOHEADER;
	}

	while (fgets(buf, sizeof(buf), in) != NULL && buf[0] != '\n') {
		char *value;
		char *end;
		value = strchr(buf, '=');
		if (value == NULL)
			return PFI_ENOHEADER;

		*value = '\0';
		value++;
		end = strchr(value, '\n');
		if (end)
		       *end = '\0';

		if (pfi_header_setvalue(head, buf, value))
			return PFI_ENOHEADER;
	}

	if (check_valid(head) != 0)
		return PFI_ENOHEADER;

	/* set current mode no. in head */
	pfi_header_getstring(head, "mode", mode, PFI_KEYWORD_LEN);
	if (head->mode_no > get_mode_no(mode)) {
		return PFI_EMODE;
	}
	head->mode_no = get_mode_no(mode);
	return 0;
}

int pfi_header_dump (FILE *out, pfi_header head __attribute__((__unused__)))
{
	fprintf(out, "Sorry not implemented yet. Write mail to "
		"Andreas Arnez and complain!\n");
	return 0;
}

int pfi_read (FILE *in, pfi_read_func func, void *priv_data)
{
	int rc;
	pfi_header header;

	rc = pfi_header_init (&header);
	if (0 != rc)
		return rc;
	if (!func)
		return PFI_EINVAL;

	while ((0 == rc) && !feof(in)) {
		/*
		 * Read header and check consistency of the fields.
		 */
		rc = pfi_header_read( in, header );
		if (0 != rc)
			break;
		if (func) {
			rc = func(in, header, priv_data);
			if (rc != 0)
				break;
		}
	}

	pfi_header_destroy(&header);
	return rc;
}