#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 };

my_uint16_t little2host_16(my_uint16_t x)
{
	/* TODO: implement this */
	/* my machine is little endian, thus: */
	return x;
}

my_uint32_t little2host_32(my_uint32_t x)
{
	/* TODO: implement this */
	/* my machine is little endian, thus: */
	return x;
}

void indent_impl(FILE* const out, int indent)
{
	while(0<(indent--))
	{
		fputs("  ", out);
	}
}

long orcad_read_header(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_header* const hdr)
{
	if(sizeof(hdr->type)!=fio_fread(rctx, (char*)&hdr->type,
		sizeof(hdr->type)))
	{
		fprintf(stderr, "Error: Could not read header type\n");
		return -1;
	}

	offs += sizeof(hdr->type);

	if(sizeof(hdr->size)!=fio_fread(rctx, (char*)&hdr->size,
		sizeof(hdr->size)))
	{
		fprintf(stderr, "Error: Could not read header size field\n");
		return -1;
	}

	if(sizeof(hdr->unknown)!=fio_fread(rctx, (char*)&hdr->unknown,
		sizeof(hdr->unknown)))
	{
		fprintf(stderr, "Error: Could not read header's unknown field\n");
		return -1;
	}

	hdr->size = little2host_32(hdr->size);
	hdr->unknown = little2host_32(hdr->unknown);

	return offs + sizeof(hdr->size) + sizeof(hdr->unknown);
}

/* 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_header hdr;
	int extra_headers;
	long save_offs1; /* save offset after primary header */
	long save_offsN; /* save offset after the N-th header */
	char magic_data[sizeof(orcad_magic)];

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

	/* [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)
	{
		/* we have only the primary header */
		goto primary_header_only;
	}

	save_offsN = save_offs1;

	for(extra_headers=5;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;
		}

		/* check for magic number: it is located near the end of the last */
		/* header (size of the last header is stored in the 'size' field of */
		/* predecessor header) */
		/* TYPE[1] ... MAGIC[4] ZERO[4] */

		if(0!=fio_fseek(rctx, offs+hdr.size-sizeof(orcad_magic)-4))
		{
			fprintf(stderr, "Error: Seek to magic (offs %ld) failed\n",
				offs+hdr.size-sizeof(orcad_magic)-4);
			return -1;
		}

		if(sizeof(orcad_magic)==fio_fread(rctx, magic_data,
			sizeof(orcad_magic)) && 0==memcmp(magic_data, orcad_magic,
			sizeof(orcad_magic)))
		{
			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_read_field_8(io_orcad_rctx_t* const rctx, long offs,
	my_uint8_t* const out)
{
	if(sizeof(*out)!=fio_fread(rctx, (char*)out, sizeof(*out)))
	{
		fprintf(stderr, "Error: Could not read 8-bit field\n");
		return -1;
	}

	offs += sizeof(*out);

	return offs;
}

long orcad_read_field_16(io_orcad_rctx_t* const rctx, long offs,
	my_uint16_t* const out)
{
	my_uint16_t tmp;

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

	offs += sizeof(tmp);
	*out = little2host_16(tmp);

	return offs;
}

long orcad_read_field_32(io_orcad_rctx_t* const rctx, long offs,
	my_uint32_t* const out)
{
	my_uint32_t tmp;

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

	offs += sizeof(tmp);
	*out = little2host_32(tmp);

	return offs;
}

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

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

	if(expected!=tmp)
	{
		fprintf(stderr, "Error: Could not skip 16-bit field at %li: "
			"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 my_uint32_t expected)
{
	my_uint32_t tmp;

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

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

	return offs;
}

long orcad_read_string(io_orcad_rctx_t* const rctx, long offs, char* buf,
	const size_t bufsz, my_uint16_t len)
{
	if(bufsz<=len)
	{
		fprintf(stderr, "Error: String does not fit in buffer (string: %lu, "
			"buffer: %lu)\n", (unsigned long)len, (unsigned long)bufsz);
		return -1;
	}

	++len;

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

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

	return offs + len;
}

/* 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,
	const size_t bufsz)
{
	my_uint16_t len;

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

	return orcad_read_string(rctx, offs, buf, bufsz, 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)))
	{
		fprintf(stderr, "Error: Magic data does not match at offs 0x%lx\n",
			offs);
		return -1;
	}

	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);
}

void orcad_free(struct orcad_node* const root)
{
	TODO("TODO (maybe in a separate module?)");
}

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_PAGE:               return "Page";
	case ORCAD_TYPE_PARTINST:           return "PartInst";
	case ORCAD_TYPE_0x10:               return "T0x10";
	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_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_TITLEBLOCKSYMBOL:   return "TitleBlockSymbol";
	case ORCAD_TYPE_TITLEBLOCK:         return "TitleBlock";
	case ORCAD_TYPE_GRAPHICBEZIERINST:  return "GraphicBezierInst";
	case ORCAD_TYPE_X_CACHE:            return "X-Cache";

	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;

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

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

struct orcad_node* orcad_create_node_from__(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_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=malloc(struct_size)))
	{
		fprintf(stderr, "Error: Could not allocate node memory for %s\n",
			orcad_type2str(type));
		return NULL;
	}

	memset(node, 0, struct_size);

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

	return node;
}

void orcad_error_backtrace__(struct orcad_node* node, const char* const 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;
	}
}

/* TODO: REMOVE! */
void orcad_report_type_mismatch__(io_orcad_rctx_t* const rctx, long offs,
	const my_uint8_t expected, const my_uint8_t actual)
{
	/* this is a cold function should not called at all */

	fprintf(stderr,
		"Error: Object at 0x%lx expected to be 0x%x, but got 0x%x\n",
		offs, (unsigned int)expected, (unsigned int)actual);
}

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**)malloc(sizeof(struct orcad_node*)*count);

	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;
}

/* TODO: REMOVE */
long orcad_process_objects(io_orcad_rctx_t* const rctx, long offs, int indent,
	int count, long (* const process_object)(io_orcad_rctx_t* const rctx,
	long offs, int ind))
{
	while(0<(count--))
	{
		if(0>(offs=process_object(rctx, offs, indent)))
		{
			return -1;
		}
	}

	return offs;
}

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

	struct orcad_header hdr;

	if(0>(offs=orcad_read_header(rctx, offs, &hdr)))
	{
		fprintf(stderr, "Error: Could not read object header\n");
		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;
	my_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,
		sizeof(node->name))))
	{
		fprintf(stderr, "Error: Could not read name\n");
		return -1;
	}

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

	read_32(color);
	read_16(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!=fio_fseek(rctx, end))
	{
		fprintf(stderr, "Error: Seek after inline_object (offs %ld) failed\n",
			end);
		return -1;
	}

	return end;
}
