/* -*- indent-tabs-mode: t; tab-width: 8; c-basic-offset: 8; -*- */
/* vim: set noet ts=8 sts=8 sw=8 : */

#include <string.h>
#include <wchar.h>
#include <stdlib.h>
#include <time.h>
#include <assert.h>
#include <err.h>
#include <locale.h>
#include <langinfo.h>
#include <limits.h>

#include "ecma119.h"
#include "ecma119_tree.h"
#include "susp.h"
#include "rockridge.h"
#include "joliet.h"
#include "volume.h"
#include "tree.h"
#include "util.h"
#include "file.h"
#include "file_src.h"
#include "libisofs.h"
#include "libburn/libburn.h"
#include "eltorito.h"
#include "messages.h"

/* burn-source compatible stuff */
static int
bs_read(struct burn_source *bs, unsigned char *buf, int size);
static off_t
bs_get_size(struct burn_source *bs);
static void
bs_free_data(struct burn_source *bs);

typedef void (*write_fn)(struct ecma119_write_target*, uint8_t*);

/* return true if the given state is only required for Joliet volumes */
static int
is_joliet_state(enum ecma119_write_state);

static void
next_state(struct ecma119_write_target *t);

/* write t->state_data to the buf, one block at a time */
static void
write_data_chunk(struct ecma119_write_target *t, uint8_t *buf);

/* writing functions. All these functions assume the buf is large enough */
static void
write_pri_vol_desc(struct ecma119_write_target *t, uint8_t *buf);
static void
write_vol_desc_terminator(struct ecma119_write_target *t, uint8_t *buf);
static void
write_path_table(struct ecma119_write_target *t, int l_type, uint8_t *buf);
static void
write_l_path_table(struct ecma119_write_target *t, uint8_t *buf);
static void
write_m_path_table(struct ecma119_write_target *t, uint8_t *buf);
static void
write_one_dir_record(struct ecma119_write_target *t,
		     struct ecma119_tree_node *dir,
		     int file_id,
		     uint8_t *buf);
static void
write_one_dir(struct ecma119_write_target *t,
	      struct ecma119_tree_node *dir,
	      uint8_t *buf);
static void
write_dirs(struct ecma119_write_target *t, uint8_t *buf);

/* wrapper functions for writing */
static void wr_system_area(struct ecma119_write_target*, uint8_t*);
static void wr_pri_vol_desc(struct ecma119_write_target*, uint8_t*);
static void wr_vol_desc_term(struct ecma119_write_target*, uint8_t*);
static void wr_l_path_table(struct ecma119_write_target*, uint8_t*);
static void wr_m_path_table(struct ecma119_write_target*, uint8_t*);
static void wr_dir_records(struct ecma119_write_target*, uint8_t*);
static void wr_files(struct ecma119_write_target*, uint8_t*);

static const write_fn writers[] =
{
	NULL,
	wr_system_area,
	wr_pri_vol_desc,
	el_torito_wr_boot_vol_desc,
	joliet_wr_sup_vol_desc,
	wr_vol_desc_term,
	wr_l_path_table,
	wr_m_path_table,
	joliet_wr_l_path_table,
	joliet_wr_m_path_table,
	wr_dir_records,
	joliet_wr_dir_records,
	el_torito_wr_catalog,
	wr_files
};

/* When a writer is created, we 
 * 1) create an ecma119 tree
 * 2) add SUSP fields (if necessary)
 * 3) calculate the size and position of all nodes in the tree
 * 4) finalize SUSP fields (if necessary)
 */

static void
add_susp_fields_rec(struct ecma119_write_target *t,
		    struct ecma119_tree_node *node)
{
	size_t i;

	rrip_add_PX(t, node);
	rrip_add_NM(t, node);
	rrip_add_TF(t, node);
	
	switch (node->type) {
		case ECMA119_FILE:
			break;
		case ECMA119_SYMLINK:
			rrip_add_SL(t, node);
			break;
		case ECMA119_DIR:
			if (node->info.dir.real_parent != node->parent) {
				rrip_add_RE(t, node);
				rrip_add_PL(t, node);
			}
			for (i = 0; i < node->info.dir.nchildren; i++) {
				add_susp_fields_rec(t, node->info.dir.children[i]);
			}
			break;
		case ECMA119_PLACEHOLDER:
			rrip_add_CL(t, node);
			break;
		default:
			// FIXME support for device blocks by uncommenting this 
			//if (node->iso_self->attrib.st_rdev)
			//	rrip_add_PN(t, node);
			break;
	}
	susp_add_CE(t, node);
}

static void
add_susp_fields(struct ecma119_write_target *t)
{
	susp_add_SP(t, t->root);
	rrip_add_ER(t, t->root);
	add_susp_fields_rec(t, t->root);
}

/**
 * Fill out the dir.len and dir.CE_len fields for each
 * ecma119_tree_node that is a directory. Also calculate the total number of
 * directories and the number of files for which we need to write out data.
 * (dirlist_len and filelist_len)
 */
static void
calc_dir_size(struct ecma119_write_target *t,
	      struct ecma119_tree_node *dir)
{
	size_t i;
	size_t newlen;

	assert(dir->type == ECMA119_DIR);

	t->dirlist_len++;
	dir->info.dir.len = 34 + dir->info.dir.self_susp.non_CE_len
			+ 34 + dir->info.dir.parent_susp.non_CE_len;
	dir->info.dir.CE_len = dir->info.dir.self_susp.CE_len
			+ dir->info.dir.parent_susp.CE_len;
	for (i = 0; i < dir->info.dir.nchildren; ++i) {
		struct ecma119_tree_node *ch = dir->info.dir.children[i];

		newlen = dir->info.dir.len + ch->dirent_len + ch->susp.non_CE_len;
		if ((newlen % 2048) < (dir->info.dir.len % 2048)) {
			dir->info.dir.len = newlen + (2048 - (dir->info.dir.len % 2048));
		} else {
			dir->info.dir.len += ch->dirent_len + ch->susp.non_CE_len;
		}
		dir->info.dir.CE_len += ch->susp.CE_len;
	}
	t->total_dir_size += round_up(dir->info.dir.len + dir->info.dir.CE_len,
				      t->block_size);

	for (i = 0; i < dir->info.dir.nchildren; i++) {
		struct ecma119_tree_node *ch = dir->info.dir.children[i];
		if (ch->type == ECMA119_DIR) {
			calc_dir_size(t, ch);
		}
	}
}

/**
 * Fill out the block field in each ecma119_tree_node that is a directory and
 * fill out t->dirlist.
 */
static void
calc_dir_pos(struct ecma119_write_target *t,
	     struct ecma119_tree_node *dir)
{
	size_t i;

	assert(dir->type == ECMA119_DIR);

	dir->info.dir.block = t->curblock;
	t->curblock += div_up(dir->info.dir.len + dir->info.dir.CE_len, t->block_size);
	t->dirlist[t->curfile++] = dir;
	for (i = 0; i < dir->info.dir.nchildren; i++) {
		struct ecma119_tree_node *ch = dir->info.dir.children[i];
		if (ch->type == ECMA119_DIR)
			calc_dir_pos(t, ch);
	}
}

static int
cmp_file(const void *f1, const void *f2)
{
	struct iso_file *f = *((struct iso_file**)f1);
	struct iso_file *g = *((struct iso_file**)f2);
	/* higher weighted first */
	return g->sort_weight - f->sort_weight;
}

/**
 * Fill out the block field for each file and fill out t->filelist.
 */
static void
calc_file_pos(struct ecma119_write_target *t)
{
	size_t i;

	assert(t);
	
	t->filelist = calloc(1, sizeof(struct iso_file *) * t->file_table->count);

	for (i = 0; i < FILE_HASH_NODES; ++i) {
		
		struct iso_file_hash_node *node;

		node = t->file_table->table[i];
		if (!node)
			continue;
			
		do {
			struct iso_file *file = node->file;
			/*
			 * We only need to write the file when.
			 * a) The size is greater than 0.
			 * b) The file is new (not from a previous image) or we
			 *    are writting an image not for ms (i.e., we are modifying an
			 *    image).
			 */
			if ( file->size && (!file->prev_img || !t->ms_block) )
				t->filelist[t->curfile++] = file;
			node = node->next;
		} while (node);
	}
	
	t->filelist_len = t->curfile;
	
	/* sort */
	if ( t->sort_files )
		qsort(t->filelist, t->filelist_len,  sizeof(void*), cmp_file);
	
	/* fill block value */
	for ( i = 0; i < t->filelist_len; ++i) {
		struct iso_file *file = t->filelist[i];
		file->block = t->curblock;
		t->curblock += div_up(file->size, t->block_size);
	}
	
	/* reset curfile when we're finished */
	t->curfile = 0;
}


/**
 * Create a new ecma119_write_target from the given volume number of the
 * given volume set.
 *
 * \pre \p volnum is less than \p volset-\>volset_size.
 * \post For each node in the tree, writer_data has been allocated.
 * \post The directory heirarchy has been reorganised to be ecma119-compatible.
 */
static struct ecma119_write_target*
ecma119_target_new(struct iso_volset *volset,
                   struct ecma119_source_opts *opts)
{
	struct ecma119_write_target *t =
		calloc(1, sizeof(struct ecma119_write_target));
	size_t i, j, cur;
	struct iso_tree_node *iso_root = 
	       (struct iso_tree_node*) volset->volume[opts->volnum]->root;

	t->cache_inodes = opts->no_cache_inodes ? 0 : 1;
	t->replace_mode = opts->default_mode ? 0 : 1;
	if ( opts->replace_dir_mode )
		t->replace_mode |= 0x02;
	if ( opts->replace_file_mode )
		t->replace_mode |= 0x04;
	if ( opts->replace_gid )
		t->replace_mode |= 0x08;
	if ( opts->replace_uid )
		t->replace_mode |= 0x10;
	t->dir_mode = opts->dir_mode;
	t->file_mode = opts->file_mode;
	t->gid = opts->gid;
	t->uid = opts->uid;
	
	if (opts->input_charset) {
		t->input_charset = opts->input_charset;
	} else {
		/* default to locale charset */
		setlocale(LC_CTYPE, "");
		t->input_charset = nl_langinfo(CODESET);
	}
	t->ouput_charset = opts->ouput_charset ? opts->ouput_charset : "UTF-8";
	t->sort_files = opts->sort_files;
	
	t->ms_block = opts->ms_block;
	t->src = opts->src;

	t->file_table = iso_file_table_new(t->cache_inodes);
	volset->refcount++;
	t->iso_level = opts->level;
	t->block_size = 2048;
	t->relaxed_constraints = opts->relaxed_constraints;

	t->rockridge = (opts->flags & ECMA119_ROCKRIDGE) ? 1 : 0;
	t->joliet = (opts->flags & ECMA119_JOLIET) ? 1 : 0;
	
	t->catalog = volset->volume[opts->volnum]->bootcat;
	t->eltorito = t->catalog ? 1 : 0;
	t->write_eltorito = opts->copy_eltorito;
	if (t->eltorito && (!t->ms_block || !t->catalog->proc) ) {
		/* 
		 * For new and modified images we always need to write el-torito.
		 * For ms images, if the boot catalog was newly added, we also need
		 * to write it!
		 */
		t->write_eltorito = 1;
	}
	
	/* create the trees */
	t->root = ecma119_tree_create(t, iso_root);
	if (t->joliet)
		t->joliet_root = joliet_tree_create(t, iso_root);
	t->volset = volset;
	t->volnum = opts->volnum;
	t->now = time(NULL);

	if (t->rockridge)
		add_susp_fields(t);
	
	calc_dir_size(t, t->root);
	if (t->joliet) {
		joliet_calc_dir_size(t, t->joliet_root);
		t->pathlist_joliet = calloc(1, sizeof(void*) * t->dirlist_len_joliet);
		t->dirlist_joliet = calloc(1, sizeof(void*) * t->dirlist_len_joliet);
	}

	t->dirlist = calloc(1, sizeof(void*) * t->dirlist_len);
	t->pathlist = calloc(1, sizeof(void*) * t->dirlist_len);

	/* fill out the pathlist */
	t->pathlist[0] = t->root;
	t->path_table_size = 10; /* root directory record */
	cur = 1;
	for (i = 0; i < t->dirlist_len; i++) {
		struct ecma119_tree_node *dir = t->pathlist[i];
		for (j = 0; j < dir->info.dir.nchildren; j++) {
			struct ecma119_tree_node *ch = dir->info.dir.children[j];
			if (ch->type == ECMA119_DIR) {
				size_t len = 8 + strlen(ch->iso_name);
				t->pathlist[cur++] = ch;
				t->path_table_size += len + len % 2;
			}
		}
	}

	t->curblock = t->ms_block /* nwa for ms, usually 0 */
		+ 16 /* system area */
		+ 1	 /* primary volume desc */
		+ 1; /* volume desc terminator */

	if (t->eltorito)
		t->curblock += 1; /* boot record volume descriptor */
	if (t->joliet) /* supplementary vol desc */
		t->curblock += div_up (2048, t->block_size);

	t->l_path_table_pos = t->curblock;
	t->curblock += div_up(t->path_table_size, t->block_size);
	t->m_path_table_pos = t->curblock;
	t->curblock += div_up(t->path_table_size, t->block_size);
	if (t->joliet) {
		joliet_prepare_path_tables(t);
		t->l_path_table_pos_joliet = t->curblock;
		t->curblock += div_up(t->path_table_size_joliet, t->block_size);
		t->m_path_table_pos_joliet = t->curblock;
		t->curblock += div_up(t->path_table_size_joliet, t->block_size);
	}

	calc_dir_pos(t, t->root);

	/* reset curfile when we're finished */
	t->curfile = 0;
	if (t->joliet) {
	
		joliet_calc_dir_pos(t, t->joliet_root);
		
		/* reset curfile when we're finished */
		t->curfile = 0;
	}
	
	/* el-torito? */
	if (t->eltorito) {
		if (t->write_eltorito) {
			/* add catalog block */
			t->catblock = t->curblock;
			t->curblock += div_up(2048, t->block_size);
	
			/* add img block */
			t->imgblock = t->curblock;
			t->curblock += div_up(t->catalog->image->node->node.attrib.st_size, 
			                      t->block_size);
		} else {
			assert(t->ms_block);
			assert(t->catalog->proc);
			t->catblock = t->catalog->node->loc.block;
			t->imgblock = t->catalog->image->node->loc.block;
		}
	}
	
	calc_file_pos(t);

	if (t->rockridge) {
		susp_finalize(t, t->root);
		rrip_finalize(t, t->root);
	}

	t->total_size = (t->curblock - t->ms_block) * t->block_size;

	if (opts->overwrite) {
		
		/*
		 * Get a copy of the volume descriptors to be written in a DVD+RW
		 * disc
		 */
		uint8_t *buf;
		
		/* skip the first 16 blocks (system area) */
		buf = opts->overwrite + 16 * t->block_size;
		
		/*
		 * In the PVM to be written in the 16th sector of the disc, we
		 * need to specify the full size.
		 */
		t->vol_space_size = t->curblock;
		write_pri_vol_desc(t, buf);
		buf += t->block_size;
		if (t->joliet) {
			joliet_write_sup_vol_desc(t, buf);
			buf += t->block_size;
		}
		if (t->eltorito) {
			el_torito_write_boot_vol_desc(t, buf);
			buf += t->block_size;
		}
		write_vol_desc_terminator(t, buf);
	}
	
	/*
	 * The volume space size is just the size of the last session, in
	 * case of ms images.
	 */
	t->vol_space_size = t->curblock - t->ms_block;

	/* prepare for writing */
	t->curblock = 0;
	t->state = ECMA119_WRITE_SYSTEM_AREA;
	t->bytes_read = 2048;

	return t;
}

static int
is_joliet_state(enum ecma119_write_state state)
{
	return state == ECMA119_WRITE_SUP_VOL_DESC_JOLIET
	    || state == ECMA119_WRITE_L_PATH_TABLE_JOLIET
	    || state == ECMA119_WRITE_M_PATH_TABLE_JOLIET
	    || state == ECMA119_WRITE_DIR_RECORDS_JOLIET;
}

static int
is_eltorito_state(enum ecma119_write_state state)
{
	return state == ECMA119_WRITE_ELTORITO_BOOT_VOL_DESC
	    || state == ECMA119_WRITE_ELTORITO_CATALOG;
}

static void
next_state(struct ecma119_write_target *t)
{
	char msg[42];
	t->state++;
	while ( (!t->joliet && is_joliet_state(t->state)) 
	      ||(!t->eltorito && is_eltorito_state(t->state))
	      ||(!t->write_eltorito && t->state == ECMA119_WRITE_ELTORITO_CATALOG) )
		t->state++;

	sprintf(msg, "Now in state %d, curblock=%d.", t->state, t->curblock);
	iso_msg_debug(msg);
}

static void
wr_system_area(struct ecma119_write_target *t, uint8_t *buf)
{
	memset(buf, 0, t->block_size);
	if (t->curblock == 15) {
		next_state(t);
	}
}
static void
wr_pri_vol_desc(struct ecma119_write_target *t, uint8_t *buf)
{
	ecma119_start_chunking(t, write_pri_vol_desc, 2048, buf);
}

static void
wr_vol_desc_term(struct ecma119_write_target *t, uint8_t *buf)
{
	ecma119_start_chunking(t, write_vol_desc_terminator, 2048, buf);
}

static void
wr_l_path_table(struct ecma119_write_target *t, uint8_t *buf)
{
	ecma119_start_chunking(t, write_l_path_table, t->path_table_size, buf);
}

static void
wr_m_path_table(struct ecma119_write_target *t, uint8_t *buf)
{
	ecma119_start_chunking(t, write_m_path_table, t->path_table_size, buf);
}

static void
wr_dir_records(struct ecma119_write_target *t, uint8_t *buf)
{
	ecma119_start_chunking(t, write_dirs, t->total_dir_size, buf);
}

static void
wr_files(struct ecma119_write_target *t, uint8_t *buf)
{
	int res;
	struct state_files *f_st = &t->state_files;
	struct iso_file *f = t->filelist[f_st->file];

	if (!f_st->src) {
		
		if (!f->prev_img) {
			char msg[PATH_MAX + 14];
			sprintf(msg, "Writing file %s", f->path);
			iso_msg_debug(msg);
		} 
		
		f_st->src = f->src;
		if ( f->src->open(f->src) <= 0) {
			//FIXME instead of exit, just print a msg error and
			//skip file (what about reading from /dev/zero? instead)
			/* can only happen with new files */
			err(1, "couldn't open %s for reading", f->path);
		}
		assert(t->curblock + t->ms_block == f->block);
	}

	res = f_st->src->read_block(f_st->src, buf);
	if (res == 0) {
		/* we have read the expected bytes from file */
		f_st->src->close(f_st->src);
		f_st->src = NULL;
		f_st->file++;
		if (f_st->file >= t->filelist_len)
			next_state(t);
	}
}

static void
write_pri_vol_desc(struct ecma119_write_target *t, uint8_t *buf)
{
	struct ecma119_pri_vol_desc *vol = (struct ecma119_pri_vol_desc*)buf;
	struct iso_volume *volume = t->volset->volume[t->volnum];
	char *vol_id = str2d_char(volume->volume_id, t->input_charset);
	char *pub_id = str2a_char(volume->publisher_id, t->input_charset);
	char *data_id = str2a_char(volume->data_preparer_id, t->input_charset);
	char *volset_id = str2d_char(t->volset->volset_id, t->input_charset);
	
	char *system_id = str2a_char(volume->system_id, t->input_charset);
	char *application_id = str2a_char(volume->application_id, t->input_charset);
	char *copyright_file_id = str2d_char(volume->copyright_file_id, t->input_charset);
	char *abstract_file_id = str2d_char(volume->abstract_file_id, t->input_charset);
	char *biblio_file_id = str2d_char(volume->biblio_file_id, t->input_charset);
	
	vol->vol_desc_type[0] = 1;
	memcpy(vol->std_identifier, "CD001", 5);
	vol->vol_desc_version[0] = 1;
	if (system_id)
		strncpy((char*)vol->system_id, system_id, 32);
	else
		/* put linux by default? */
		memcpy(vol->system_id, "LINUX", 5); 
	if (vol_id)
		strncpy((char*)vol->volume_id, vol_id, 32);
	iso_bb(vol->vol_space_size, t->vol_space_size, 4);
	iso_bb(vol->vol_set_size, t->volset->volset_size, 2);
	iso_bb(vol->vol_seq_number, t->volnum + 1, 2);
	iso_bb(vol->block_size, t->block_size, 2);
	iso_bb(vol->path_table_size, t->path_table_size, 4);
	iso_lsb(vol->l_path_table_pos, t->l_path_table_pos, 4);
	iso_msb(vol->m_path_table_pos, t->m_path_table_pos, 4);

	write_one_dir_record(t, t->root, 3, vol->root_dir_record);

	/* mmm, why not check for null? */
	strncpy((char*)vol->vol_set_id, volset_id, 128);
	strncpy((char*)vol->publisher_id, pub_id, 128);
	strncpy((char*)vol->data_prep_id, data_id, 128);
	
	if (application_id)
		strncpy((char*)vol->application_id, application_id, 128);
	if (copyright_file_id)
		strncpy((char*)vol->copyright_file_id, copyright_file_id, 37);
	if (abstract_file_id)
		strncpy((char*)vol->abstract_file_id, abstract_file_id, 37);
	if (biblio_file_id)
		strncpy((char*)vol->bibliographic_file_id, biblio_file_id, 37);

	iso_datetime_17(vol->vol_creation_time, t->now);
	iso_datetime_17(vol->vol_modification_time, t->now);
	iso_datetime_17(vol->vol_effective_time, t->now);
	vol->file_structure_version[0] = 1;

	free(vol_id);
	free(volset_id);
	free(pub_id);
	free(data_id);
	free(system_id);
	free(application_id);
	free(copyright_file_id);
	free(abstract_file_id);
	free(biblio_file_id);
}

static void
write_vol_desc_terminator(struct ecma119_write_target *t, uint8_t *buf)
{
	struct ecma119_vol_desc_terminator *vol =
		(struct ecma119_vol_desc_terminator*) buf;

	vol->vol_desc_type[0] = 255;
	memcpy(vol->std_identifier, "CD001", 5);
	vol->vol_desc_version[0] = 1;
}

static void
write_path_table(struct ecma119_write_target *t, int l_type, uint8_t *buf)
{
	void (*write_int)(uint8_t*, uint32_t, int) = l_type ? iso_lsb
							    : iso_msb;
	size_t i;
	struct ecma119_path_table_record *rec;
	struct ecma119_tree_node *dir;
	int parent = 0;

	for (i = 0; i < t->dirlist_len; i++) {
		dir = t->pathlist[i];
		assert(dir->type == ECMA119_DIR);
		while ((i) && t->pathlist[parent] != dir->parent)
			parent++;
		assert(parent < i || i == 0);

		rec = (struct ecma119_path_table_record*) buf;
		rec->len_di[0] = dir->parent ? (uint8_t) strlen(dir->iso_name) : 1;
		rec->len_xa[0] = 0;
		write_int(rec->block, dir->info.dir.block, 4);
		write_int(rec->parent, parent + 1, 2);
		if (dir->parent)
			memcpy(rec->dir_id, dir->iso_name, rec->len_di[0]);
		buf += 8 + rec->len_di[0] + (rec->len_di[0] % 2);
	}
}

static void
write_l_path_table(struct ecma119_write_target *t, uint8_t *buf)
{
	write_path_table(t, 1, buf);
}

static void
write_m_path_table(struct ecma119_write_target *t, uint8_t *buf)
{
	write_path_table(t, 0, buf);
}

/* if file_id is >= 0, we use it instead of the filename. As a magic number,
 * file_id == 3 means that we are writing the root directory record (in order
 * to distinguish it from the "." entry in the root directory) */
static void
write_one_dir_record(struct ecma119_write_target *t,
		     struct ecma119_tree_node *node,
		     int file_id,
		     uint8_t *buf)
{
	uint32_t len;
	uint32_t block;
	uint8_t len_dr = (file_id >= 0) ? 34 : node->dirent_len;
	uint8_t len_fi = (file_id >= 0) ? 1 : strlen(node->iso_name);
	uint8_t f_id = (uint8_t) ((file_id == 3) ? 0 : file_id);
	uint8_t *name = (file_id >= 0) ? &f_id : (uint8_t*)node->iso_name;
	struct ecma119_dir_record *rec = (struct ecma119_dir_record*)buf;
	
	if (node->type == ECMA119_DIR) {
		len = node->info.dir.len;
		block = node->info.dir.block;
	} else if (node->type == ECMA119_FILE) {
		len = node->info.file->size;
		block = node->info.file->block;
	} else if (node->type == ECMA119_BOOT) {
		assert(t->eltorito);
		if (node->info.boot_img) {
			block = t->imgblock;
			len = t->catalog->image->node->node.attrib.st_size;
		} else {
			/* we always assume 2048 as catalog len */
			block = t->catblock;
			len = 2048;
		}
	} else {
		/* for nodes other than files and dirs, we set both len and block to 0 */
		len = 0;
		block = 0;
	}
	
	/* we don't write out susp fields for the root node */
	if (t->rockridge) {
		if (file_id == 0) {
			assert(node->type == ECMA119_DIR);
			susp_write(t, &node->info.dir.self_susp, &buf[len_dr]);
			len_dr += node->info.dir.self_susp.non_CE_len;
		} else if (file_id == 1) {
			assert(node->type == ECMA119_DIR);
			susp_write(t, &node->info.dir.parent_susp, &buf[len_dr]);
			len_dr += node->info.dir.parent_susp.non_CE_len;
		} else if (file_id < 0) {
			susp_write(t, &node->susp, &buf[len_dr]);
			len_dr += node->susp.non_CE_len;
		}
	}
	if (file_id == 1 && node->parent)
		node = node->parent;

	rec->len_dr[0] = len_dr;
	iso_bb(rec->block, block, 4);
	iso_bb(rec->length, len, 4);
	iso_datetime_7(rec->recording_time, t->now);
	rec->flags[0] = (node->type == ECMA119_DIR) ? 2 : 0;
	iso_bb(rec->vol_seq_number, t->volnum + 1, 2);
	rec->len_fi[0] = len_fi;
	memcpy(rec->file_id, name, len_fi);
}

static void
write_one_dir(struct ecma119_write_target *t,
	      struct ecma119_tree_node *dir,
	      uint8_t *buf)
{
	size_t i;
	int j;
	size_t len;
	uint8_t *orig_buf = buf;
	uint8_t *prior_buf = buf;

	assert(dir->type == ECMA119_DIR);
	/* write the "." and ".." entries first */
	write_one_dir_record(t, dir, 0, buf);
	buf += ((struct ecma119_dir_record*) buf)->len_dr[0];

	write_one_dir_record(t, dir, 1, buf);
	buf += ((struct ecma119_dir_record*) buf)->len_dr[0];

	for (i = 0; i < dir->info.dir.nchildren; i++) {
		write_one_dir_record(t, dir->info.dir.children[i], -1, buf);
		len = ((struct ecma119_dir_record*) buf)->len_dr[0];
		if ((buf + len - prior_buf) >= 2048) {
			for (j = len - 1; j >= 0; j--) {
				prior_buf[2048 + j] = buf[j];
				buf[j] = 0;
			}
			prior_buf += 2048;
			buf = prior_buf + len;
		}
		else {
			buf += ((struct ecma119_dir_record*) buf)->len_dr[0];
		}
	}

	/* write the susp continuation areas */
	if (t->rockridge) {
		susp_write_CE(t, &dir->info.dir.self_susp, buf);
		buf += dir->info.dir.self_susp.CE_len;
		susp_write_CE(t, &dir->info.dir.parent_susp, buf);
		buf += dir->info.dir.parent_susp.CE_len;
		for (i = 0; i < dir->info.dir.nchildren; i++) {
			susp_write_CE(t, &dir->info.dir.children[i]->susp, buf);
			buf += dir->info.dir.children[i]->susp.CE_len;
		}
	}
	assert (buf - orig_buf == dir->info.dir.len + dir->info.dir.CE_len);
}

static void
write_dirs(struct ecma119_write_target *t, uint8_t *buf)
{
	size_t i;
	struct ecma119_tree_node *dir;
	for (i = 0; i < t->dirlist_len; i++) {
		dir = t->dirlist[i];
		write_one_dir(t, dir, buf);
		buf += round_up(dir->info.dir.len + dir->info.dir.CE_len, t->block_size);
	}
}

void
ecma119_start_chunking(struct ecma119_write_target *t,
	       write_fn writer,
	       off_t data_size,
	       uint8_t *buf)
{
	if (data_size != t->state_data_size) {
		data_size = round_up(data_size, t->block_size);
		t->state_data = realloc(t->state_data, data_size);
		t->state_data_size = data_size;
	}
	memset(t->state_data, 0, t->state_data_size);
	t->state_data_off = 0;
	t->state_data_valid = 1;
	writer(t, t->state_data);
	write_data_chunk(t, buf);
}

static void
write_data_chunk(struct ecma119_write_target *t, uint8_t *buf)
{
	memcpy(buf, t->state_data + t->state_data_off, t->block_size);
	t->state_data_off += t->block_size;
	if (t->state_data_off >= t->state_data_size) {
		assert (t->state_data_off <= t->state_data_size);
		t->state_data_valid = 0;
		next_state(t);
	}
}

static int
bs_read_block(struct burn_source *bs)
{
	struct ecma119_write_target *t = (struct ecma119_write_target*)bs->data;
	
	if (t->curblock >= t->vol_space_size) {
		/* total_size could be setted by libburn */
		if ( t->curblock < (t->total_size / (off_t) t->block_size) ) {
			/* we pad the image */
			memset(t->buffer, 0, t->block_size);
			t->curblock++;
			return t->block_size;
		}
		return 0;
	}
	if (t->state_data_valid)
		write_data_chunk(t, t->buffer);
	else
		writers[t->state](t, t->buffer);
	t->curblock++;
	return t->block_size;
}

static int
bs_read(struct burn_source *bs, unsigned char *buf, int size)
{
	int ret = 0,summed_ret = 0;
	struct ecma119_write_target *t = (struct ecma119_write_target*)bs->data;

        /* make safe against partial buffer returns */
        while (1) {
		if (t->bytes_read == 2048) {
			/* we need to read next block */
			t->bytes_read = 0;
			ret = bs_read_block(bs);
			if (ret <= 0)
				return ret;
		}

		int bytes_to_read = 2048 - t->bytes_read;
		if (summed_ret + bytes_to_read > size) {
			bytes_to_read = size - summed_ret;
		}

		memcpy(buf + summed_ret, t->buffer, bytes_to_read);

		t->bytes_read += bytes_to_read;
                summed_ret += bytes_to_read;
                if (summed_ret >= size)
	        	break;
        }
        return summed_ret;
}

static off_t
bs_get_size(struct burn_source *bs)
{
	struct ecma119_write_target *t = (struct ecma119_write_target*)bs->data;
	return t->total_size;
}

static void
bs_free_data(struct burn_source *bs)
{
	struct ecma119_write_target *t = (struct ecma119_write_target*)bs->data;
	ecma119_tree_free(t->root);
	iso_file_table_clear(t->file_table);
	
	free(t->dirlist);
	free(t->pathlist);
	free(t->dirlist_joliet);
	free(t->pathlist_joliet);
	free(t->filelist);
	free(t->state_data);
	if (t->joliet)
		joliet_tree_free(t->joliet_root);
		
	// TODO is this needed?
	if (t->state_files.src)
		t->state_files.src->close(t->state_files.src);
}

int bs_set_size(struct burn_source *bs, off_t size)
{
	struct ecma119_write_target *t = (struct ecma119_write_target*)bs->data;
	t->total_size = size;
	return 1;
}
	
struct burn_source *iso_source_new_ecma119(struct iso_volset *volset,
					    struct ecma119_source_opts *opts)
{
	struct burn_source *ret = calloc(1, sizeof(struct burn_source));
	ret->refcount = 1;
	ret->read = bs_read;
	ret->get_size = bs_get_size;
	ret->set_size = bs_set_size;
	ret->free_data = bs_free_data;
	ret->data = ecma119_target_new(volset, opts);
	return ret;
}
