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

#include "joliet.h"
#include "ecma119.h"
#include "ecma119_tree.h"
#include "tree.h"
#include "util.h"
#include "volume.h"
#include "eltorito.h"
#include "messages.h"

#include <assert.h>
#include <string.h>

static struct joliet_tree_node*
create_node(struct ecma119_write_target *t,
	    struct joliet_tree_node *parent,
	    struct iso_tree_node *iso)
{
	struct joliet_tree_node *ret =
		calloc(1, sizeof(struct joliet_tree_node));

	ret->name = iso_j_id(iso->name, t->input_charset);
	ret->dirent_len = 34 + (ret->name ? ucslen(ret->name) * 2 : 0);
	ret->parent = parent;
	ret->target = t;
	
	if ( ISO_ISDIR(iso) ) {
		struct iso_tree_node_dir *dir = (struct iso_tree_node_dir *) iso;
		ret->info.dir.children = calloc(sizeof(void*), dir->nchildren);
		ret->type = JOLIET_DIR;
	} else if (ISO_ISREG(iso)) {
		/* it's a file */
		struct iso_tree_node_file *iso_f = (struct iso_tree_node_file *) iso;
		struct iso_file *file;
		file = iso_file_table_lookup(t->file_table, iso_f);
		if ( file == NULL ) {
			file = iso_file_new(t, iso_f);
			if (!file) {
				/* 
				 * That was an error.
				 * TODO currently this cause the file to be ignored... Maybe
				 * throw an error is a better alternative
				 */
				joliet_tree_free(ret);
				return NULL;
			}
			iso_file_table_add_file(t->file_table, file);
		}
		ret->info.file = file;
		ret->type = JOLIET_FILE;
	} else if (ISO_ISBOOT(iso)) {
		/* it's boot catalog info */
		struct iso_tree_node_boot *boot = (struct iso_tree_node_boot*) iso;
		ret->info.boot_img = boot->img;
		ret->type = JOLIET_BOOT;
	} else {
		/* this should never happen */
		assert(0);
	}
	
	return ret;
}

static struct joliet_tree_node*
create_tree(struct ecma119_write_target *t,
	    struct joliet_tree_node *parent,
	    struct iso_tree_node *iso)
{	
	struct joliet_tree_node *root;
	assert(t && iso);
	
	if ( iso->hide_flags & LIBISO_HIDE_ON_JOLIET ) 
		return NULL;
	
	switch (iso->type) {
		case LIBISO_NODE_FILE:
		case LIBISO_NODE_BOOT:
			root = create_node(t, parent, iso);
			break;
		case LIBISO_NODE_DIR:
			{
			size_t i;
			struct joliet_tree_node *node;
			struct iso_tree_node_dir *dir;
			root = create_node(t, parent, iso);
			dir = (struct iso_tree_node_dir*)iso;
			for (i = 0; i < dir->nchildren; ++i) {
				node = create_tree(t, root, dir->children[i]);
				if ( node != NULL ) {
					root->info.dir.children[root->info.dir.nchildren++] = node;
				}
			}
			}
			break;
		default:
			{
			char msg[512];
			sprintf(msg, "Can't add %s to Joliet tree. This kind of files "
				"can only be added to a Rock Ridget tree. Skipping", iso->name);
			iso_msg_note(LIBISO_JOLIET_WRONG_FILE_TYPE, msg);
			return NULL;
			}
			break;
	}
	return root;
}

static int
cmp_node(const void *f1, const void *f2)
{
	struct joliet_tree_node *f = *((struct joliet_tree_node**)f1);
	struct joliet_tree_node *g = *((struct joliet_tree_node**)f2);
	return ucscmp(f->name, g->name);
}

static void
sort_tree(struct joliet_tree_node *root)
{
	size_t i;

	assert(root && (root->type == JOLIET_DIR));

	qsort(root->info.dir.children, root->info.dir.nchildren, 
	      sizeof(void*), cmp_node);
	for (i = 0; i < root->info.dir.nchildren; i++) {
		struct joliet_tree_node *child = root->info.dir.children[i];
		if ( child->type == JOLIET_DIR )
			sort_tree(child);
	}
}

void
joliet_prepare_path_tables(struct ecma119_write_target *t)
{
	size_t cur, i, j;

	t->pathlist_joliet[0] = t->joliet_root;
	t->path_table_size_joliet = 10; /* root directory record */
	cur = 1;

	for (i = 0; i < t->dirlist_len_joliet; i++) {
		struct joliet_tree_node *dir = t->pathlist_joliet[i];
		for (j = 0; j < dir->info.dir.nchildren; j++) {
			struct joliet_tree_node *ch = dir->info.dir.children[j];
			if (ch->type == JOLIET_DIR) {
				size_t len = 8 + ucslen(ch->name)*2;
				t->pathlist_joliet[cur++] = ch;
				t->path_table_size_joliet += len;
			}
		}
	}
}

/**
 * Calculate the size of each directory.
 */
void
joliet_calc_dir_size(struct ecma119_write_target *t,
		     struct joliet_tree_node *root)
{
	size_t i;
	size_t newlen;
	struct joliet_tree_node *ch;

	assert(root && (root->type == JOLIET_DIR) );

	t->dirlist_len_joliet++;
	root->info.dir.len = 68; /* for "." and ".." entries */
	for (i = 0; i < root->info.dir.nchildren; ++i) {
		ch = root->info.dir.children[i];
		newlen = root->info.dir.len + ch->dirent_len;
		if ((newlen % 2048) < (root->info.dir.len % 2048)) {
			root->info.dir.len = newlen + (2048 - (root->info.dir.len % 2048));
		} else {
			root->info.dir.len += ch->dirent_len;
		}
		if (ch->type == JOLIET_DIR)
			joliet_calc_dir_size(t, ch);
	}
	t->total_dir_size_joliet += round_up (root->info.dir.len, t->block_size);
}

/**
 * Calculate the position of each directory. Also fill out t->dirlist_joliet.
 */
void
joliet_calc_dir_pos(struct ecma119_write_target *t,
		    struct joliet_tree_node *root)
{
	size_t i;
	struct joliet_tree_node *ch;

	assert(root && (root->type == JOLIET_DIR));

	root->info.dir.block = t->curblock;
	t->curblock += div_up(root->info.dir.len, t->block_size);

	t->dirlist_joliet[t->curfile++] = root;
	for (i = 0; i < root->info.dir.nchildren; i++) {
		ch = root->info.dir.children[i];
		if (ch->type == JOLIET_DIR)
			joliet_calc_dir_pos(t, ch);
	}
}

struct joliet_tree_node*
joliet_tree_create(struct ecma119_write_target *t,
		   struct iso_tree_node *iso_root)
{
	struct joliet_tree_node *root = create_tree(t, NULL, iso_root);

	sort_tree(root);
	return root;
}

void
joliet_tree_free(struct joliet_tree_node *root)
{
	size_t i;

	if (root->type == JOLIET_DIR) {
		for (i=0; i < root->info.dir.nchildren; i++) {
			joliet_tree_free(root->info.dir.children[i]);
		}
		free(root->info.dir.children);
	}
	free(root->name);
	free(root);
}

/* ugh. this is mostly C&P */
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 joliet_tree_node *dir;
	int parent = 0;

	assert (t->joliet);

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

		rec = (struct ecma119_path_table_record*) buf;
		rec->len_di[0] = dir->parent ?
			(uint8_t) ucslen(dir->name) * 2 : 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->name, rec->len_di[0]);
		buf += 8 + rec->len_di[0] + (rec->len_di[0] % 2);
	}

}

/* 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 joliet_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 : ucslen(node->name) * 2;
	uint8_t f_id = (uint8_t) ((file_id == 3) ? 0 : file_id);
	uint8_t *name = (file_id >= 0) ? &f_id : (uint8_t*)node->name;
	struct ecma119_dir_record *rec = (struct ecma119_dir_record*)buf;

	if (node->type == JOLIET_DIR) {
		len = node->info.dir.len;
		block = node->info.dir.block;
	} else if (node->type == JOLIET_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 {
		/* file */
		assert(node->type == JOLIET_FILE);
		len = node->info.file->size;
		block = node->info.file->block;
	}
	
	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 == JOLIET_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_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);
}

void
joliet_write_sup_vol_desc(struct ecma119_write_target *t, uint8_t *buf)
{
	struct ecma119_sup_vol_desc *vol = (struct ecma119_sup_vol_desc*)buf;
	struct iso_volume *volume = t->volset->volume[t->volnum];
	uint16_t *vol_id = str2ucs(volume->volume_id, t->input_charset);
	uint16_t *pub_id = str2ucs(volume->publisher_id, t->input_charset);
	uint16_t *data_id = str2ucs(volume->data_preparer_id, t->input_charset);
	uint16_t *volset_id = str2ucs(t->volset->volset_id, t->input_charset);
	int vol_id_len = MIN(32, ucslen(vol_id) * 2);
	int pub_id_len = MIN(128, ucslen(pub_id) * 2);
	int data_id_len = MIN(128, ucslen(data_id) * 2);
	int volset_id_len = MIN(128, ucslen(volset_id) * 2);
	
	uint16_t *system_id = str2ucs(volume->system_id, t->input_charset);
	uint16_t *application_id = str2ucs(volume->application_id, t->input_charset);
	uint16_t *copyright_file_id = str2ucs(volume->copyright_file_id, t->input_charset);
	uint16_t *abstract_file_id = str2ucs(volume->abstract_file_id, t->input_charset);
	uint16_t *biblio_file_id = str2ucs(volume->biblio_file_id, t->input_charset);
	int system_id_len = MIN(32, ucslen(system_id) * 2);
	int application_id_len = MIN(128, ucslen(application_id) * 2);
	int copyright_file_id_len = MIN(37, ucslen(copyright_file_id) * 2);
	int abstract_file_id_len = MIN(37, ucslen(abstract_file_id) * 2);
	int biblio_file_id_len = MIN(37, ucslen(biblio_file_id) * 2);

	vol->vol_desc_type[0] = 2;
	memcpy(vol->std_identifier, "CD001", 5);
	vol->vol_desc_version[0] = 1;
	if (vol_id)
		memcpy(vol->volume_id, vol_id, vol_id_len);
	memcpy(vol->esc_sequences, "%/E", 3);
	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_joliet, 4);
	iso_lsb(vol->l_path_table_pos, t->l_path_table_pos_joliet, 4);
	iso_msb(vol->m_path_table_pos, t->m_path_table_pos_joliet, 4);

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

	memcpy(vol->vol_set_id, volset_id, volset_id_len);
	memcpy(vol->publisher_id, pub_id, pub_id_len);
	memcpy(vol->data_prep_id, data_id, data_id_len);
	
	memcpy(vol->system_id, system_id, system_id_len);
	memcpy(vol->application_id, "APPID", application_id_len);
	memcpy(vol->copyright_file_id, copyright_file_id, copyright_file_id_len);
	memcpy(vol->abstract_file_id, abstract_file_id, abstract_file_id_len);
	memcpy(vol->bibliographic_file_id, biblio_file_id, biblio_file_id_len);
	
	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_one_dir(struct ecma119_write_target *t,
	      struct joliet_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 == JOLIET_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];
		}
	}

	assert (buf - orig_buf == dir->info.dir.len);
}

static void
write_dirs(struct ecma119_write_target *t, uint8_t *buf)
{
	size_t i;
	struct joliet_tree_node *dir;

	assert (t->curblock + t->ms_block == t->dirlist_joliet[0]->info.dir.block);
	for (i = 0; i < t->dirlist_len_joliet; i++) {
		dir = t->dirlist_joliet[i];
		write_one_dir(t, dir, buf);
		buf += round_up(dir->info.dir.len, t->block_size);
	}
}

void
joliet_wr_sup_vol_desc(struct ecma119_write_target *t,
		       uint8_t *buf)
{
	ecma119_start_chunking(t,
			       joliet_write_sup_vol_desc,
			       2048,
			       buf);
}

void
joliet_wr_l_path_table(struct ecma119_write_target *t,
		       uint8_t *buf)
{
	ecma119_start_chunking(t,
			       write_l_path_table,
			       t->path_table_size_joliet,
			       buf);
}

void
joliet_wr_m_path_table(struct ecma119_write_target *t,
		       uint8_t *buf)
{
	ecma119_start_chunking(t,
			       write_m_path_table,
			       t->path_table_size_joliet,
			       buf);
}

void
joliet_wr_dir_records(struct ecma119_write_target *t,
		      uint8_t *buf)
{
	ecma119_start_chunking(t,
			       write_dirs,
			       t->total_dir_size_joliet,
			       buf);
}

