#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 long orcad_read_symbolpin(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_node* const parent, struct orcad_node** const out_node)
{
	orcad_create_node(struct orcad_symbolpin_node, ORCAD_TYPE_SYMBOLPIN);

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

	read_i32(start_x);
	read_i32(start_y);
	read_i32(hotpt_x);
	read_i32(hotpt_y);
	read_u16(pin_shape);
	read_u16(unknown_0);
	read_u32(port_type);
	read_u16(unknown_1);
	read_u16(unknown_2);

	read_node_array(displayprop, orcad_read_symboldisplayprop);

	return offs;
}

static long orcad_read_pinidxmapping(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_pinidxmapping_node,
		ORCAD_TYPE_PINIDXMAPPING);

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

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

	read_u16(num_pins);

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

	for(i=0;i<node->num_pins;++i)
	{
		orcad_uint16_t len;

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

		if(0xFFFF!=len)
		{
			orcad_uint8_t pincfg;

			struct orcad_pin* const pin = (struct orcad_pin*)calloc(1,
				sizeof(struct orcad_pin));

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

			node->pins[i] = pin;

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

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

			++offs;

			if(0x80 & pincfg)
			{
				pin->pin_ignore = 1;
				pincfg ^= 0x80;
			}

			if(0x7f!=pincfg)
			{
				pin->pin_group = pincfg;
			}
			else
			{
				/* pin is in an empty group */
				pin->pin_group = -1;
			}
		}
	}

	return offs;
}

struct orcad_symbol_common_node__int
{
	struct orcad_node node;
	struct orcad_symbol_common sym_common;
};

static long orcad_read_symbol_common(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_symbol_common_node__int* const node)
{
	if(0>(offs=orcad_read_string2(rctx, offs, &node->sym_common.name)))
	{
		fprintf(stderr, "Error: Could not read sym name\n");
		return -1;
	}

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

	return offs;
}

static long orcad_read_titleblocksymbol(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_titleblocksymbol_node,
		ORCAD_TYPE_TITLEBLOCKSYMBOL);

	if(0>(offs=orcad_read_symbol_common(rctx, offs,
		(struct orcad_symbol_common_node__int*)&node->node)))
	{
		fprintf(stderr, "Error: Could not read symbol common\n");
		return -1;
	}

	read_u16(unknown_0);
	read_u16(unknown_1);

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

	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>(offs=orcad_skip_field_32(rctx, offs, 0x00000000)))
	{
		fprintf(stderr, "Error: Could not skip zero field\n");
		return -1;
	}

	read_u16(unknown_2);
	read_u16(unknown_3);

	/* symbolpins in a title block? I'm confused */
	read_node_array(symbolpin, orcad_read_symbolpin);
	read_node_array(displayprop, orcad_read_symboldisplayprop);

	return offs;
}

static long orcad_read_symbolgraphic(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_symbolgraphic_node,
		ORCAD_TYPE_SYMBOLGRAPHIC);

	if(0>(offs=orcad_read_symbol_common(rctx, offs,
		(struct orcad_symbol_common_node__int*)&node->node)))
	{
		fprintf(stderr, "Error: Could not read symbol common\n");
		return -1;
	}

	read_u16(unknown_0);
	read_u16(unknown_1);

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

	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>(offs=orcad_skip_field_32(rctx, offs, 0x00000000)))
	{
		fprintf(stderr, "Error: Could not skip zero field\n");
		return -1;
	}

	read_u16(symbox_x);
	read_u16(symbox_y);

	read_node_array(symbolpin, orcad_read_symbolpin);
	TODO(
		"NOTE: symbolpins can have symboldisplayprops as well, when the user "
		"moved the Name or Number in the symbol editor (by default both "
		"pin name and pin number positions are implied). If Name (or Number) "
		"is moved, then symboldisplayprops are genereted both into "
		"symbolgraphic->symbolpin and partinst->pinconnection with a magic "
		"name_idx. These names will be evaluated to \"Name\" or \"Number\" "
		"depending on the thing was moved. If a symboldisplayprop is found "
		"and this magic number/string is matched, the implied coordinates "
		"must be overridden by the coordinates and rotation in "
		"symboldisplayprop."
	);

	read_node_array(displayprop, orcad_read_symboldisplayprop);

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

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

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

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

	read_u16(flags);
	node->pin_names_visible   = !!(0x1 & node->flags);
	node->pin_names_rotate    = !!(0x2 & node->flags);
	node->pin_numbers_visible = !(0x4 & node->flags);

	return offs;
}

static long orcad_read_properties(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_properties_node, ORCAD_TYPE_PROPERTIES);

	if(0>(offs=orcad_read_symbol_common(rctx, offs,
		(struct orcad_symbol_common_node__int*)&node->node)))
	{
		fprintf(stderr, "Error: Could not read symbol common\n");
		return -1;
	}

	read_u16(num_partnames);

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

	for(i=0;i<node->num_partnames;++i)
	{
		if(0>(offs=orcad_read_string2(rctx, offs, &node->partnames[i])))
		{
			fprintf(stderr, "Error: Could not read partnames[%u]\n",
				(unsigned int)i);
			return -1;
		}
	}

	return offs;
}

static long orcad_read_symbolpinmapping(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_node* const parent, struct orcad_node** const out_node)
{
	orcad_create_node(struct orcad_symbolpinmapping_node,
		ORCAD_TYPE_SYMBOLPINMAPPING);

	if(0>(offs=orcad_read_symbol_common(rctx, offs,
		(struct orcad_symbol_common_node__int*)&node->node)))
	{
		fprintf(stderr, "Error: Could not read symbol common\n");
		return -1;
	}

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

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

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

	read_node_array(pinidxmapping, orcad_read_pinidxmapping);

	return offs;
}

static long orcad_read__symbol(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_node* const parent, struct orcad_node** const out_node,
	const enum orcad_type type)
{
	orcad_uint16_t i;

	orcad_create_node(struct orcad_globalsymbol_node, type);

	if(0>(offs=orcad_read_symbol_common(rctx, offs,
		(struct orcad_symbol_common_node__int*)&node->node)))
	{
		fprintf(stderr, "Error: Could not read symbol common\n");
		return -1;
	}

	read_u16(unknown_0);
	read_u16(unknown_1);

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

	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>(offs=orcad_skip_field_32(rctx, offs, 0x00000000)))
	{
		fprintf(stderr, "Error: Could not skip zero field\n");
		return -1;
	}

	read_u16(symbox_x);
	read_u16(symbox_y);

	read_node_array(symbolpin, orcad_read_symbolpin);

	read_node_array(displayprop, orcad_read_symboldisplayprop);

	return offs;
}

static long orcad_read_ercsymbol(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_ercsymbol_node,
		ORCAD_TYPE_ERCSYMBOL);

	if(0>(offs=orcad_read_symbol_common(rctx, offs,
		(struct orcad_symbol_common_node__int*)&node->node)))
	{
		fprintf(stderr, "Error: Could not read symbol common\n");
		return -1;
	}

	read_u16(unknown_0);
	read_u16(unknown_1);

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

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

	read_i16(x1);
	read_i16(y1);
	read_i16(x2);
	read_i16(y2);
	read_i16(x);
	read_i16(y);

	return offs;
}

static long orcad_read_xcachesymvariant(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_node* const parent, struct orcad_node** const out_node)
{
	orcad_create_xnode(struct orcad_xcachesymvariant_node,
		ORCAD_TYPE_X_CACHESYMVARIANT);

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

	read_u32(ctime);
	read_u32(mtime);
	read_u8(type);

	if(0>(offs=orcad_skip_field_8(rctx, offs, 0x00)))
	{
		fprintf(stderr, "Error: Failed to skip\n");
		return -1;
	}

	switch((enum orcad_type)node->type)
	{
	case ORCAD_TYPE_TITLEBLOCKSYMBOL:
		offs = orcad_read_titleblocksymbol(rctx, offs, &node->node, &node->obj);
		break;

	case ORCAD_TYPE_SYMBOLGRAPHIC:
		offs = orcad_read_symbolgraphic(rctx, offs, &node->node, &node->obj);
		break;

	case ORCAD_TYPE_PROPERTIES:
		offs = orcad_read_properties(rctx, offs, &node->node, &node->obj);
		break;

	case ORCAD_TYPE_SYMBOLPINMAPPING:
		offs = orcad_read_symbolpinmapping(rctx, offs, &node->node, &node->obj);
		break;

	case ORCAD_TYPE_GLOBALSYMBOL:
	case ORCAD_TYPE_OFFPAGECONNSYMBOL:
	case ORCAD_TYPE_PORTSYMBOL:
		offs = orcad_read__symbol(rctx, offs, &node->node, &node->obj,
			(enum orcad_type)node->type);
		break;

	case ORCAD_TYPE_ERCSYMBOL:
		offs = orcad_read_ercsymbol(rctx, offs, &node->node, &node->obj);
		break;

	default:
		fprintf(stderr,
			"Error: xcachesymvariant: Unhandled type in cache: 0x%x (%s) @ offs 0x%lx\n",
			(unsigned int)node->type, orcad_type2str(node->type), offs);
		orcad_error_backtrace__(&node->node, NULL);
		return -1;
	}

	if(NULL!=node->obj)
	{
		const long end = node->obj->offs + node->obj->size;

		if(end!=offs)
		{
			fprintf(stderr, "Error: xcachesymvariant: Structure overread or "
				"unparsed segments of '%s' remained! start=0x%lx, offs=0x%lx, "
				"end=0x%lx\n", orcad_type2str(node->type), node->node.offs,
				offs, end);
			return -1;
		}
	}

	return offs;
}

static long orcad_read_xcachesymbol(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_node* const parent, struct orcad_node** const out_node)
{
	orcad_create_xnode(struct orcad_xcachesymbol_node,
		ORCAD_TYPE_X_CACHESYMBOL);

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

	read_node_array(variant, orcad_read_xcachesymvariant);

	return offs;
}

static long orcad_read_xsymbolgroup(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_node* const parent, struct orcad_node** const out_node)
{
	orcad_create_xnode(struct orcad_xsymbolgroup_node,
		ORCAD_TYPE_X_SYMBOLGROUP);

	read_node_array(symbol, orcad_read_xcachesymbol);

	return offs;
}

static long orcad_read_cache__(struct io_orcad_rctx_s* const rctx, long offs,
	struct orcad_node** const out_node)
{
	struct orcad_node* const parent = NULL;

	orcad_create_xnode(struct orcad_xcache_node, ORCAD_TYPE_X_CACHE);

	if(0>(offs=orcad_skip_field_16(rctx, offs, 0x0000)))
	{
		fprintf(stderr, "Error: First 16-bit field is not 0x0000!\n");
		return -1;
	}

	/* titleblocks */
	if(0>(offs=orcad_read_xsymbolgroup(rctx, offs, &node->node,
		(struct orcad_node**)&node->titleblocks)))
	{
		fprintf(stderr, "Error: Could not read titleblocks xsymbolgroup\n");
		return -1;
	}

	/* symbolgraphics */
	if(0>(offs=orcad_read_xsymbolgroup(rctx, offs, &node->node,
		(struct orcad_node**)&node->symbolgraphics)))
	{
		fprintf(stderr, "Error: Could not read symbolgraphics xsymbolgroup\n");
		return -1;
	}

	/* symbolproperties */
	if(0>(offs=orcad_read_xsymbolgroup(rctx, offs, &node->node,
		(struct orcad_node**)&node->symbolproperties)))
	{
		fprintf(stderr, "Error: Could not read symbolproperties xsymbolgroup\n");
		return -1;
	}

	/* symbolpinmappings */
	if(0>(offs=orcad_read_xsymbolgroup(rctx, offs, &node->node,
		(struct orcad_node**)&node->symbolpinmappings)))
	{
		fprintf(stderr, "Error: Could not read symbolpinmappings xsymbolgroup\n");
		return -1;
	}

	return offs;
}

struct orcad_node* orcad_read_cache(struct io_orcad_rctx_s* const rctx)
{
	struct orcad_node* res = NULL;

	long offs = orcad_read_cache__(rctx, 0, &res);

	if(0>offs)
	{
		if(NULL!=res)
		{
			orcad_free(res);
		}

		return NULL;
	}

	{
		char c;

		if(0<fio_fread(rctx, &c, 1))
		{
			fprintf(stderr, "Error: File 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;
}
