/* Copyright 2013 Bernhard R. Link <brlink@debian.org>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * SOFTWARE IN THE PUBLIC INTEREST, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */
#include "config.h"

#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <endian.h>
#include <errno.h>

#include "globals.h"
#define EFF_INTERNAL 1
#include "eff.h"

/* some trivial little extensible file format - write support */

struct effwriter {
	char *filename;
	FILE *file;
	block_type typecount;
	struct efftype {
		block_type id;
		char *name;
	} *types;
	size_t depth;
};

static void eff_write(const void *buffer, size_t len, struct effwriter *file) {
	ssize_t r;

	if (file->depth == 0)
		fail("Internal error: trying to write past EOF marker.");

	r = fwrite(buffer, len, 1, file->file);
	if (r < 0)
		fail("Error %d writing to '%s': %s", errno, file->filename,
				strerror(errno));
	if (r != 1)
		fail("Unexpectedend error writing to file '%s'", file->filename);
}

struct effwriter *eff_create(const char *filename, const char *type) {
	struct effwriter *f;
	size_t typelen = strlen(type);
	unsigned char buffer[5];

	assert(typelen < 256);

	f = zNEW(1, struct effwriter);
	f->depth = 1;
	f->typecount = BT_BLOCKSTART;
	f->filename = strdup(filename);
	if (f->filename == NULL)
		oom();
	if (strcmp(filename, "-") == 0)
		f->file = fdopen(1, "wb");
	else
		f->file = fopen(filename, "wb");
	if (f == NULL)
		fail("Error %d creating '%s': %s",
				errno, filename, strerror(errno));
	errno = 0;
	memcpy(buffer, "eff", 3);
	buffer[3] = 0;
	buffer[4] = typelen;
	eff_write(buffer, 5, f);
	eff_write(type, typelen, f);
	return f;
}

void eff_writestart(struct effwriter *file, const char *type) {
	struct efftype *et;
	block_type tid;
	uint8_t buffer[3];

	for (et = file->types;
	     et < file->types + file->typecount - BT_BLOCKSTART;
	     et++) {
		if (strcmp(et->name, type) == 0)
			break;
	}
	tid = et - file->types + BT_BLOCKSTART;
	if (tid >= file->typecount) {
		assert (tid == file->typecount);
		if (tid == BT_MAX)
			fail("Too many file block types for this version");
		RESIZE(&file->types, tid + 1 - BT_BLOCKSTART, struct efftype);
		et = file->types + tid - BT_BLOCKSTART;
		et->id = tid;
		et->name = strdup(type);
		if (et->name == NULL)
			oom();
		buffer[0] = BT_TYPE;
		buffer[1] = strlen(type);
		eff_write(buffer, 2, file);
		eff_write(type, buffer[1], file);
		file->typecount ++;
	}
	assert (tid == et->id);
	/* if (tid >= 128) ... */
	buffer[0] = tid;
	eff_write(buffer, 1, file);
	file->depth++;
}

void eff_writeend(struct effwriter *file) {
	unsigned char buffer[1];

	buffer[0] = BT_END;
	eff_write(buffer, 1, file);
	file->depth--;
}

void eff_writeuint32(struct effwriter *file, uint32_t u) {
	unsigned char buffer[1 + 4];

	u = u32trans(u);
	buffer[0] = BT_UINT32;
	memcpy(buffer + 1, &u, 4);
	eff_write(buffer, 1 + 4, file);
}
void eff_writeuint16(struct effwriter *file, uint16_t u) {
	unsigned char buffer[1 + 2];

	u = u16trans(u);
	buffer[0] = BT_UINT16;
	memcpy(buffer + 1, &u, 2);
	eff_write(buffer, 1 + 2, file);
}
void eff_writeuint8(struct effwriter *file, uint8_t u) {
	unsigned char buffer[1 + 1];

	buffer[0] = BT_UINT8;
	memcpy(buffer + 1, &u, 1);
	eff_write(buffer, 1 + 1, file);
}

void eff_writestring(struct effwriter *file, const char *string, size_t len) {
	unsigned char buffer[1 + 1];

	if (len >= 256)
		fail("Writing strings of length >= 256 not yet supported");
	buffer[0] = BT_SHORTSTRING;
	buffer[1] = len;
	eff_write(buffer, 2, file);
	eff_write(string, len, file);
}

void eff_writearray(struct effwriter *file, size_t len, size_t num, const void *data) {
	unsigned char buffer[1 + 1 + 2];
	uint16_t un;

	assert (len < 256UL && num < 63356UL);
	un = u16trans((uint16_t)num);
	buffer[0] = BT_SHORTARRAY;
	buffer[1] = len;
	memcpy(buffer + 2, &un, 2);
	eff_write(buffer, 4, file);
	eff_write(data, len * num, file);
}

void eff_done(struct effwriter *file) {
	if (file->depth > 0)
		eff_writeend(file);
	if (ferror(file->file))
		fail("There were errors writing '%s'", file->filename);
	if (fclose(file->file) != 0)
		fail("Error %d writing '%s': %s", errno, file->filename,
				strerror(errno));
	while (file->typecount > BT_BLOCKSTART) {
		free(file->types[--file->typecount - BT_BLOCKSTART].name);
	}
	free(file->types);
	free(file->filename);
	free(file);
}
