/*
 * Copyright (c) 2002, The Tendra Project <http://www.ten15.org/>
 * 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 unmodified, 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 THE AUTHOR ``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 THE AUTHOR 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.
 *
 *
 *    		 Crown Copyright (c) 1997
 *
 *    This TenDRA(r) Computer Program is subject to Copyright
 *    owned by the United Kingdom Secretary of State for Defence
 *    acting through the Defence Evaluation and Research Agency
 *    (DERA).  It is made available to Recipients with a
 *    royalty-free licence for its use, reproduction, transfer
 *    to other parties and amendment for any purpose not excluding
 *    product development provided that any such use et cetera
 *    shall be deemed to be acceptance of the following conditions:-
 *
 *        (1) Its Recipients shall ensure that this Notice is
 *        reproduced upon any copies or amended versions of it;
 *
 *        (2) Any amended version of it shall be clearly marked to
 *        show both the nature of and the organisation responsible
 *        for the relevant amendment or amendments;
 *
 *        (3) Its onward transfer from a recipient to another
 *        party shall be deemed to be that party's acceptance of
 *        these conditions;
 *
 *        (4) DERA gives no warranty or assurance as to its
 *        quality or suitability for any purpose and DERA accepts
 *        no liability whatsoever in relation to any use to which
 *        it may be put.
 *
 * $TenDRA: tendra/src/tools/tld/capsule.c,v 1.10 2004/08/09 08:48:30 bp Exp $
 */


/*** capsule.c --- TDF capsule ADT.
 *
 ** Author: Steve Folkes <smf@hermes.mod.uk>
 *
 *** Commentary:
 *
 * This file implements the TDF capsule routines used by the TDF linker.
 *
 *** Change Log:*/

/****************************************************************************/
#include <limits.h>

#include "capsule.h"
#include "debug.h"
#include "dstring.h"
#include "exception.h"
#include "msgcat.h"
#include "istream.h"
#include "name-key.h"
#include "library.h"
#include "syntax.h"
#include "tdf.h"
#include "unit-entry.h"

#include "solve-cycles.h"

/*--------------------------------------------------------------------------*/

typedef struct UnitSetListEntryT {
    struct UnitSetListEntryT   *next;
    NStringT			name;
} UnitSetListEntryT, *UnitSetListEntryP;

typedef struct UnitSetT {
    NStringT			name;
    UnitEntryP			entry;
} UnitSetT, *UnitSetP;

typedef struct ShapeDataT {
    ShapeEntryP			entry;
    unsigned			num_ids;
    unsigned		       *id_maps;
} ShapeDataT, *ShapeDataP;

typedef struct NameDataT {
    NameEntryP		       *names_vec;
    unsigned			num_names;
} NameDataT, *NameDataP;

/*--------------------------------------------------------------------------*/

#define NUM_DEFAULT_UNIT_SETS	(12)

/*--------------------------------------------------------------------------*/

static char *capsule_default_unit_set_names [NUM_DEFAULT_UNIT_SETS] = {
    "tld",
    "tld2",
    "versions",
    "tokdec",
    "tokdef",
    "aldef",
    "diagtype",
    "tagdec",
    "diagdef",
    "dgcompunit",
    "tagdef",
    "linkinfo"
};
static UnitSetT   capsule_default_unit_sets [NUM_DEFAULT_UNIT_SETS];
static unsigned   capsule_num_unit_sets;
static UnitSetP   capsule_unit_sets     = NIL (UnitSetP);
static unsigned   capsule_tld_index     = 0;
static unsigned   capsule_tld2_index    = 1;
static unsigned   capsule_unit_length;
static unsigned   capsule_unit_offset;
static ExceptionP XX_capsule_error      = EXCEPTION ("error in TDF capsule");
static unsigned   capsule_major_version = 0;
static unsigned   capsule_minor_version = 0;

/*--------------------------------------------------------------------------*/

static void
capsule_setup_defaults()
{
    if (capsule_unit_sets == NIL (UnitSetP)) {
		unsigned i;

		for (i = 0; i < NUM_DEFAULT_UNIT_SETS; i ++) {
			nstring_copy_cstring (&(capsule_default_unit_sets [i].name),
								  capsule_default_unit_set_names [i]);
		}
		capsule_num_unit_sets = NUM_DEFAULT_UNIT_SETS;
		capsule_unit_sets     = capsule_default_unit_sets;
    }
}

static void
capsule_setup(UnitTableP units)
{
    static BoolT need_setup = TRUE;

    if (need_setup) {
		unsigned i;

		capsule_setup_defaults ();
		for (i = 0; i < capsule_num_unit_sets; i ++) {
			NStringP   name = &(capsule_unit_sets [i].name);
			UnitEntryP entry;

			entry = unit_table_add (units, name, i);
			capsule_unit_sets [i].entry = entry;
			debug_info_u_name (name);
		}
		need_setup = FALSE;
    }
}

static BoolT
capsule_read_unit_set_name(IStreamP istream,
						   DStringP dstring)
{
    char c;

    do {
		if (!istream_read_char (istream, &c)) {
			return (FALSE);
		}
    } while (syntax_is_white_space (c));
    if (c != '"') {
		MSG_unit_set_expected_quote (istream);
		UNREACHED;
    }
    dstring_init (dstring);
    while (istream_read_char (istream, &c)) {
		if (c == '"') {
			return (TRUE);
		} else if (c == '\\') {
			switch (istream_read_escaped_char (istream, &c)) EXHAUSTIVE {
			case ISTREAM_STAT_READ_CHAR:
				dstring_append_char (dstring, c);
				break;
			case ISTREAM_STAT_NO_CHAR:
				break;
			case ISTREAM_STAT_SYNTAX_ERROR:
				MSG_unit_set_illegal_escape (istream);
				UNREACHED;
			}
		} else {
			dstring_append_char (dstring, c);
		}
    }
    MSG_unit_set_eof_in_name (istream);
    UNREACHED;
}

static void
capsule_check_unit_sets(IStreamP istream)
{
    static BoolT    inited    = FALSE;
    static NStringT tld;
    static NStringT tld2;
    BoolT           tld_found = FALSE;
    unsigned        i;

    if (!inited) {
		nstring_copy_cstring (&tld, "tld");
		nstring_copy_cstring (&tld2, "tld2");
		inited = TRUE;
    }
    capsule_tld_index  = UINT_MAX;
    capsule_tld2_index = UINT_MAX;
    for (i = 0; i < capsule_num_unit_sets; i ++) {
		NStringP name = &(capsule_unit_sets [i].name);
		unsigned j;

		for (j = 0; j < i; j ++) {
			if (nstring_equal (name, &(capsule_unit_sets [j].name))) {
				MSG_unit_set_duplicate_name (istream_name (istream), name);
				UNREACHED;
			}
		}
		if (nstring_equal (name, &tld)) {
			capsule_tld_index = i;
			tld_found         = TRUE;
		} else if (nstring_equal (name, &tld2)) {
			capsule_tld2_index = i;
		}
    }
    if (!tld_found) {
		MSG_unit_set_no_tld_name (istream_name (istream));
		UNREACHED;
    }
}

static void
capsule_read_unit_set_file_1(IStreamP istream)
{
    UnitSetListEntryP  head          = NIL (UnitSetListEntryP);
    UnitSetListEntryP *tail          = &head;
    unsigned           num_unit_sets = 0;
    UnitSetListEntryP  entry;
    UnitSetP           unit_sets;
    unsigned           i;

    for (;;) {
		DStringT dstring;

		if (!capsule_read_unit_set_name (istream, &dstring)) {
			goto done;
		}
		entry       = ALLOCATE (UnitSetListEntryT);
		entry->next = NIL (UnitSetListEntryP);
		dstring_to_nstring (&dstring, &(entry->name));
		*tail       = entry;
		tail        = &(entry->next);
		dstring_destroy (&dstring);
		num_unit_sets ++;
    }
  done:
    unit_sets = ALLOCATE_VECTOR (UnitSetT, num_unit_sets);
    i         = 0;
    entry     = head;
    while (entry) {
		UnitSetListEntryP tmp = entry->next;

		nstring_assign (&(unit_sets [i].name), &(entry->name));
		DEALLOCATE (entry);
		entry = tmp;
		i ++;
    }
    capsule_num_unit_sets = num_unit_sets;
    capsule_unit_sets     = unit_sets;
    capsule_check_unit_sets (istream);
}

/*--------------------------------------------------------------------------*/

static TDFReaderP
capsule_reader(CapsuleP capsule)
{
    ASSERT (capsule->type == CT_INPUT);
    return (&(capsule->u.reader));
}

static TDFWriterP
capsule_writer(CapsuleP capsule)
{
    ASSERT (capsule->type == CT_OUTPUT);
    return (&(capsule->u.writer));
}

static NStringP
capsule_magic()
{
    static NStringT const_magic;
    static BoolT    inited = FALSE;

    if (!inited) {
		nstring_copy_cstring (&const_magic, "TDFC");
		inited = TRUE;
    }
    return (&const_magic);
}

/*--------------------------------------------------------------------------*/

static void
capsule_read_header(CapsuleP capsule)
{
    TDFReaderP reader      = capsule_reader (capsule);
    NStringP   const_magic = capsule_magic ();
    NStringT   magic;
    unsigned   major;
    unsigned   minor;

    nstring_init_length (&magic, (unsigned) 4);
    tdf_read_bytes (reader, &magic);
    if (!nstring_equal (&magic, const_magic)) {
		MSG_capsule_bad_magic (capsule, &magic, const_magic);
		THROW (XX_capsule_error);
		UNREACHED;
    }
    nstring_destroy (&magic);
    major = tdf_read_int (reader);
    minor = tdf_read_int (reader);
    debug_info_r_versions (major, minor);
    if (major < 4) {
		MSG_capsule_bad_version (capsule, major);
		THROW (XX_capsule_error);
		UNREACHED;
    } else if (capsule_major_version == 0) {
		capsule_major_version = major;
		capsule_minor_version = minor;
    } else if (capsule_major_version != major) {
		MSG_capsule_version_mismatch (capsule, capsule_major_version, major);
		THROW (XX_capsule_error);
		UNREACHED;
    } else if (capsule_minor_version < minor) {
		capsule_minor_version = minor;
    }
    tdf_read_align (reader);
}

static UnitEntryP*
capsule_read_unit_set_names(CapsuleP capsule,
							UnitTableP units,
							unsigned *num_unit_sets_ref)
{
    TDFReaderP  reader        = capsule_reader (capsule);
    unsigned    num_unit_sets = tdf_read_int (reader);
    UnitEntryP *units_vec     = ALLOCATE_VECTOR (UnitEntryP, num_unit_sets);
    UnitEntryP  tld_entry     = capsule_unit_sets [capsule_tld_index].entry;
    UnitEntryP  tld2_entry    = ((capsule_tld2_index == UINT_MAX) ?
								 NIL (UnitEntryP) :
								 capsule_unit_sets [capsule_tld2_index].entry);
    BoolT       has_tld_unit  = FALSE;
    unsigned    i;

    debug_info_r_start_unit_decs (num_unit_sets);
    for (i = 0; i < num_unit_sets; i ++) {
		NStringT   nstring;
		UnitEntryP entry;

		tdf_read_string (reader, &nstring);
		if ((entry = unit_table_get (units, &nstring)) != NIL (UnitEntryP)) {
			unsigned order = unit_entry_order (entry);
			unsigned j;

			for (j = 0; j < i; j ++) {
				if (entry == units_vec [j]) {
					MSG_duplicate_unit_set_name (capsule, &nstring);
					THROW (XX_capsule_error);
					UNREACHED;
				} else if (order < unit_entry_order (units_vec [j])) {
					MSG_out_of_order_unit_set_name (capsule, &nstring);
					THROW (XX_capsule_error);
					UNREACHED;
				}
			}
			if (entry == tld2_entry) {
				MSG_tld2_unit_set_type_obsolete (capsule);
			}
			if ((entry == tld_entry) || (entry == tld2_entry)) {
				if (has_tld_unit) {
					MSG_extra_tld_unit_set (capsule);
					THROW (XX_capsule_error);
					UNREACHED;
				}
				has_tld_unit = TRUE;
			}
			units_vec [i] = entry;
		} else {
			MSG_unknown_unit_set_name (capsule, &nstring);
			THROW (XX_capsule_error);
			UNREACHED;
		}
		debug_info_r_unit_dec (&nstring);
		nstring_destroy (&nstring);
    }
    if (!has_tld_unit) {
		MSG_missing_tld_unit_set (tdf_reader_name (reader));
    }
    *num_unit_sets_ref = num_unit_sets;
    return (units_vec);
}

static ShapeDataP
capsule_read_shapes(CapsuleP capsule, ShapeTableP shapes,
		    unsigned *num_shapes_ref)
{
    TDFReaderP reader     = capsule_reader (capsule);
    unsigned   num_shapes = tdf_read_int (reader);
    ShapeDataP shapes_vec = ALLOCATE_VECTOR (ShapeDataT, num_shapes);
    unsigned   i;

    debug_info_r_start_shapes (num_shapes);
    for (i = 0; i < num_shapes; i ++) {
		NStringT    nstring;
		unsigned    num_ids;
		ShapeEntryP entry;
		unsigned    j;

		tdf_read_string (reader, &nstring);
		num_ids = tdf_read_int (reader);
		entry   = shape_table_add (shapes, &nstring);
		for (j = 0; j < i; j ++) {
			if (entry == shapes_vec [j].entry) {
				MSG_duplicate_shape_name (capsule, &nstring);
				THROW (XX_capsule_error);
				UNREACHED;
			}
		}
		debug_info_r_shape (&nstring, num_ids);
		nstring_destroy (&nstring);
		shapes_vec [i].entry   = entry;
		shapes_vec [i].num_ids = num_ids;
		shapes_vec [i].id_maps = ALLOCATE_VECTOR (unsigned, num_ids);
		for (j = 0; j < num_ids; j ++) {
			shapes_vec [i].id_maps [j] = UINT_MAX;
		}
    }
    *num_shapes_ref = num_shapes;
    return (shapes_vec);
}

static NameEntryP*
capsule_read_external_names_1(CapsuleP capsule,
							  ShapeDataP shape,
							  unsigned *num_ref)
{
    TDFReaderP  reader         = capsule_reader (capsule);
    unsigned    num_this_shape = tdf_read_int (reader);
    ShapeEntryP entry          = shape->entry;
    NStringP    key            = shape_entry_key (entry);
    NameTableP  table          = shape_entry_name_table (entry);
    unsigned    num_ids        = shape->num_ids;
    unsigned   *id_maps        = shape->id_maps;
    NameEntryP *names_vec      = ALLOCATE_VECTOR (NameEntryP, num_this_shape);
    unsigned    i;

    debug_info_r_start_shape_names (key, num_this_shape);
    for (i = 0; i < num_this_shape; i ++) {
		unsigned   id = tdf_read_int (reader);
		NameKeyT   name;
		NameEntryP name_entry;

		tdf_read_name (reader, &name);
		if (id >= num_ids) {
			MSG_name_id_out_of_range (capsule, key, &name, id, num_ids);
			THROW (XX_capsule_error);
			UNREACHED;
		}
		name_entry = name_table_add (table, &name, entry);
		if (id_maps [id] != UINT_MAX) {
			MSG_name_id_used_multiple_times (capsule, key, &name, id);
			THROW (XX_capsule_error);
			UNREACHED;
		}
		names_vec [i] = name_entry;
		id_maps [id]  = name_entry_id (name_entry);
		debug_info_r_name (&name, id, id_maps [id],
						   name_entry_key (name_entry));
		name_key_destroy (&name);
    }
    *num_ref = num_this_shape;
    return (names_vec);
}

static NameDataP
capsule_read_external_names(CapsuleP capsule,
							unsigned num_shapes,
							ShapeDataP shapes_vec)
{
    TDFReaderP reader = capsule_reader (capsule);
    NameDataT *names_vec_vec;
    unsigned   num_names;
    unsigned   i;

    if ((num_names = tdf_read_int (reader)) != num_shapes) {
		MSG_shape_and_name_count_mismatch (capsule, num_shapes, num_names);
		THROW (XX_capsule_error);
		UNREACHED;
    }
    debug_info_r_start_names (num_names);
    names_vec_vec = ALLOCATE_VECTOR (NameDataT, num_names);
    for (i = 0; i < num_names; i ++) {
		ShapeDataP  shape = &(shapes_vec [i]);
		NameEntryP *names_vec;
		unsigned    num_this_shape;

		names_vec = capsule_read_external_names_1 (capsule, shape,
												   &num_this_shape);
		names_vec_vec [i].names_vec = names_vec;
		names_vec_vec [i].num_names = num_this_shape;
    }
    return (names_vec_vec);
}

/*--------------------------------------------------------------------------*/

static unsigned
capsule_get_token_index(ShapeTableP shapes,
						unsigned num_shapes,
						ShapeDataP shapes_vec)
{
    ShapeEntryP token_entry = shape_table_get_token_entry (shapes);
    unsigned    i;

    for (i = 0; i < num_shapes; i ++) {
		if (shapes_vec [i].entry == token_entry) {
			return (i);
		}
    }
    return (UINT_MAX);
}

static unsigned
capsule_get_tag_index(ShapeTableP shapes,
					  unsigned num_shapes,
					  ShapeDataP shapes_vec)
{
    ShapeEntryP tag_entry = shape_table_get_tag_entry (shapes);
    unsigned    i;

    for (i = 0; i < num_shapes; i ++) {
		if (shapes_vec [i].entry == tag_entry) {
			return (i);
		}
    }
    return (UINT_MAX);
}

static void
capsule_read_usage(CapsuleP capsule, NameDataP entry,
				   BoolT need_dec, BoolT no_mult,
				   NStringP shape_key)
{
    TDFReaderP  reader    = capsule_reader (capsule);
    unsigned    num_names = entry->num_names;
    NameEntryP *names_vec = entry->names_vec;
    unsigned    i;

    debug_info_r_start_usages (shape_key, num_names);
    for (i = 0; i < num_names; i ++) {
		unsigned   use        = tdf_read_int (reader);
		NameEntryP name_entry = names_vec [i];
		unsigned   name_use   = name_entry_get_use (name_entry);
		NameKeyP   key        = name_entry_key (name_entry);

		if (use & ~(U_USED | U_DECD | U_DEFD | U_MULT)) {
			MSG_bad_usage (capsule, shape_key, key, use);
			THROW (XX_capsule_error);
			UNREACHED;
		} else if (no_mult && (use & U_MULT)) {
			MSG_illegally_multiply_defined (capsule, shape_key, key);
			THROW (XX_capsule_error);
			UNREACHED;
		} else if (need_dec &&
				   (((use & (U_DEFD | U_DECD)) == U_DEFD) ||
					((use & (U_MULT | U_DECD)) == U_MULT))) {
			MSG_defined_but_not_declared (capsule, shape_key, key);
			THROW (XX_capsule_error);
			UNREACHED;
		}
		if ((use & U_DEFD) && (name_use & U_DEFD)) {
			CapsuleP definition = name_entry_get_definition (name_entry);
			char *prev_name = capsule_name (definition);

			MSG_multiply_defined (capsule, shape_key, key, prev_name);
		} else if ((use & U_MULT) && (name_use & U_MULT) &&
				   (!(use & U_DEFD)) && (!(name_use & U_DEFD))) {
			name_entry_set_definition (name_entry, NIL (CapsuleP));
		} else if ((use & U_DEFD) ||
				   ((use & U_MULT) && (!(name_use & (U_MULT | U_DEFD))))) {
			name_entry_set_definition (name_entry, capsule);
		}
		debug_info_r_usage (use, name_use, key);
		name_entry_merge_use (name_entry, use);
    }
}

static void
capsule_read_tld_type_0_unit(CapsuleP capsule,
							 ShapeTableP shapes,
							 unsigned num_shapes,
							 ShapeDataP shapes_vec,
							 NameDataP names_vec_vec)
{
    unsigned i;

    i = capsule_get_token_index (shapes, num_shapes, shapes_vec);
    if (i != UINT_MAX) {
		NStringP key = shape_entry_key (shapes_vec [i].entry);

		capsule_read_usage (capsule, &(names_vec_vec [i]), FALSE, TRUE, key);
    }
    i = capsule_get_tag_index (shapes, num_shapes, shapes_vec);
    if (i != UINT_MAX) {
		NStringP key = shape_entry_key (shapes_vec [i].entry);

		capsule_read_usage (capsule, &(names_vec_vec [i]), TRUE, FALSE, key);
    }
}

static void
capsule_read_tld_type_1_unit(CapsuleP capsule,
							 ShapeTableP shapes,
							 unsigned num_shapes,
							 ShapeDataP shapes_vec,
							 NameDataP names_vec_vec)
{
    unsigned i;
    unsigned token = capsule_get_token_index (shapes, num_shapes, shapes_vec);
    unsigned tag   = capsule_get_tag_index (shapes, num_shapes, shapes_vec);

    for (i = 0; i < num_shapes; i ++) {
		NStringP key = shape_entry_key (shapes_vec [i].entry);

		capsule_read_usage (capsule, &(names_vec_vec [i]), i == tag,
							i == token, key);
    }
}

/*--------------------------------------------------------------------------*/

typedef void (*UnitTypeProcP)(CapsuleP, ShapeTableP, unsigned, ShapeDataP, NameDataP);

static UnitTypeProcP capsule_type_jump_table [] = {
    capsule_read_tld_type_0_unit,
    capsule_read_tld_type_1_unit
};

#define CAPSULE_TYPE_JUMP_TABLE_SIZE \
	((unsigned) (sizeof (capsule_type_jump_table) / \
		     sizeof (UnitTypeProcP)))

/*--------------------------------------------------------------------------*/

static void
capsule_read_tld_unit_header(CapsuleP capsule,
							 NStringP unit_set)
{
    TDFReaderP reader = capsule_reader (capsule);

    if (tdf_read_int (reader) != 1) {
		MSG_too_many_tld_units (capsule);
		THROW (XX_capsule_error);
		UNREACHED;
    }
    debug_info_r_start_units (unit_set, (unsigned) 1);
    debug_info_r_start_unit (unit_set, (unsigned) 1, (unsigned) 1);
    if (tdf_read_int (reader) != 0) {
		MSG_too_many_tld_unit_counts (capsule);
		THROW (XX_capsule_error);
		UNREACHED;
    }
    debug_info_r_start_counts ((unsigned) 0);
    if (tdf_read_int (reader) != 0) {
		MSG_too_many_tld_unit_mappings (capsule);
		THROW (XX_capsule_error);
		UNREACHED;
    }
    debug_info_r_start_maps ((unsigned) 0);
    capsule_unit_length = tdf_read_int (reader);
    debug_info_r_unit_body (capsule_unit_length);
    tdf_read_align (reader);
    capsule_unit_offset = tdf_reader_byte (reader);
}

static void
capsule_read_tld_unit_trailer(CapsuleP capsule)
{
    TDFReaderP reader  = capsule_reader (capsule);
    unsigned   offset  = tdf_reader_byte (reader);
    unsigned   correct = (capsule_unit_offset + capsule_unit_length);

    tdf_read_align (reader);
    if (correct != offset) {
		MSG_tld_unit_wrong_size (capsule, correct, offset);
		THROW (XX_capsule_error);
		UNREACHED;
    }
}

static void
capsule_read_tld2_units(CapsuleP capsule,
						ShapeTableP shapes,
						unsigned num_shapes,
						ShapeDataP shapes_vec,
						NameDataP names_vec_vec)
{
    UnitEntryP tld2_entry = capsule_unit_sets [capsule_tld2_index].entry;
    NStringP   key        = unit_entry_key (tld2_entry);

    ASSERT (capsule_tld2_index != UINT_MAX);
    capsule_read_tld_unit_header (capsule, key);
    debug_info_r_tld_version ((unsigned) 0);
    capsule_read_tld_type_0_unit (capsule, shapes, num_shapes, shapes_vec,
								  names_vec_vec);
    capsule_read_tld_unit_trailer (capsule);
}

static void
capsule_read_tld_units(CapsuleP capsule, ShapeTableP shapes,
					   unsigned num_shapes,
					   ShapeDataP shapes_vec,
					   NameDataP names_vec_vec)
{
    TDFReaderP reader    = capsule_reader (capsule);
    UnitEntryP tld_entry = capsule_unit_sets [capsule_tld_index].entry;
    NStringP   key       = unit_entry_key (tld_entry);
    unsigned   unit_type;

    capsule_read_tld_unit_header (capsule, key);
    unit_type = tdf_read_int (reader);
    if (unit_type >= CAPSULE_TYPE_JUMP_TABLE_SIZE) {
		MSG_unknown_tld_unit_type (capsule, unit_type);
		THROW (XX_capsule_error);
		UNREACHED;
    }
    debug_info_r_tld_version (unit_type);
    (*(capsule_type_jump_table [unit_type])) (capsule, shapes, num_shapes,
											  shapes_vec, names_vec_vec);
    capsule_read_tld_unit_trailer (capsule);
}

static MapEntryP*
capsule_read_unit_counts(CapsuleP capsule,
			 unsigned num_shapes,
			 ShapeDataP shapes_vec,
			 unsigned num_counts,
			 UnitEntryP unit_entry,
			 UnitP unit, unsigned unit_num)
{
    if ((num_counts != 0) && (num_counts != num_shapes)) {
		MSG_unit_count_num_mismatch (capsule, num_counts, num_shapes, unit_num,
								   unit_entry_key (unit_entry));
		THROW (XX_capsule_error);
		UNREACHED;
    }
    debug_info_r_start_counts (num_counts);
    if (num_counts != 0) {
		TDFReaderP reader  = capsule_reader (capsule);
		MapTableP  table   = unit_map_table (unit);
		MapEntryP *entries = ALLOCATE_VECTOR (MapEntryP, num_counts);
		unsigned   i;

		for (i = 0; i < num_counts; i ++) {
			unsigned  count = tdf_read_int (reader);
			NStringP  key   = shape_entry_key (shapes_vec [i].entry);
			MapEntryP entry = map_table_add (table, key, count);

			debug_info_r_count (count, key);
			entries [i] = entry;
		}
		return (entries);
    } else {
		return (NIL (MapEntryP *));
    }
}

static void
capsule_read_unit_maps(CapsuleP capsule, unsigned num_counts,
					   ShapeDataP shapes_vec,
					   UnitEntryP unit_entry,
					   unsigned unit_num,
					   MapEntryP *entries)
{
    TDFReaderP reader          = capsule_reader (capsule);
    unsigned   num_link_shapes = tdf_read_int (reader);
    unsigned   i;

    if (num_link_shapes != num_counts) {
		MSG_unit_mapping_num_mismatch (capsule, num_link_shapes, num_counts,
									 unit_num, unit_entry_key (unit_entry));
		THROW (XX_capsule_error);
		UNREACHED;
    }
    debug_info_r_start_maps (num_link_shapes);
    for (i = 0; i < num_link_shapes; i ++) {
		unsigned    num_links   = tdf_read_int (reader);
		ShapeEntryP shape_entry = (shapes_vec [i].entry);
		NStringP    key         = shape_entry_key (shape_entry);
		unsigned    j;

		map_entry_set_num_links (entries [i], num_links);
		debug_info_r_start_shape_maps (key, num_links);
		for (j = 0; j < num_links; j ++) {
			unsigned  internal = tdf_read_int (reader);
			unsigned  external = tdf_read_int (reader);
			unsigned  num_ids  = shapes_vec [i].num_ids;
			unsigned *id_maps  = shapes_vec [i].id_maps;

			if (external >= num_ids) {
				MSG_id_out_of_range (capsule, external, num_ids, key, unit_num,
								   unit_entry_key (unit_entry));
				THROW (XX_capsule_error);
				UNREACHED;
			}
			if (id_maps [external] == UINT_MAX) {
				unsigned id = shape_entry_next_id (shape_entry);

				id_maps [external] = id;
			}
			debug_info_r_map (internal, external, id_maps [external]);
			external = id_maps [external];
			map_entry_set_link (entries [i], j, internal, external);
		}
    }
}

static void
capsule_read_unit(CapsuleP capsule, unsigned num_shapes,
				  ShapeDataP shapes_vec,
				  UnitEntryP unit_entry,
				  unsigned unit_num)
{
    TDFReaderP reader     = capsule_reader (capsule);
    unsigned   num_counts = tdf_read_int (reader);
    UnitP      unit       = unit_entry_add_unit (unit_entry, num_counts);
    MapEntryP *entries;
    unsigned   size;
    NStringT   nstring;

    entries = capsule_read_unit_counts (capsule, num_shapes, shapes_vec,
					num_counts, unit_entry, unit,
					unit_num);

    capsule_read_unit_maps (capsule, num_counts, shapes_vec, unit_entry,
			    unit_num, entries);
    size = tdf_read_int (reader);
    debug_info_r_unit_body (size);
    nstring_init_length (&nstring, size);
    tdf_read_bytes (reader, &nstring);
    unit_set_contents (unit, &nstring);
    DEALLOCATE (entries);
}

static void
capsule_read_units(CapsuleP capsule, unsigned num_shapes,
				   ShapeDataP shapes_vec,
				   UnitEntryP unit_entry)
{
    TDFReaderP reader    = capsule_reader (capsule);
    unsigned   num_units = tdf_read_int (reader);
    unsigned   i;

    debug_info_r_start_units (unit_entry_key (unit_entry), num_units);
    for (i = 0; i < num_units; i ++) {
		debug_info_r_start_unit (unit_entry_key (unit_entry), i + 1,
					 num_units);
		capsule_read_unit (capsule, num_shapes, shapes_vec,
				   unit_entry, i);
    }
}

static void
capsule_read_unit_sets(CapsuleP capsule, unsigned num_unit_sets,
					   UnitEntryP *units_vec,
					   ShapeTableP shapes,
					   unsigned num_shapes,
					   ShapeDataP shapes_vec,
					   NameDataP names_vec_vec)
{
    TDFReaderP reader       = capsule_reader (capsule);
    UnitEntryP tld_entry    = capsule_unit_sets [capsule_tld_index].entry;
    UnitEntryP tld2_entry   = ((capsule_tld2_index == UINT_MAX) ?
							   NIL (UnitEntryP) :
							   capsule_unit_sets [capsule_tld2_index].entry);
    unsigned   num_units;
    unsigned   i;

    if ((num_units = tdf_read_int (reader)) != num_unit_sets) {
		MSG_unit_set_count_mismatch (capsule, num_unit_sets, num_units);
		THROW (XX_capsule_error);
		UNREACHED;
    }
    debug_info_r_start_unit_sets (num_units);
    for (i = 0; i < num_units; i ++) {
		if (units_vec [i] == tld_entry) {
			capsule_read_tld_units (capsule, shapes, num_shapes, shapes_vec,
									names_vec_vec);
		} else if (units_vec [i] == tld2_entry) {
			capsule_read_tld2_units (capsule, shapes, num_shapes, shapes_vec,
									 names_vec_vec);
		} else {
			capsule_read_units (capsule, num_shapes, shapes_vec,
								units_vec [i]);
		}
    }
}

/*--------------------------------------------------------------------------*/

static void
capsule_write_header(CapsuleP capsule)
{
    TDFWriterP writer      = capsule_writer (capsule);
    NStringP   const_magic = capsule_magic ();

    tdf_write_bytes (writer, const_magic);
    ASSERT (capsule_major_version >= 4);
    tdf_write_int (writer, capsule_major_version);
    tdf_write_int (writer, capsule_minor_version);
    debug_info_w_versions (capsule_major_version, capsule_minor_version);
    tdf_write_align (writer);
}

/*--------------------------------------------------------------------------*/

void
capsule_read_unit_set_file(char *name)
{
    IStreamT istream;

    ASSERT (capsule_unit_sets == NIL (UnitSetP));
    if (!istream_open (&istream, name)) {
		MSG_cant_open_unit_set_file (name);
		UNREACHED;
    }
    capsule_read_unit_set_file_1 (&istream);
    istream_close (&istream);
}

CapsuleP
capsule_create_stream_input(char *name)
{
    CapsuleP capsule = ALLOCATE (CapsuleT);

    capsule->type = CT_INPUT;
    if (!tdf_reader_open (capsule_reader (capsule), name)) {
		DEALLOCATE (capsule);
		return (NIL (CapsuleP));
    }
    capsule->name     = name;
    capsule->complete = FALSE;
    return (capsule);
}

CapsuleP
capsule_create_string_input(char *name,
							NStringP contents)
{
    CapsuleP capsule = ALLOCATE (CapsuleT);

    capsule->type     = CT_INPUT;
    tdf_reader_open_string (capsule_reader (capsule), name, contents);
    capsule->name     = name;
    capsule->complete = FALSE;
    return (capsule);
}

CapsuleP
capsule_create_stream_output(char *name)
{
    CapsuleP capsule = ALLOCATE (CapsuleT);

    capsule->type = CT_OUTPUT;
    if (!tdf_writer_open (capsule_writer (capsule), name)) {
		DEALLOCATE (capsule);
		return (NIL (CapsuleP));
    }
    capsule->name = name;
    return (capsule);
}

char *
capsule_name(CapsuleP capsule)
{
    return (capsule->name);
}

unsigned
capsule_byte(CapsuleP capsule)
{
    return (tdf_reader_byte (capsule_reader (capsule)));
}

void
capsule_read(CapsuleP capsule, UnitTableP units,
			 ShapeTableP shapes)
{
    ASSERT (capsule->type == CT_INPUT);
    capsule_setup (units);
    HANDLE {
		UnitEntryP *units_vec;
		unsigned    num_unit_sets;
		ShapeDataP  shapes_vec;
		unsigned    num_shapes;
		NameDataP   names_vec_vec;
		unsigned    i;

		debug_info_r_start_capsule (capsule_name (capsule));
		capsule_read_header (capsule);
		units_vec     = capsule_read_unit_set_names (capsule, units,
													 &num_unit_sets);
		shapes_vec    = capsule_read_shapes (capsule, shapes, &num_shapes);
		names_vec_vec = capsule_read_external_names (capsule, num_shapes,
													 shapes_vec);
		capsule_read_unit_sets (capsule, num_unit_sets, units_vec, shapes,
								num_shapes, shapes_vec, names_vec_vec);
		tdf_read_eof (capsule_reader (capsule));
		debug_info_r_end_capsule ();
		DEALLOCATE (units_vec);
		for (i = 0; i < num_shapes; i ++) {
			DEALLOCATE (shapes_vec [i].id_maps);
			DEALLOCATE (names_vec_vec [i].names_vec);
		}
		DEALLOCATE (shapes_vec);
		DEALLOCATE (names_vec_vec);
		capsule->complete = TRUE;
    } WITH {
		ExceptionP exception = EXCEPTION_EXCEPTION ();

		debug_info_r_abort_capsule ();
		if ((exception != XX_capsule_error) &&
			(exception != XX_tdf_read_error)) {
			RETHROW ();
		}
    } END_HANDLE
		  }

void
capsule_store_contents(CapsuleP capsule)
{
    if (capsule->complete) {
		TDFReaderP reader = capsule_reader (capsule);
		unsigned   length = tdf_reader_byte (reader);

		nstring_init_length (&(capsule->contents), length);
		tdf_reader_rewind (reader);
		tdf_read_bytes (reader, &(capsule->contents));
		tdf_read_eof (reader);
    }
}

NStringP
capsule_contents(CapsuleP capsule)
{
    return (&(capsule->contents));
}

void
capsule_set_index(CapsuleP capsule, unsigned i)
{
    capsule->capsule_index = i;
}

unsigned
capsule_get_index(CapsuleP capsule)
{
    return (capsule->capsule_index);
}

void
capsule_write(CapsuleP capsule, UnitTableP units,
			  ShapeTableP shapes)
{
    TDFWriterP      writer     = capsule_writer (capsule);
    UnitEntryP      tld_entry  = capsule_unit_sets [capsule_tld_index].entry;
    unsigned        num_shapes = 0;
    UnitSetClosureT unit_set_closure;
    unsigned        i;

    debug_info_w_start_capsule (capsule_name (capsule));
    capsule_write_header (capsule);
    unit_set_closure.num_unit_sets = 1;
    unit_set_closure.shapes        = shapes;
    unit_table_iter (units, unit_entry_do_count, (void *) &unit_set_closure);
    debug_info_w_start_unit_decs (unit_set_closure.num_unit_sets);
    tdf_write_int (writer, unit_set_closure.num_unit_sets);
    for (i = 0; i < capsule_num_unit_sets; i ++) {
		UnitEntryP entry = capsule_unit_sets [i].entry;

		unit_entry_write_unit_set (entry, tld_entry, writer);
    }
    shape_table_iter (shapes, shape_entry_do_count, (void *) &num_shapes);
    debug_info_w_start_shapes (num_shapes);
    tdf_write_int (writer, num_shapes);
    shape_table_iter (shapes, shape_entry_write_shape, (void *) writer);
    debug_info_w_start_names (num_shapes);
    tdf_write_int (writer, num_shapes);
    shape_table_iter (shapes, shape_entry_write_externs, (void *) writer);
    debug_info_w_start_unit_sets (unit_set_closure.num_unit_sets);
    tdf_write_int (writer, unit_set_closure.num_unit_sets);
    for (i = 0; i < capsule_num_unit_sets; i ++) {
		UnitEntryP entry = capsule_unit_sets [i].entry;

		if (entry == tld_entry) {
			unit_entry_write_tld_unit (entry, shapes, writer);
		} else {
			unit_entry_write_units (entry, shapes, num_shapes, writer);
		}
    }
    debug_info_w_end_capsule ();
}

void
capsule_close(CapsuleP capsule)
{
    switch (capsule->type) EXHAUSTIVE {
	case CT_INPUT:
		tdf_reader_close (capsule_reader (capsule));
		break;
	case CT_OUTPUT:
		tdf_writer_close (capsule_writer (capsule));
		break;
    }
}

unsigned
capsule_get_major_version()
{
    return (capsule_major_version);
}

void
capsule_set_major_version(unsigned major)
{
    capsule_major_version = major;
}

unsigned
capsule_get_minor_version()
{
    return (capsule_minor_version);
}

/*
 * Local variables(smf):
 * eval: (include::add-path-entry "../os-interface" "../library")
 * eval: (include::add-path-entry "../generated")
 * end:
 **/
