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

long orcad_read_netalias(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_node* const parent, struct orcad_node** const out_node)
{
	orcad_create_xnode(struct orcad_xnetalias_node, ORCAD_TYPE_X_NETALIAS);

	if(0>(offs=orcad_read_string2(rctx, offs, &node->alias)))
	{
		orcad_error_backtrace__(&node->node, "read net name");
		return -1;
	}

	read_u32(net_id);

	return offs;
}

long orcad_read_alias(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_node* const parent, struct orcad_node** const out_node)
{
	orcad_create_node(struct orcad_alias_node, ORCAD_TYPE_ALIAS);

	read_u32(x);
	read_u32(y);
	read_u32(color);
	read_u8(rotation);
	read_u8(mirrored);
	read_u16(unknown_0);
	read_u16(font_id);
	read_u16(unknown_1);

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

	return offs;
}

long orcad_read_wire(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_node* const parent, struct orcad_node** const out_node)
{
	/*
		14 3d 00 00 00 00 00 00 00          header_1
		14 0b 00 00 00 00 00 00 00          header_2
		14 00 00 ff e4 5c 39 00 00 00 00    header_3
		11 00 00 00   unk[0]
		45 00 00 00   net_id
		30 00 00 00   color
		5a 00 00 00   start_x
		32 00 00 00   start_y
		82 00 00 00   end_x
		32 00 00 00   end_y
		00            unk[1]
		00 00         num of Alias
		00 00         num of SymbolDisplayProps
		03 00 00 00   linewidth
		05 00 00 00   linestyle
	*/

	orcad_uint8_t type;

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

	switch(type)
	{
	case ORCAD_TYPE_WIRE:
	case 0x15: /* TODO: maybe bus? or bus entry? */
		break;

	default:
		fprintf(stderr, "ERROR: Expected WIRE object, got 0x%x\n",
			(unsigned int)type);
		return -1;
	}

	{
		orcad_create_node(struct orcad_wire_node, type);
		node->node.type = ORCAD_TYPE_WIRE; /* hack it! */

		read_u32(wire_id);
		read_u32(net_id);
		read_u32(color);
		read_u32(start_x);
		read_u32(start_y);
		read_u32(end_x);
		read_u32(end_y);
		read_u8(unknown_0);

		read_node_array(aliase, orcad_read_alias);
		read_node_array(displayprop, orcad_read_symboldisplayprop);

		/* line_width and line_style is not present of we have old-style header */
		if(0<node->node.size)
		{
			read_u32(line_width);
			read_u32(line_style);
		}
	}

	return offs;
}

long orcad_read_titleblock(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_node* const parent, struct orcad_node** const out_node)
{
	/*
		41
		01 00 00 00           ?
		0e 00 00 00           ?
		0b 00                 strlen
		54 69 74 6c 65 42 6c 6f 63 6b 30 00
		01 00 00 00           ?
		00 00 00 00           ?
		5c 00 68 01           ?
		74 04 5c 03           ?
		30 00                 ?
		41 00                 ?
		07 00                 num_displayprops
		...                   displayprops
		40                    ?
		01 00 00 00           ?
		00 00 00 00           ?
		00 00 00 00           ?
	*/

	orcad_create_node(struct orcad_titleblock_node, ORCAD_TYPE_TITLEBLOCK);

	read_u32(unknown_0);
	read_u32(unknown_1);

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

	read_u32(unknown_2);
	read_u32(unknown_3);
	read_u32(unknown_4);
	read_u32(unknown_5);
	read_u16(unknown_6);
	read_u16(unknown_7);

	read_node_array(displayprop, orcad_read_symboldisplayprop);

	read_u8(unknown_8);
	read_u32(unknown_9);
	read_u32(unknown_10);
	read_u32(unknown_11);

	return offs;
}

static long orcad_read_netprop__new(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_node* const parent, struct orcad_node** const out_node)
{
	/*
		34 17 00 00 00 00 00 00 00
		45 00 00 00           net_id
		00 00 00 00 00 00 00  unk[0..6]
		30 00 00 00           color
		05 00 00 00           linewidth
		03 00 00 00           linestyle
	*/

	orcad_create_node(struct orcad_netprop_node, ORCAD_TYPE_NETPROP);

	read_u32(net_id);
	read_u8(unknown[0]);
	read_u8(unknown[1]);
	read_u8(unknown[2]);
	read_u8(unknown[3]);
	read_u8(unknown[4]);
	read_u8(unknown[5]);
	read_u8(unknown[6]);
	read_u32(color);
	read_u32(line_width);
	read_u32(line_style);

	return offs;
}

/* old file version do not have the "standard" (questionable word here) */
/* header around the netprop object */
static long orcad_read_netprop__old(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_node* const parent, struct orcad_node** const out_node)
{
	struct orcad_header hdr;
	struct orcad_namemapping_info nmi;

	memset(&hdr, 0, sizeof(hdr));
	memset(&nmi, 0, sizeof(nmi));

	hdr.type = ORCAD_TYPE_NETPROP;

	{
		orcad_create_node_from(struct orcad_netprop_node, ORCAD_TYPE_NETPROP,
			&hdr, &nmi);

		read_u32(net_id);

		/* it seems we have 3 zero bytes here, skip them */

		if(0>(offs=orcad_skip_field_8(rctx, offs, 0x00)) ||
			0>(offs=orcad_skip_field_8(rctx, offs, 0x00)) ||
			0>(offs=orcad_skip_field_8(rctx, offs, 0x00)))
		{
			return offs;
		}

		return offs;
	}
}

long orcad_read_netprop(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_node* const parent, struct orcad_node** const out_node)
{
	if(3<=rctx->ver_major)
	{
		return orcad_read_netprop__new(rctx, offs, parent, out_node);
	}
	else
	{
		return orcad_read_netprop__old(rctx, offs, parent, out_node);
	}
}

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

	orcad_create_node(struct orcad_busprop_node, ORCAD_TYPE_BUSPROP);

	read_u32(net_id);
	read_u8(unknown[0]);
	read_u8(unknown[1]);
	read_u8(unknown[2]);
	read_u8(unknown[3]);
	read_u8(unknown[4]);
	read_u8(unknown[5]);
	read_u8(unknown[6]);
	read_u32(color);
	read_u32(line_width);
	read_u32(line_style);
	read_u16(num_busnetids);

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

	for(i=0;i<node->num_busnetids;++i)
	{
		read_u32(busnetids[i]);
	}

	return offs;
}

static long orcad_read_graphicinst_inline(io_orcad_rctx_t* const rctx,
	long offs, struct orcad_graphicinst_node* const node,
	const orcad_uint32_t size)
{
	orcad_uint8_t type;

	read_u32(graphic.instname_idx);
	read_u32(graphic.libpath_idx);

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

	read_u32(graphic.db_id);
	read_i16(graphic.y1);
	read_i16(graphic.x1);
	read_i16(graphic.y2);
	read_i16(graphic.x2);
	read_i16(graphic.x);
	read_i16(graphic.y);
	read_u8(graphic.color);
	read_u8(graphic.rotation);
	read_u8(graphic.unknown_2);
	read_u8(graphic.unknown_3);

	if(0x4 & node->graphic.rotation)
	{
		node->graphic.mirrored = 1;
		node->graphic.rotation ^= 0x4;
	}

	read_u16(graphic.num_displayprops);
	if(0>(offs=orcad_read_nodes__(rctx, offs, &node->node,
		(struct orcad_node***)&node->graphic.displayprops,
		node->graphic.num_displayprops, orcad_read_symboldisplayprop)))
	{
		orcad_error_backtrace__(&node->node, "read 'displayprops'");
		return -1;
	}

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

	offs += sizeof(type);

	switch(type)
	{
	case ORCAD_TYPE_INLINEPAGEOBJECT:
		if(0>(offs=orcad_read_inlinepageobject(rctx, offs, &node->node,
			(struct orcad_node**)&node->graphic.obj)))
		{
			return -1;
		}
		break;

	case ORCAD_TYPE_GLOBALSYMBOL:
	case ORCAD_TYPE_OFFPAGECONNSYMBOL:
	case ORCAD_TYPE_PORTSYMBOL:
	case ORCAD_TYPE_ERCSYMBOL:
		break;

	default:
		fprintf(stderr, "Error: Unexpected graphic object type: 0x%x\n",
			(unsigned int)type);
		return -1;
	}

	node->graphic.type = type;

	return offs;
}

long orcad_read_global(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_node* const parent, struct orcad_node** const out_node)
{
	/*
		25 39 00 00 00 00 00 00 00        header-1
		25 0b 00 00 00 00 00 00 00        header-2
		25 00 00 ff e4 5c 39 00 00 00 00  header-3
		19 00 00 00          unk-0
		0e 00 00 00          unk-1
		03 00 47 4e 44 00    name
		9c 00 00 00          db_id
		46 00                y1
		50 00                x1
		50 00                y2
		64 00                x2
		50 00                x
		46 00                y
		30                   color
		00                   rotation
		25                   unk-2 (seems to be always 0x25)
		2d                   unk-3
		00 00                num_displayprops
		21                   unk-4 (seems to be always 0x21)

		extra 5 bytes, after the object
		a2 00 00 00          wire_id
		00                   x-unk-0
	*/

	orcad_create_node(struct orcad_global_node, ORCAD_TYPE_GLOBAL);

	if(0>(offs=orcad_read_graphicinst_inline(rctx, offs,
		(struct orcad_graphicinst_node*)node, node->node.size-5)))
	{
		return -1;
	}

	/* this is a nasty object, it has 5 extra bytes after the object */
	/* (why it is not part of the object then?) */

	read_u32(wire_id);
	read_u8(unknown_0);

	return offs;
}

long orcad_read_offpageconn(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_node* const parent, struct orcad_node** const out_node)
{
	orcad_create_node(struct orcad_offpageconn_node, ORCAD_TYPE_OFFPAGECONN);

	if(0>(offs=orcad_read_graphicinst_inline(rctx, offs,
		(struct orcad_graphicinst_node*)node, node->node.size-5)))
	{
		return -1;
	}

	/* this is a nasty object, it has 5 extra bytes after the object */
	/* (why it is not part of the object then?) */

	read_u32(wire_id);
	read_u8(unknown_0);

	return offs;
}

long orcad_read_graphicinst(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_node* const parent, struct orcad_node** const out_node)
{
	struct orcad_header hdr;
	struct orcad_namemapping_info nmi;

/* TODO: allow old-style headers? */
	if(0>(offs=orcad_parse_header(rctx, offs, &hdr, &nmi, 1)))
	{
		fprintf(stderr, "Error: Could not read graphicinst header\n");
		return -1;
	}

	switch(hdr.type)
	{
	case ORCAD_TYPE_GRAPHICBOXINST:
	case ORCAD_TYPE_GRAPHICLINEINST:
	case ORCAD_TYPE_GRAPHICARCINST:
	case ORCAD_TYPE_GRAPHICELLIPSEINST:
	case ORCAD_TYPE_GRAPHICPOLYGONINST:
	case ORCAD_TYPE_GRAPHICTEXTINST:
	case ORCAD_TYPE_GRAPHICBITMAPINST:
	case ORCAD_TYPE_GRAPHICBEZIERINST:
		break;

	default:
		fprintf(stderr, "Error: Unhandled graphic instance type: 0x%x\n",
			(unsigned int)hdr.type);
		return -1;
	}

	{
		orcad_create_node_from(struct orcad_graphicinst_node, hdr.type, &hdr,
			&nmi);

		if(0>(offs=orcad_read_graphicinst_inline(rctx, offs,
			(struct orcad_graphicinst_node*)node, node->node.size)))
		{
			return -1;
		}
	}

	return offs;
}

long orcad_read_pinconnection(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_node* const parent, struct orcad_node** const out_node)
{
	orcad_int16_t idx;

	orcad_create_node(struct orcad_pinconnection_node,
		ORCAD_TYPE_PINCONNECTION);

	if(0>(offs=orcad_read_field_i16(rctx, offs, &idx)))
	{
		fprintf(stderr, "Error: Could not read pin_idx field\n");
		return -1;
	}

	if(0<=idx)
	{
		node->nc  = 0;
		node->idx = idx;
	}
	else
	{
		node->nc  = 1;
		node->idx = -idx;
	}

	read_u16(x);
	read_u16(y);
	read_i32(wire_id);
	read_u32(net_id);

	read_node_array(displayprop, orcad_read_symboldisplayprop);

	return offs;
}

long orcad_read_partinst(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_node* const parent, struct orcad_node** const out_node)
{
	orcad_create_node(struct orcad_partinst_node, ORCAD_TYPE_PARTINST);

	read_u32(instname_idx);
	read_u32(libpath_idx);

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

	read_u32(db_id);
	read_i16(y1);
	read_i16(x1);
	read_i16(y2);
	read_i16(x2);
	read_i16(x);
	read_i16(y);
	read_u8(color);
	read_u8(rotation);
	read_u16(unknown_4);

	if(0x4 & node->rotation)
	{
		node->mirrored = 1;
		node->rotation ^= 0x4;
	}

	read_node_array(displayprop, orcad_read_symboldisplayprop);

	read_u8(unknown_5);

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

	read_u32(value_idx);

	read_u16(unknown_7);

	if(0<node->node.size)
	{
		read_u16(unknown_8);
		read_u32(unknown_9);
	}

	read_u16(flags);
	node->power_pins_visible = !!(0x8000 & node->flags);
	node->primitive = 0x3 & node->flags;

	read_node_array(pinconnection, orcad_read_pinconnection);

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

	read_u16(pim_idx);

	return offs;
}

long orcad_read_port(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_node* const parent, struct orcad_node** const out_node)
{
	orcad_create_node(struct orcad_port_node, ORCAD_TYPE_PORT);

	if(0>(offs=orcad_read_graphicinst_inline(rctx, offs,
		(struct orcad_graphicinst_node*)node, node->node.size-9)))
	{
		return -1;
	}

	read_u32(wire_id);
	read_u8(unknown_0);
	read_u32(unknown_1);

	return offs;
}

long orcad_read_ercsymbolinst(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_node* const parent, struct orcad_node** const out_node)
{
	orcad_create_node(struct orcad_ercsymbolinst_node,
		ORCAD_TYPE_ERCSYMBOLINST);

	if(0>(offs=orcad_read_graphicinst_inline(rctx, offs,
		(struct orcad_graphicinst_node*)node, node->node.size-9)))
	{
		return -1;
	}

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

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

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

	return offs;
}

static int orcad_cmp_netalias(const void* p_lhs, const void* p_rhs)
{
	const struct orcad_xnetalias_node* const lhs =
		*(const struct orcad_xnetalias_node**)p_lhs;

	const struct orcad_xnetalias_node* const rhs =
		*(const struct orcad_xnetalias_node**)p_rhs;

	return strcmp(lhs->alias, rhs->alias);
}

long orcad_read_page(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_node** const out_node, const struct orcad_header* const p_hdr,
	struct orcad_namemapping_info* const nmi)
{
	orcad_uint16_t i;

	struct orcad_node* const parent = NULL;

	orcad_create_node_from(struct orcad_page_node, ORCAD_TYPE_PAGE, p_hdr,
		nmi);

	/* ---- page name and size */

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

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

	/* ---- page settings */

	if(0>(offs=orcad_read_pagesettings(rctx, offs, &node->settings)))
	{
		orcad_error_backtrace__(&node->node, "read 'pagesettings'");
		return -1;
	}

	/* ---- objects */

	/* title blocks */

	read_node_array(titleblock, orcad_read_titleblock);

	/* net properties */

	read_node_array(netprop, orcad_read_netprop);

	/* bus properties */

	read_node_array(netprop, orcad_read_busprop);

	/* netalias-ID pairs */

	read_u16(num_netaliases);
	if(NULL==(node->netaliases=(struct orcad_xnetalias_node**)calloc(
		node->num_netaliases, sizeof(struct orcad_xnetalias_node*))))
	{
		fprintf(stderr, "Error: Could not allocate memory for netaliases\n");
		return -1;
	}
	for(i=0;i<node->num_netaliases;++i)
	{
		if(0>(offs=orcad_read_netalias(rctx, offs, &node->node,
			(struct orcad_node**)&node->netaliases[i])))
		{
			return -1;
		}
	}
	/* netaliases are placed into the file in random order, and the order */
	/* can change from save to save, so better to sort them */
	qsort(node->netaliases, node->num_netaliases, sizeof(node->netaliases[0]),
		orcad_cmp_netalias);

	/* wires */

	read_node_array(wire, orcad_read_wire);

	/* part instances */

	read_node_array(partinst, orcad_read_partinst);

	/* ports */

	read_node_array(port, orcad_read_port);

	/* globals */

	read_node_array(global, orcad_read_global);

	/* off-page connectors */

	read_node_array(offpageconn, orcad_read_offpageconn);

	/* ERC symbols instances */

	read_node_array(ercsymbolinst, orcad_read_ercsymbolinst);

	/* busentries */

	read_u16(num_busentries);
	if(0>(offs=orcad_skip_objects(rctx, offs, node->num_busentries)))
	{
		fprintf(stderr, "Error: Could not read busentries\n");
		return -1;
	}

	/* graphic instances */

	read_node_array(graphicinst, orcad_read_graphicinst);

	/* unknown #10 */

	read_u16(num_unk10);
	if(0>(offs=orcad_skip_objects(rctx, offs, node->num_unk10)))
	{
		fprintf(stderr, "Error: Could not skip unk10 objects\n");
		return -1;
	}

	/* unknown #11 */

	read_u16(num_unk11);
	if(0>(offs=orcad_skip_objects(rctx, offs, node->num_unk11)))
	{
		fprintf(stderr, "Error: Could not skip unk11 objects\n");
		return -1;
	}

	return offs;
}

struct orcad_node* orcad_read(io_orcad_rctx_t* const rctx)
{
	struct orcad_node* res;
	struct orcad_header hdr;
	struct orcad_namemapping_info nmi;
	long offs = orcad_parse_header(rctx, 0, &hdr, &nmi, 1);

	if(0>offs)
	{
		fprintf(stderr, "Error: Could not parse the initial header of '%s'\n",
			rctx->fn);
		return NULL;
	}

	res = NULL;

	switch(hdr.type)
	{
	case ORCAD_TYPE_PAGE:
		offs = orcad_read_page(rctx, offs, &res, &hdr, &nmi);
		break;

	default:
		fprintf(stderr, "Error: '%s' has an unknown root header type: 0x%x\n",
			rctx->fn, (unsigned int)hdr.type);
		return NULL;
	}

	if(0>offs)
	{
		fprintf(stderr, "Error: Reading '%s' failed\n", rctx->fn);

		if(NULL!=res)
		{
			orcad_free(res);
		}

		return NULL;
	}

	{
		char c;

		if(0<fio_fread(rctx, &c, 1))
		{
			fprintf(stderr, "Error: Page was not interpreted correctly!\n");
			fprintf(stderr, "Ending offs: %li (0x%lx)\n", offs, offs);

			if(NULL!=res)
			{
				orcad_free(res);
			}

			return NULL;
		}
	}

	return res;
}
