#ifdef ORCAD_TESTER
#	include "tester/read_fio.h"
#else
#	include "read_fio.h"
#endif
#include "read_common.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

static const unsigned char orcad_magic[] = { 0xFF, 0xE4, 0x5C, 0x39 };

long orcad_read_header(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_header* const hdr)
{
	if(0>(offs=orcad_read_field_u8(rctx, offs, &hdr->type)))
	{
		fprintf(stderr, "Error: Could not read header type @ 0x%lx\n",
			offs);
		return -1;
	}

	if(0>(offs=orcad_read_field_u32(rctx, offs, &hdr->size)))
	{
		fprintf(stderr, "Error: Could not read header size field\n");
		return -1;
	}

	if(0>(offs=orcad_read_field_u32(rctx, offs, &hdr->unknown)))
	{
		fprintf(stderr, "Error: Could not read header's unknown field\n");
		return -1;
	}

	return offs;
}

/* find the magic number and validate additional data (size of the last */
/* header is stored in the 'size' field of predecessor header) */
/* | TYPE[1] ... MAGIC[4] SOME_LENGTH[4] ... | */
/* <--------------- hdr_size ----------------> */
static int orcad_check_last_header(io_orcad_rctx_t* const rctx, long offs,
	const orcad_uint32_t hdr_size, struct orcad_namemapping_info* const nmi)
{
	char magic_data[sizeof(orcad_magic)];
	orcad_uint32_t some_len;

	/* expected header end */
	const long end = offs + hdr_size;

	/* the last valid position of the magic number (keep in mind, we always */
	/* have a 4-byte size field *after* the magic number) */
	const long last = offs + hdr_size - sizeof(orcad_magic) - 1;

	const long extra_start = offs + 1;

	/* skip type (1 byte) and a 16-bit number, which is always there */
	/* (2 bytes), but its purpose is not known */
	offs += 3;

	/* fetch initial buffer (read 1 byte less then needed, see loop below) */

	if(0!=fio_fseek(rctx, offs))
	{
		fprintf(stderr, "Error: Seek to magic (offs 0x%lx) failed\n", offs);
		return -1;
	}

	if((sizeof(orcad_magic)-1)!=fio_fread(rctx, magic_data+1,
		sizeof(orcad_magic)-1))
	{
		return -1;
	}

	offs += sizeof(orcad_magic)-1;

	while(offs<=last)
	{
		if(0!=fio_fseek(rctx, offs))
		{
			fprintf(stderr, "Error: Seek to magic (offs 0x%lx) failed\n",
				offs);
			return -1;
		}

		/* shift buffer */
		memmove(magic_data, magic_data+1, sizeof(orcad_magic)-1);

		/* read missing byte */
		if(1!=fio_fread(rctx, &magic_data[sizeof(orcad_magic)-1], 1))
		{
			return -1;
		}

		++offs;

		if(0==memcmp(magic_data, orcad_magic, sizeof(orcad_magic)))
		{
			if(0!=fio_fseek(rctx, offs))
			{
				fprintf(stderr, "Error: Seek to magic length info "
					"(offs 0x%lx) failed\n", offs);
				return -1;
			}

			if(0<=orcad_read_field_u32(rctx, offs, &some_len) &&
				end==(offs+4+some_len))
			{
				nmi->offs = extra_start;
				nmi->size = (offs - sizeof(orcad_magic)) - extra_start;
				return 1;
			}
		}
	}

	return 0;
}

/* Format: |TYPE[1] NMI_COUNT[2] NMI[4]...| */
long orcad_parse_old_header(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_header* const out_hdr,
	struct orcad_namemapping_info* const nmi)
{
	orcad_uint8_t n_raw[2];

	out_hdr->size = 0;

	/* skip type, it is already loaded */
	++offs;

	if(0!=fio_fseek(rctx, offs))
	{
		fprintf(stderr, "Error: Seek to old-header's NMI (offs %ld) failed\n",
			offs);
		return -1;
	}

	/* read NMI count */

	if(sizeof(n_raw)!=fio_fread(rctx, (char*)&n_raw[0], sizeof(n_raw)))
	{
		fprintf(stderr, "Error: Could not read NMI count\n");
		return -1;
	}

	nmi->size = (((orcad_uint16_t)n_raw[1]) << 8) | n_raw[0];

	/* TODO: this 0xFFFF thing is not 100% certain */
	if(0<nmi->size && 0xFFFF!=nmi->size)
	{
		nmi->offs = offs;
		nmi->size *= 4;

		offs += nmi->size;

		nmi->size += sizeof(n_raw);
	}

	offs += sizeof(n_raw);

	return offs;
}

/* Parse header or headers. Returns file offset after the header(s), and */
/* returns the type and the remaining length in 'out_hdr'. */
long orcad_parse_header(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_header* const out_hdr,
	struct orcad_namemapping_info* const nmi, int allow_old_style)
{
	struct orcad_header hdr;
	int extra_headers;
	long save_offs1; /* save offset after primary header */
	long save_offsN; /* save offset after the N-th header */
	int res;

	const long offs0 = offs;

	memset(nmi, 0, sizeof(*nmi));

	if(0>(offs=orcad_read_header(rctx, offs, out_hdr)))
	{
		fprintf(stderr, "Error: Could not read object primary header\n");
		return -1;
	}

	if(allow_old_style && 2>=rctx->ver_major)
	{
		return orcad_parse_old_header(rctx, offs0, out_hdr, nmi);
	}

	/* [pos]: after the primary header */

	save_offs1 = offs;

	if(sizeof(hdr.type)!=fio_fread(rctx, (char*)&hdr.type, sizeof(hdr.type)))
	{
		/* probably a huge problem (way too early EOF), but delegate the */
		/* problem to the caller */
		if(0!=fio_fseek(rctx, save_offs1))
		{
			fprintf(stderr, "Error: Seek to payload (offs %ld) failed\n",
				save_offs1);
			return -1;
		}

		return save_offs1;
	}

	if(hdr.type!=out_hdr->type)
	{
		int res = orcad_check_last_header(rctx, offs0, out_hdr->size, nmi);

		if(0<res || (3<=rctx->ver_major && 0==res))
		{
			/* we have only the primary (new-style) header */
			goto primary_header_only;
		}

		if(allow_old_style)
		{
			return orcad_parse_old_header(rctx, offs0, out_hdr, nmi);
		}

		return -1;
	}

	/* we must set offset back to offs0, because in some rare cases, only */
	/* the primary header and the last header are present; this way the */
	/* very first header will be read twice, but the last_header will be */
	/* properly recognized (that's why we couldn't continue from save_offs1) */
	save_offsN = offs0;

	for(extra_headers=6;0<extra_headers;--extra_headers)
	{
		if(0!=fio_fseek(rctx, save_offsN))
		{
			fprintf(stderr, "Error: Seek to aux-header at offs %ld failed\n",
				save_offsN);
			return -1;
		}

		offs = save_offsN;

		if(0>(offs=orcad_read_header(rctx, offs, &hdr)))
		{
			fprintf(stderr, "Error: Could not read N-th header\n");
			return -1;
		}

		/* [pos]: after the N-th header */
		/* check type; is there a next header? */

		save_offsN = offs;

		if(sizeof(hdr.type)!=fio_fread(rctx, (char*)&hdr.type,
			sizeof(hdr.type)) || hdr.type!=out_hdr->type)
		{
			goto primary_header_only;
		}

		if(0>(res=orcad_check_last_header(rctx, offs, hdr.size, nmi)))
		{
			return -1;
		}

		if(res)
		{
			offs += hdr.size;

			if(0!=fio_fseek(rctx, offs))
			{
				fprintf(stderr, "Error: Seek to payload (offs %ld) failed\n",
					offs);
				return -1;
			}

			out_hdr->size -= (offs - save_offs1);

			return offs;
		}
	}

primary_header_only:
	if(0!=fio_fseek(rctx, save_offs1))
	{
		fprintf(stderr, "Error: Seek after primary header (offs %ld) failed\n",
			save_offs1);
		return -1;
	}

	return save_offs1;
}

long orcad_peek_field_u8(io_orcad_rctx_t* const rctx, long offs,
	orcad_uint8_t* const out)
{
	unsigned char raw[1];

	if(sizeof(raw)!=fio_fread(rctx, (char*)&raw[0], sizeof(raw)))
	{
		fprintf(stderr, "Error: Could not peek 8-bit field\n");
		return -1;
	}

	*out = raw[0];

	if(0!=fio_fseek(rctx, offs))
	{
		fprintf(stderr, "Error: Could not seek back to 0x%lx in peek\n", offs);
		return -1;
	}

	return offs;
}

long orcad_read_field_u8(io_orcad_rctx_t* const rctx, long offs,
	orcad_uint8_t* const out)
{
	unsigned char raw[1];

	if(sizeof(raw)!=fio_fread(rctx, (char*)&raw[0], sizeof(raw)))
	{
		fprintf(stderr, "Error: Could not read 8-bit field\n");
		return -1;
	}

	*out = raw[0];
	offs += sizeof(raw);

	return offs;
}

long orcad_read_field_u16(io_orcad_rctx_t* const rctx, long offs,
	orcad_uint16_t* const out)
{
	unsigned char raw[2];

	if(sizeof(raw)!=fio_fread(rctx, (char*)&raw[0], sizeof(raw)))
	{
		fprintf(stderr, "Error: Could not read 16-bit field\n");
		return -1;
	}

	offs += sizeof(raw);
	*out = ((orcad_uint16_t)raw[0]) | (((orcad_uint16_t)raw[1]) << 8);

	return offs;
}

long orcad_read_field_i16(io_orcad_rctx_t* const rctx, long offs,
	orcad_int16_t* const out)
{
	orcad_int16_t  stmp;
	orcad_uint16_t tmp;

	if(0>(offs=orcad_read_field_u16(rctx, offs, &tmp)))
	{
		return -1;
	}

	stmp = 0;

	if(0x8000 & tmp)
	{
		stmp = ~stmp;

		/* shift left by 8 bits twice, to elude warning */
		stmp <<= 8;
	}

	*out = (stmp << 8) | tmp;

	return offs;
}

long orcad_read_field_u32(io_orcad_rctx_t* const rctx, long offs,
	orcad_uint32_t* const out)
{
	unsigned char raw[4];

	if(sizeof(raw)!=fio_fread(rctx, (char*)&raw[0], sizeof(raw)))
	{
		fprintf(stderr, "Error: Could not read 32-bit field\n");
		return -1;
	}

	offs += sizeof(raw);
	*out = ((orcad_uint32_t)raw[0]) | (((orcad_uint32_t)raw[1]) << 8) |
		(((orcad_uint32_t)raw[2]) << 16) | (((orcad_uint32_t)raw[3]) << 24);

	return offs;
}

long orcad_read_field_i32(io_orcad_rctx_t* const rctx, long offs,
	orcad_int32_t* const out)
{
	orcad_int32_t  stmp;
	orcad_uint32_t tmp;

	if(0>(offs=orcad_read_field_u32(rctx, offs, &tmp)))
	{
		return -1;
	}

	stmp = 0;

	if(0x80000000 & tmp)
	{
		stmp = ~stmp;

		/* shift left by 16 bits twice, to elude warning */
		stmp <<= 16;
	}

	*out = (stmp << 16) | tmp;

	return offs;
}

long orcad_skip_field_8(io_orcad_rctx_t* const rctx, long offs,
	const orcad_uint8_t expected)
{
	orcad_uint8_t tmp;

	if(0>(offs=orcad_read_field_u8(rctx, offs, &tmp)))
	{
		return -1;
	}

	if(expected!=tmp)
	{
		fprintf(stderr, "Error: Could not skip 8-bit field at 0x%lx: "
			"expected 0x%x, but got 0x%x!\n", (offs-sizeof(tmp)),
			(unsigned int)expected, (unsigned int)tmp);
		return -1;
	}

	return offs;
}

long orcad_skip_field_16(io_orcad_rctx_t* const rctx, long offs,
	const orcad_uint16_t expected)
{
	orcad_uint16_t tmp;

	if(0>(offs=orcad_read_field_u16(rctx, offs, &tmp)))
	{
		return -1;
	}

	if(expected!=tmp)
	{
		fprintf(stderr, "Error: Could not skip 16-bit field at 0x%lx: "
			"expected 0x%x, but got 0x%x!\n", (offs-sizeof(tmp)),
			(unsigned int)expected, (unsigned int)tmp);
		return -1;
	}

	return offs;
}

long orcad_skip_field_32(io_orcad_rctx_t* const rctx, long offs,
	const orcad_uint32_t expected)
{
	orcad_uint32_t tmp;

	if(0>(offs=orcad_read_field_u32(rctx, offs, &tmp)))
	{
		return -1;
	}

	if(expected!=tmp)
	{
		fprintf(stderr, "Error: Could not skip 32-bit field at 0x%lx: "
			"expected 0x%lx, but got 0x%lx!\n", (offs-sizeof(tmp)),
			(unsigned long)expected, (unsigned long)tmp);
		return -1;
	}

	return offs;
}

long orcad_read_string(io_orcad_rctx_t* const rctx, long offs, char** buf,
	orcad_uint16_t len)
{
	char* const str = (char*)malloc(len+1);

	*buf = str;

	if(NULL==str)
	{
		fprintf(stderr, "Error: Could not allocate string\n");
		return -1;
	}

	if((len+1)!=fio_fread(rctx, str, len+1))
	{
		fprintf(stderr, "Error: Unexpected EOF while reading string\n");
		return -1;
	}

	if(0!=str[len])
	{
		fprintf(stderr, "Error: String at 0x%lx is not zero-terminated\n",
			offs);
		return -1;
	}

	return offs + len+1;
}

/* read zero-terminated string which is prefixed with a 16-bit length field */
long orcad_read_string2(io_orcad_rctx_t* const rctx, long offs, char** buf)
{
	orcad_uint16_t len;

	if(0>(offs=orcad_read_field_u16(rctx, offs, &len)))
	{
		fprintf(stderr, "Error: Could not read string length field\n");
		return -1;
	}

	return orcad_read_string(rctx, offs, buf, len);
}

long orcad_skip_magic(io_orcad_rctx_t* const rctx, long offs)
{
	char data[sizeof(orcad_magic)+4];
	char* ptr;

	if(sizeof(data)!=fio_fread(rctx, data, sizeof(data)))
	{
		fprintf(stderr, "Error: Could not read magic data to skip\n");
		return -1;
	}

	if(0!=memcmp(data, orcad_magic, sizeof(orcad_magic)))
	{
		if(0!=fio_fseek(rctx, offs))
		{
			fprintf(stderr, "Error: Could not seek back to 0x%lx\n", offs);
			return -1;
		}

		return offs;
	}

	ptr = data + sizeof(orcad_magic);

	if(0!=ptr[0] || 0!=ptr[1] || 0!=ptr[2] || 0!=ptr[3])
	{
		fprintf(stderr, "Error: Word after magic is not zero\n");
		return -1;
	}

	return offs + sizeof(data);
}

int orcad_is_end_or_magic(io_orcad_rctx_t* const rctx, long offs, long end)
{
	char data[sizeof(orcad_magic)];
	size_t n;

	if(offs>=end)
	{
		return 1;
	}

	n = fio_fread(rctx, data, sizeof(data));
	fio_fseek(rctx, offs);

	return sizeof(data)==n &&
		0==memcmp(data, orcad_magic, sizeof(orcad_magic));
}

const char* orcad_type2str(const enum orcad_type type)
{
	switch(type)
	{
	case ORCAD_TYPE_INLINEPAGEOBJECT:   return "InlinePageObject";
	case ORCAD_TYPE_PROPERTIES:         return "Properties";
	case ORCAD_TYPE_SCHEMATIC:          return "Schematic";
	case ORCAD_TYPE_PAGE:               return "Page";
	case ORCAD_TYPE_PARTINST:           return "PartInst";
	case ORCAD_TYPE_PINCONNECTION:      return "PinConnection";
	case ORCAD_TYPE_WIRE:               return "Wire";
	case ORCAD_TYPE_PORT:               return "Port";
	case ORCAD_TYPE_SYMBOLGRAPHIC:      return "SymbolGraphic";
	case ORCAD_TYPE_SYMBOLPIN:          return "SymbolPin";
	case ORCAD_TYPE_SYMBOLPINMAPPING:   return "SymbolPinMapping";
	case ORCAD_TYPE_PINIDXMAPPING:      return "PinIdxMapping";
	case ORCAD_TYPE_GLOBALSYMBOL:       return "GlobalSymbol";
	case ORCAD_TYPE_PORTSYMBOL:         return "PortSymbol";
	case ORCAD_TYPE_OFFPAGECONNSYMBOL:  return "OffPageConnSymbol";
	case ORCAD_TYPE_GLOBAL:             return "Global";
	case ORCAD_TYPE_OFFPAGECONN:        return "OffPageConn";
	case ORCAD_TYPE_SYMBOLDISPLAYPROP:  return "SymbolDisplayProp";
	case ORCAD_TYPE_ALIAS:              return "Alias";
	case ORCAD_TYPE_NETPROP:            return "NetProp";
	case ORCAD_TYPE_GRAPHICBOXINST:     return "GraphicBoxInst";
	case ORCAD_TYPE_GRAPHICLINEINST:    return "GraphicLineInst";
	case ORCAD_TYPE_GRAPHICARCINST:     return "GraphicArcInst";
	case ORCAD_TYPE_GRAPHICELLIPSEINST: return "GraphicEllipseInst";
	case ORCAD_TYPE_GRAPHICPOLYGONINST: return "GraphicPolygonInst";
	case ORCAD_TYPE_GRAPHICTEXTINST:    return "GraphicTextInst";
	case ORCAD_TYPE_GRAPHICBITMAPINST:  return "GraphicBitmapInst";
	case ORCAD_TYPE_TITLEBLOCKSYMBOL:   return "TitleBlockSymbol";
	case ORCAD_TYPE_TITLEBLOCK:         return "TitleBlock";
	case ORCAD_TYPE_ERCSYMBOL:          return "ERCSymbol";
	case ORCAD_TYPE_BOOKMARKSYMBOL:     return "BookMarkSymbol";
	case ORCAD_TYPE_ERCSYMBOLINST:      return "ERCSymbolInst";
	case ORCAD_TYPE_BOOKMARKSYMBOLINST: return "BookMarkSymbolInst";
	case ORCAD_TYPE_GRAPHICBEZIERINST:  return "GraphicBezierInst";
	case ORCAD_TYPE_X_NETALIAS:         return "X-NetAlias";
	case ORCAD_TYPE_X_CACHE:            return "X-Cache";
	case ORCAD_TYPE_X_SYMBOLGROUP:      return "X-SymbolGroup";
	case ORCAD_TYPE_X_CACHESYMBOL:      return "X-CacheSymbol";
	case ORCAD_TYPE_X_CACHESYMVARIANT:  return "X-CacheSymVariant";
	case ORCAD_TYPE_X_LIBRARY:          return "X-Library";

	default: break;
	}

	return "?";
}

const char* orcad_primtype2str(const enum orcad_primtype primtype)
{
	switch(primtype)
	{
	case ORCAD_PRIMITIVE_TEXT:         return "Text";
	case ORCAD_PRIMITIVE_BITMAP:       return "Bitmap";
	case ORCAD_PRIMITIVE_LINE:         return "Line";
	case ORCAD_PRIMITIVE_RECT:         return "Rect";
	case ORCAD_PRIMITIVE_ARC:          return "Arc";
	case ORCAD_PRIMITIVE_ELLIPSE:      return "Ellipse";
	case ORCAD_PRIMITIVE_POLYGON:      return "Polygon";
	case ORCAD_PRIMITIVE_POLYLINE:     return "Polyline";
	case ORCAD_PRIMITIVE_BEZIER:       return "Bezier";
	case ORCAD_PRIMITIVE_SYMBOLVECTOR: return "SymbolVector";

	default: break;
	}

	return "?";
}

struct orcad_node* orcad_create_node__(io_orcad_rctx_t* const rctx,
	long* const p_offs, const size_t struct_size, const enum orcad_type type,
	struct orcad_node* const parent)
{
	struct orcad_header hdr;
	struct orcad_namemapping_info nmi;

	if(0>((*p_offs)=orcad_parse_header(rctx, *p_offs, &hdr, &nmi, 1)))
	{
		fprintf(stderr, "Error: Could not read header of %s\n",
			orcad_type2str(type));
		return NULL;
	}

	return orcad_create_node_from__(rctx, *p_offs, struct_size, type, &hdr,
		parent, &nmi);
}

static int read_namemappings(io_orcad_rctx_t* const rctx,
	const long orig_offs, struct orcad_node* const node)
{
	long offs = node->nmi.offs;

	orcad_uint16_t i;
	orcad_uint16_t num;
	struct orcad_namemapping* map;

	if(2>=node->nmi.size || 0==node->nmi.offs)
	{
		/* do nothing, just return */
		return 0;
	}

	if(0!=fio_fseek(rctx, node->nmi.offs))
	{
		fprintf(stderr, "Error: Could not seek to namemappings (0x%lx)\n",
			node->nmi.offs);
		return -1;
	}

	vread_u16(num);

	if(NULL==(map=node->namemappings=(struct orcad_namemapping*)calloc(num,
		sizeof(struct orcad_namemapping))))
	{
		fprintf(stderr, "Error: Could not allocate memory for namemappings\n");
		return -1;
	}

	for(i=0;i<num;++i)
	{
		vread_u32(map[i].name_idx);
		vread_u32(map[i].value_idx);
	}

	node->num_namemappings = num;

	/* seek back where we were */

	if(0!=fio_fseek(rctx, orig_offs))
	{
		fprintf(stderr, "Error: Could not seek to original offset (0x%lx)\n",
			orig_offs);
		return -1;
	}

	return 0;
}

void orcad_create_prim_callback__(struct orcad_prim* const prim)
{
	fprintf(stderr, "Create prim %s (0x%x) @0x%lx\n",
		orcad_primtype2str(prim->type), (unsigned int)prim->type, prim->offs);
}

static void orcad_create_node_callback__(struct orcad_node* const node)
{
	fprintf(stderr, "Create node %s (0x%x) @0x%lx\n",
		orcad_type2str(node->type), (unsigned int)node->type, node->offs);
}

struct orcad_node* orcad_create_node_from__(io_orcad_rctx_t* const rctx,
	const long offs, const size_t struct_size, const enum orcad_type type,
	const struct orcad_header* const p_hdr, struct orcad_node* const parent,
	struct orcad_namemapping_info* const nmi)
{
	struct orcad_node* node;

	if(type!=p_hdr->type)
	{
		fprintf(stderr,
			"Error: Object at 0x%lx expected to be 0x%x, but got 0x%x\n",
			offs, (unsigned int)type, (unsigned int)p_hdr->type);
		return NULL;
	}

	if(NULL==(node=calloc(1, struct_size)))
	{
		fprintf(stderr, "Error: Could not allocate node memory for %s\n",
			orcad_type2str(type));
		return NULL;
	}

	node->type = type;
	node->offs = offs;
	node->size = p_hdr->size;
	node->parent = parent;

	memcpy(&node->nmi, nmi, sizeof(*nmi));

	orcad_create_node_callback__(node);

	if(0!=read_namemappings(rctx, offs, node))
	{
		free(node);
		return NULL;
	}

	return node;
}

void orcad_error_backtrace__(struct orcad_node* node, const char* const msg)
{
	if(NULL!=msg)
	{
		fprintf(stderr, "Error: Could not %s\n", msg);
	}

	fprintf(stderr, "Backtrace:\n");

	while(NULL!=node)
	{
		fprintf(stderr, "  %s @0x%lx\n", orcad_type2str(node->type),
			node->offs);
		node = node->parent;
	}
}

long orcad_read_nodes__(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_node* const parent, struct orcad_node*** const p_array,
	size_t count, long (*const read_object)(io_orcad_rctx_t* const rctx,
		long offs, struct orcad_node* const parent,
		struct orcad_node** const out_node))
{
	struct orcad_node** array =
		(struct orcad_node**)calloc(count, sizeof(struct orcad_node*));

	if(NULL==array)
	{
		return -1;
	}

	*p_array = array;

	while(0<(count--))
	{
		if(0>(offs=read_object(rctx, offs, parent, array)))
		{
			return -1;
		}

		++array;
	}

	return offs;
}

long orcad_skip_objects(io_orcad_rctx_t* const rctx, long offs, int count)
{
	while(0<(count--))
	{
		if(0>(offs=orcad_skip_object(rctx, offs)))
		{
			return -1;
		}
	}

	return offs;
}

long orcad_skip_object(io_orcad_rctx_t* const rctx, long offs)
{
	/* first header contains the length we need */

	struct orcad_header hdr;

	if(2>=rctx->ver_major)
	{
		fprintf(stderr,
			"Error: Could not skip object at 0x%lx: old-style headers do not store size\n",
			offs);
		return -1;
	}

	if(0>(offs=orcad_read_header(rctx, offs, &hdr)))
	{
		fprintf(stderr, "Error: Could not read object header\n");
		return -1;
	}

	if(0==hdr.size)
	{
		fprintf(stderr,
			"Error: Could not skip object at 0x%lx (type: 0x%x): object size is zero\n",
			offs, (unsigned int)hdr.type);
		return -1;
	}

	offs += hdr.size;

	if(0!=fio_fseek(rctx, offs))
	{
		fprintf(stderr, "Error: Seek after object (offs %ld) failed\n",
			offs);
		return -1;
	}

	return offs;
}

long orcad_read_inlinepageobject(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_node* const parent, struct orcad_node** const out_node)
{
	long end;
	orcad_uint16_t i;

	orcad_create_node(struct orcad_inlinepageobject_node,
		ORCAD_TYPE_INLINEPAGEOBJECT);

	end = offs + node->node.size;

	if(0>(offs=orcad_read_string2(rctx, offs, &node->name)))
	{
		fprintf(stderr, "Error: Could not read name\n");
		return -1;
	}

	if(0>(offs=orcad_read_string2(rctx, offs, &node->unk_str)))
	{
		fprintf(stderr, "Error: Could not read name\n");
		return -1;
	}

	read_u32(color);
	read_u16(num_primitives);

	if(NULL==(node->primitives=(struct orcad_prim**)calloc(
		node->num_primitives, sizeof(struct orcad_prim*))))
	{
		fprintf(stderr, "Error: Could not allocate memory for primitives\n");
		return -1;
	}

	/* NOTE: there are some additional bytes here and there, which are not */
	/* included in the size fields, so it could be misunderstood! */

	for(i=0;i<node->num_primitives;++i)
	{
		if(0>(offs=orcad_read_primitive(rctx, offs, &node->primitives[i])))
		{
			orcad_error_backtrace__(&node->node, "read primitives");
			return -1;
		}
	}

	if(0<node->node.size)
	{
		offs = end;

		if(0!=fio_fseek(rctx, end))
		{
			fprintf(stderr,
				"Error: Seek after inline_object (offs %ld) failed\n", end);
			return -1;
		}
	}
	else
	{
		orcad_uint32_t unk1, unk2;
		vread_u32(unk1);
		vread_u32(unk2);
	}

	return offs;
}

long orcad_read_symboldisplayprop(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_node* const parent, struct orcad_node** const out_node)
{
	orcad_create_node(struct orcad_symboldisplayprop_node,
		ORCAD_TYPE_SYMBOLDISPLAYPROP);

	if(0==node->node.size)
	{
		/* we have an old-style header, it seems name_idx field is 16-bit */

		orcad_uint16_t name_idx;
		vread_u16(name_idx);
		node->name_idx = name_idx;
	}
	else
	{
		read_u32(name_idx);
	}

	read_i16(x);
	read_i16(y);
	read_u16(font_id);

	node->rotation = node->font_id >> 14;
	node->font_id &= 0x3ffff;

	read_u8(color);
	read_u8(unknown_0);
	read_u8(format);
	read_u8(unknown_2);

	return offs;
}

long orcad_read_pagesettings(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_pagesettings* const settings)
{
	vread_u32(settings->ctime);
	vread_u32(settings->mtime);
	vread_u32(settings->unknown_0);
	vread_u32(settings->unknown_1);
	vread_u32(settings->unknown_2);
	vread_u32(settings->unknown_3);
	vread_u32(settings->width);
	vread_u32(settings->height);
	vread_u32(settings->pin_to_pin);
	vread_u16(settings->unknown_4);
	vread_u16(settings->horiz_count);
	vread_u16(settings->vert_count);
	vread_u16(settings->unknown_5);
	vread_u32(settings->horiz_width);
	vread_u32(settings->vert_width);
	vread_u32(settings->unknown_6);
	vread_u32(settings->unknown_7);
	vread_u32(settings->unknown_8);
	vread_u32(settings->unknown_9);
	vread_u32(settings->unknown_10);
	vread_u32(settings->unknown_11);
	vread_u32(settings->unknown_12);
	vread_u32(settings->unknown_13);
	vread_u32(settings->unknown_14);
	vread_u32(settings->unknown_15);
	vread_u32(settings->unknown_16);
	vread_u32(settings->unknown_17);
	vread_u32(settings->horiz_char);
	vread_u32(settings->unknown_18);
	vread_u32(settings->horiz_ascending);
	vread_u32(settings->vert_char);
	vread_u32(settings->unknown_19);
	vread_u32(settings->vert_ascending);
	vread_u32(settings->is_metric);
	vread_u32(settings->border_displayed);
	vread_u32(settings->border_printed);
	vread_u32(settings->gridref_displayed);
	vread_u32(settings->gridref_printed);
	vread_u32(settings->titleblock_displayed);
	vread_u32(settings->titleblock_printed);
	vread_u32(settings->ansi_grid_refs);

	return offs;
}
