#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 <stddef.h>
#include <string.h>
#include <time.h>

static long orcad_read_primitive__(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_prim** const out_prim, const int with_zerobyte);

static long test_remaining_size(io_orcad_rctx_t* const rctx, const long offs,
	const long end, const size_t size)
{
	const long target_offs = offs + size;

	if(0!=fio_fseek(rctx, target_offs))
	{
		return -1;
	}

	if(orcad_is_end_or_magic(rctx, target_offs, end))
	{
		if(0!=fio_fseek(rctx, offs))
		{
			return -1;
		}

		return offs;
	}

	return -1;
}

static long orcad_read_point(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_point* const pt)
{
	/* points are in Y,X order! */

	if(0>(offs=orcad_read_field_i16(rctx, offs, &pt->y)) ||
		0>(offs=orcad_read_field_i16(rctx, offs, &pt->x)))
	{
		return -1;
	}

	return offs;
}

#define create_prim(_typename_, _primtype_) \
	_typename_* const prim = (_typename_*)malloc(sizeof(_typename_)); \
	if(NULL==prim) \
	{ \
		fprintf(stderr, "Error: Could not allocate primitive: %s\n", \
			#_primtype_); \
		return -1; \
	} \
	*out_prim = &prim->prim; \
	memset(prim, 0, sizeof(*prim)); \
	prim->prim.type = (_primtype_); \
	prim->prim.offs = offs; \
	orcad_create_prim_callback__(&prim->prim)

static long orcad_read_prim_text(io_orcad_rctx_t* const rctx, long offs,
	orcad_uint32_t size, struct orcad_prim** const out_prim)
{
	create_prim(struct orcad_text_prim, ORCAD_PRIMITIVE_TEXT);

	vread_i32(prim->x);
	vread_i32(prim->y);
	vread_i32(prim->x2);
	vread_i32(prim->y2);
	vread_i32(prim->x1);
	vread_i32(prim->y1);
	vread_u16(prim->font_id);
	vread_u16(prim->unknown_0);

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

	return offs;
}

static long orcad_read_prim_bitmap(io_orcad_rctx_t* const rctx, long offs,
	orcad_uint32_t size, struct orcad_prim** const out_prim)
{
	create_prim(struct orcad_bitmap_prim, ORCAD_PRIMITIVE_BITMAP);

	vread_i32(prim->x);
	vread_i32(prim->y);
	vread_i32(prim->x2);
	vread_i32(prim->y2);
	vread_i32(prim->x1);
	vread_i32(prim->y1);
	vread_u32(prim->bmp_width);
	vread_u32(prim->bmp_height);
	vread_u32(prim->bmp_datasize);

	if(NULL==(prim->bmp_data=(unsigned char*)malloc(prim->bmp_datasize)))
	{
		fprintf(stderr, "Error: Coult not allocate memory for bitmap data\n");
		orcad_free_primitive(&prim->prim);
		*out_prim = NULL;
		return -1;
	}

	if(prim->bmp_datasize!=fio_fread(rctx, (char*)prim->bmp_data,
		prim->bmp_datasize))
	{
		fprintf(stderr, "Error: Coult not read bitmap data\n");
		return -1;
	}

	offs += prim->bmp_datasize;

	return offs;
}

static long orcad_read_prim_line(io_orcad_rctx_t* const rctx, long offs,
	orcad_uint32_t size, struct orcad_prim** const out_prim)
{
	const long end = offs+size;

	create_prim(struct orcad_line_prim, ORCAD_PRIMITIVE_LINE);

	vread_i32(prim->x1);
	vread_i32(prim->y1);
	vread_i32(prim->x2);
	vread_i32(prim->y2);

	if(0==size || !orcad_is_end_or_magic(rctx, offs, end))
	{
		prim->have_line_style = 1;
		vread_u32(prim->line_style);
		vread_u32(prim->line_width);
	}

	return offs;
}

static long orcad_read_prim_rect(io_orcad_rctx_t* const rctx, long offs,
	orcad_uint32_t size, struct orcad_prim** const out_prim)
{
	const long end = offs+size;

	create_prim(struct orcad_rect_prim, ORCAD_PRIMITIVE_RECT);

	vread_i32(prim->x1);
	vread_i32(prim->y1);
	vread_i32(prim->x2);
	vread_i32(prim->y2);

	if(0==size || !orcad_is_end_or_magic(rctx, offs, end))
	{
		prim->have_line_style = 1;
		vread_u32(prim->line_style);
		vread_u32(prim->line_width);
	}

	if(0==size || !orcad_is_end_or_magic(rctx, offs, end))
	{
		prim->have_fill_style = 1;
		vread_u32(prim->fill_style);
		vread_u32(prim->hatch_style);
	}

	return offs;
}

static long orcad_read_prim_arc(io_orcad_rctx_t* const rctx, long offs,
	orcad_uint32_t size, struct orcad_prim** const out_prim)
{
	const long end = offs+size;

	create_prim(struct orcad_arc_prim, ORCAD_PRIMITIVE_ARC);

	vread_i32(prim->x1);
	vread_i32(prim->y1);
	vread_i32(prim->x2);
	vread_i32(prim->y2);
	vread_i32(prim->start_x);
	vread_i32(prim->start_y);
	vread_i32(prim->end_x);
	vread_i32(prim->end_y);

	if(0==size || !orcad_is_end_or_magic(rctx, offs, end))
	{
		prim->have_line_style = 1;
		vread_u32(prim->line_style);
		vread_u32(prim->line_width);
	}

	return offs;
}

static long orcad_read_prim_ellipse(io_orcad_rctx_t* const rctx, long offs,
	orcad_uint32_t size, struct orcad_prim** const out_prim)
{
	const long end = offs+size;

	create_prim(struct orcad_ellipse_prim, ORCAD_PRIMITIVE_ELLIPSE);

	vread_i32(prim->x1);
	vread_i32(prim->y1);
	vread_i32(prim->x2);
	vread_i32(prim->y2);

	if(0==size || !orcad_is_end_or_magic(rctx, offs, end))
	{
		prim->have_line_style = 1;
		vread_u32(prim->line_style);
		vread_u32(prim->line_width);
	}

	if(0==size || !orcad_is_end_or_magic(rctx, offs, end))
	{
		prim->have_fill_style = 1;
		vread_u32(prim->fill_style);
		vread_u32(prim->hatch_style);
	}

	return offs;
}

/*
	-- polygon --

	there are 3 versions:

	(1)
		POINT_COUNT_16 [Y X]...

	(2)
		LINE_STYLE_32 LINE_WIDTH_32 POINT_COUNT_16 [Y X]...

	(3)
		LINE_STYLE_32 LINE_WIDTH_32 FILL_STYLE_32 HATCH_STYLE_32 POINT_COUNT_16 [Y X]...
*/

static const int orcad_polygon_num_V = 3;

static long orcad_read_prim_polygon_V(io_orcad_rctx_t* const rctx, long offs,
	orcad_uint32_t size, struct orcad_prim** const out_prim, const int V)
{
	const long end = offs+size;

	orcad_uint16_t cnt;
	orcad_uint16_t i;
	orcad_uint32_t line_style;
	orcad_uint32_t line_width;
	orcad_uint32_t fill_style;
	orcad_uint32_t hatch_style;

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

	if(1<V)
	{
		if(0>(offs=orcad_read_field_u32(rctx, offs, &line_style)) ||
			0>(offs=orcad_read_field_u32(rctx, offs, &line_width)))
		{
			fprintf(stderr, "Error: Could not read polygon style\n");
			return -1;
		}
	}

	if(2<V)
	{
		if(0>(offs=orcad_read_field_u32(rctx, offs, &fill_style)) ||
			0>(offs=orcad_read_field_u32(rctx, offs, &hatch_style)))
		{
			fprintf(stderr, "Error: Could not read polygon style\n");
			return -1;
		}
	}

	if(0>(offs=orcad_read_field_u16(rctx, offs, &cnt)))
	{
		fprintf(stderr, "Error: Could not read polygon point count\n");
		return -1;
	}

	/* 2 coords, 2 byte each, 'cnt' count */
	if(0<size && 0>(offs=test_remaining_size(rctx, offs, end, 2*2*cnt)))
	{
		/* invariant violation */
		return -1;
	}

	{
		create_prim(struct orcad_polygon_prim, ORCAD_PRIMITIVE_POLYGON);

		if(1<V)
		{
			prim->have_line_style = 1;
			prim->line_style = line_style;
			prim->line_width = line_width;
		}

		if(2<V)
		{
			prim->have_fill_style = 1;
			prim->fill_style  = fill_style;
			prim->hatch_style = hatch_style;
		}

		prim->num_points = cnt;

		if(NULL==(prim->points=(struct orcad_point*)calloc(cnt,
			sizeof(struct orcad_point))))
		{
			fprintf(stderr, "Error: Coult not allocate memory for polygon "
				"points\n");
			orcad_free_primitive(&prim->prim);
			*out_prim = NULL;
			return -1;
		}

		for(i=0;i<cnt;++i)
		{
			if(0>(offs=orcad_read_point(rctx, offs, &prim->points[i])))
			{
				fprintf(stderr, "Error: Could not read point of polygon prim\n");
				orcad_free_primitive(&prim->prim);
				*out_prim = NULL;
				return -1;
			}
		}
	}

	return offs;
}

static long orcad_read_prim_polygon(io_orcad_rctx_t* const rctx, long offs,
	orcad_uint32_t size, struct orcad_prim** const out_prim)
{
	long offs2;
	int v;

	if(0==size)
	{
		/* older orcad supports only V=3 */
		return orcad_read_prim_polygon_V(rctx, offs, size, out_prim, 3);
	}

	for(v=1;v<=orcad_polygon_num_V;++v)
	{
		if(0<=(offs2=orcad_read_prim_polygon_V(rctx, offs, size, out_prim, v)))
		{
			break;
		}
	}

	if(orcad_polygon_num_V<v)
	{
		fprintf(stderr, "Error: Could not read polygon, all loaders failed\n");
		return -1;
	}

	return offs2;
}

/*
	-- polyline --

	there are 2 versions:

	(1)
		POINT_COUNT_16 [Y X]...

	(2)
		LINE_STYLE_32 LINE_WIDTH_32 POINT_COUNT_16 [Y X]...
*/

static const int orcad_polyline_num_V = 2;

static long orcad_read_prim_polyline_V(io_orcad_rctx_t* const rctx, long offs,
	orcad_uint32_t size, struct orcad_prim** const out_prim, const int V)
{
	const long end = offs+size;

	orcad_uint16_t cnt;
	orcad_uint16_t i;
	orcad_uint32_t line_style;
	orcad_uint32_t line_width;

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

	if(1<V)
	{
		if(0>(offs=orcad_read_field_u32(rctx, offs, &line_style)) ||
			0>(offs=orcad_read_field_u32(rctx, offs, &line_width)))
		{
			fprintf(stderr, "Error: Could not read polyline style\n");
			return -1;
		}
	}

	if(0>(offs=orcad_read_field_u16(rctx, offs, &cnt)))
	{
		fprintf(stderr, "Error: Could not read polyline point count\n");
		return -1;
	}

	/* 2 coords, 2 byte each, 'cnt' count */
	if(0<size && 0>(offs=test_remaining_size(rctx, offs, end, 2*2*cnt)))
	{
		/* invariant violation */
		return -1;
	}

	{
		create_prim(struct orcad_polyline_prim, ORCAD_PRIMITIVE_POLYLINE);

		if(1<V)
		{
			prim->have_line_style = 1;
			prim->line_style = line_style;
			prim->line_width = line_width;
		}

		prim->num_points = cnt;

		if(NULL==(prim->points=(struct orcad_point*)calloc(cnt,
			sizeof(struct orcad_point))))
		{
			fprintf(stderr, "Error: Coult not allocate memory for polyline "
				"points\n");
			orcad_free_primitive(&prim->prim);
			*out_prim = NULL;
			return -1;
		}

		for(i=0;i<cnt;++i)
		{
			if(0>(offs=orcad_read_point(rctx, offs, &prim->points[i])))
			{
				fprintf(stderr, "Error: Could not read point of polyline prim\n");
				orcad_free_primitive(&prim->prim);
				*out_prim = NULL;
				return -1;
			}
		}
	}

	return offs;
}

static long orcad_read_prim_polyline(io_orcad_rctx_t* const rctx, long offs,
	orcad_uint32_t size, struct orcad_prim** const out_prim)
{
	long offs2;
	int v;

	if(0==size)
	{
		/* older orcad supports only V=2 */
		return orcad_read_prim_polyline_V(rctx, offs, size, out_prim, 2);
	}

	for(v=1;v<=orcad_polyline_num_V;++v)
	{
		if(0<=(offs2=orcad_read_prim_polyline_V(rctx, offs, size, out_prim, v)))
		{
			break;
		}
	}

	if(orcad_polyline_num_V<v)
	{
		fprintf(stderr, "Error: Could not read polyline, all loaders failed\n");
		return -1;
	}

	return offs2;
}

/*
	-- bezier --

	there are 2 versions:

	(1)
		POINT_COUNT_16 [Y X]...

	(2)
		LINE_STYLE_32 LINE_WIDTH_32 POINT_COUNT_16 [Y X]...
*/

static const int orcad_bezier_num_V = 2;

static long orcad_read_prim_bezier_V(io_orcad_rctx_t* const rctx, long offs,
	orcad_uint32_t size, struct orcad_prim** const out_prim, const int V)
{
	const long end = offs+size;

	orcad_uint16_t cnt;
	orcad_uint16_t i;
	orcad_uint32_t line_style;
	orcad_uint32_t line_width;
	struct orcad_point p;

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

	if(1<V)
	{
		if(0>(offs=orcad_read_field_u32(rctx, offs, &line_style)) ||
			0>(offs=orcad_read_field_u32(rctx, offs, &line_width)))
		{
			fprintf(stderr, "Error: Could not read bezier style\n");
			return -1;
		}
	}

	if(0>(offs=orcad_read_field_u16(rctx, offs, &cnt)))
	{
		fprintf(stderr, "Error: Could not read bezier point count\n");
		return -1;
	}

	/* 2 coords, 2 byte each, 'cnt' count */
	if(4>cnt || 0!=((cnt-1)%3) || 0>(offs=test_remaining_size(rctx, offs, end,
		2*2*cnt)))
	{
		/* invariant violation */
		return -1;
	}

	{
		create_prim(struct orcad_bezier_prim, ORCAD_PRIMITIVE_BEZIER);

		if(1<V)
		{
			prim->have_line_style = 1;
			prim->line_style = line_style;
			prim->line_width = line_width;
		}

		prim->num_segments = (cnt - 1) / 3;

		if(NULL==(prim->segments=(struct orcad_bsegment*)calloc(
			prim->num_segments, sizeof(struct orcad_bsegment))))
		{
			orcad_free_primitive(&prim->prim);
			*out_prim = NULL;
			return -1;
		}

		/*
			adjacent bezier segments share 1 point: end point of a segment is
			the start point of the subsequent bezier segment -- this is done
			in the code below
		*/

		if(0>(offs=orcad_read_point(rctx, offs, &p)))
		{
			fprintf(stderr, "Error: Could not read first point of bezier prim\n");
			orcad_free_primitive(&prim->prim);
			*out_prim = NULL;
			return -1;
		}

		for(i=0;i<prim->num_segments;++i)
		{
			memcpy(&prim->segments[i].p1, &p, sizeof(p));

			if(0>(offs=orcad_read_point(rctx, offs, &prim->segments[i].p2)) ||
				0>(offs=orcad_read_point(rctx, offs, &prim->segments[i].p3)) ||
				0>(offs=orcad_read_point(rctx, offs, &prim->segments[i].p4)))
			{
				fprintf(stderr, "Error: Could not read points of bezier prim\n");
				orcad_free_primitive(&prim->prim);
				*out_prim = NULL;
				return -1;
			}

			/* copy shared start/end point */
			memcpy(&p, &prim->segments[i].p4, sizeof(p));
		}
	}

	return offs;
}

static long orcad_read_prim_bezier(io_orcad_rctx_t* const rctx, long offs,
	orcad_uint32_t size, struct orcad_prim** const out_prim)
{
	long offs2;
	int v;

	TODO("this will not work with older file versions (ver_major<=2)!");

	for(v=1;v<=orcad_bezier_num_V;++v)
	{
		if(0<=(offs2=orcad_read_prim_bezier_V(rctx, offs, size, out_prim, v)))
		{
			break;
		}
	}

	if(orcad_bezier_num_V<v)
	{
		fprintf(stderr, "Error: Could not read bezier, all loaders failed\n");
		return -1;
	}

	return offs2;
}

static long orcad_read_prim_symbolvector(io_orcad_rctx_t* const rctx,
	long offs, orcad_uint32_t size, struct orcad_prim** const out_prim)
{
	orcad_uint16_t i;

	create_prim(struct orcad_symbolvector_prim, ORCAD_PRIMITIVE_SYMBOLVECTOR);

	vread_i16(prim->x);
	vread_i16(prim->y);
	vread_u16(prim->num_primitives);

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

	for(i=0;i<prim->num_primitives;++i)
	{
		if(0>(offs=orcad_read_primitive__(rctx, offs, &prim->primitives[i], 1)))
		{
			fprintf(stderr, "Error: Could not read primitive #%lu\n",
				(unsigned long)i);
			return -1;
		}
	}

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

	return offs;
}

static long orcad_read_primitive__(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_prim** const out_prim, const int with_zerobyte)
{
	const long start_offs = offs;

	orcad_uint8_t ptype;
	int new_style = 0;

	struct orcad_header hdr;
	struct orcad_namemapping_info nmi;

	/* first byte is the primitive type */

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

	offs += sizeof(ptype);

	if(with_zerobyte)
	{
		if(0>(offs=orcad_skip_field_8(rctx, offs, 0x00)))
		{
			fprintf(stderr,
				"Error: Could not skip 0x00 byte before prim header\n");
			return -1;
		}
	}

	{
		long new_offs = orcad_parse_header(rctx, offs, &hdr, &nmi, 0);

		if(0<=new_offs && ptype==hdr.type)
		{
			new_style = 1;
			offs = new_offs;
		}
		else
		{
			/* restore file position */

			if(0!=fio_fseek(rctx, offs))
			{
				fprintf(stderr,
					"Error: Could not restore file position to %ld in orcad_read_primitive__()\n", offs);
				return -1;
			}

			if(2>=rctx->ver_major && ORCAD_PRIMITIVE_SYMBOLVECTOR==ptype)
			{
				/* caches before v3 has no header before primitives, */
				/* EXCEPT for SymbolVector, which has an old-style header; */
				/* parse it! */

				const long offs0 = offs;

				offs = orcad_parse_old_header(rctx, offs, &hdr, &nmi);

				if(0>offs)
				{
					fprintf(stderr,
						"ERROR: Could not parse header before %s primitive at 0x%lx\n",
						orcad_primtype2str(ptype), offs0);
					return -1;
				}
			}
			else
			{
				/* failed to parse new-style header -> there is no header! */

				hdr.type = ptype;
				hdr.size = 0;

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

	switch(hdr.type)
	{
	case ORCAD_PRIMITIVE_TEXT:
		offs = orcad_read_prim_text(rctx, offs, hdr.size, out_prim);
		break;

	case ORCAD_PRIMITIVE_BITMAP:
		offs = orcad_read_prim_bitmap(rctx, offs, hdr.size, out_prim);
		break;

	case ORCAD_PRIMITIVE_LINE:
		offs = orcad_read_prim_line(rctx, offs, hdr.size, out_prim);
		break;

	case ORCAD_PRIMITIVE_RECT:
		offs = orcad_read_prim_rect(rctx, offs, hdr.size, out_prim);
		break;

	case ORCAD_PRIMITIVE_ARC:
		offs = orcad_read_prim_arc(rctx, offs, hdr.size, out_prim);
		break;

	case ORCAD_PRIMITIVE_ELLIPSE:
		offs = orcad_read_prim_ellipse(rctx, offs, hdr.size, out_prim);
		break;

	case ORCAD_PRIMITIVE_POLYGON:
		offs = orcad_read_prim_polygon(rctx, offs, hdr.size, out_prim);
		break;

	case ORCAD_PRIMITIVE_POLYLINE:
		offs = orcad_read_prim_polyline(rctx, offs, hdr.size, out_prim);
		break;

	case ORCAD_PRIMITIVE_BEZIER:
		offs = orcad_read_prim_bezier(rctx, offs, hdr.size, out_prim);
		break;

	case ORCAD_PRIMITIVE_SYMBOLVECTOR:
		offs = orcad_read_prim_symbolvector(rctx, offs, hdr.size, out_prim);
		break;

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

	if(0>offs || !new_style)
	{
		return offs;
	}

	return orcad_skip_magic(rctx, offs);
}

long orcad_read_primitive(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_prim** const out_prim)
{
	return orcad_read_primitive__(rctx, offs, out_prim, 0);
}
