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

void orcad_free_primitive(struct orcad_prim* const prim)
{
	TODO("TODO");
}

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_16(rctx, offs, &pt->y)) ||
		0>(offs=orcad_read_field_16(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

#define goto_end() \
	do \
	{ \
		if(0!=fio_fseek(rctx, end)) \
		{ \
			orcad_free_primitive(&prim->prim); \
			*out_prim = NULL; \
			return -1; \
		} \
	} \
	while(0)

static long orcad_read_prim_text(io_orcad_rctx_t* const rctx, long offs,
	my_uint32_t size, struct orcad_prim** const out_prim, const long start)
{
	const long end = offs + size;

	create_prim(struct orcad_text_prim, ORCAD_PRIMITIVE_TEXT);

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

	vread_32(prim->x);
	vread_32(prim->y);
	vread_32(prim->x2);
	vread_32(prim->y2);
	vread_32(prim->x1);
	vread_32(prim->y1);
	vread_16(prim->font_id);
	vread_16(prim->unknown_0);

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

	goto_end();
	return end;
}

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

	create_prim(struct orcad_line_prim, ORCAD_PRIMITIVE_LINE);

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

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

	if(32<=size)
	{
		prim->have_line_style = 1;
		vread_32(prim->line_style);
		vread_32(prim->line_width);
	}

	goto_end();
	return end;
}

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

	create_prim(struct orcad_rect_prim, ORCAD_PRIMITIVE_RECT);

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

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

	if(20<size)
	{
		prim->have_line_style = 1;
		vread_32(prim->line_style);
		vread_32(prim->line_width);
	}

	if(28<size)
	{
		prim->have_fill_style = 1;
		vread_32(prim->fill_style);
		vread_32(prim->hatch_style);
	}

	if(offs>end)
	{
		fprintf(stderr, "Error: Rect primitive overread!\n");
		return -1;
	}

	fio_fseek(rctx, end);
	return end;
}

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

	create_prim(struct orcad_arc_prim, ORCAD_PRIMITIVE_ARC);

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

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

	if((4*9)<size)
	{
		prim->have_line_style = 1;
		vread_32(prim->line_style);
		vread_32(prim->line_width);
	}

	if(offs>end)
	{
		fprintf(stderr, "Error: Arc primitive overread!\n");
		return -1;
	}

	goto_end();
	return end;
}

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

	create_prim(struct orcad_ellipse_prim, ORCAD_PRIMITIVE_ELLIPSE);

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

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

	if(20<size)
	{
		prim->have_line_style = 1;
		vread_32(prim->line_style);
		vread_32(prim->line_width);
	}

	if(28<size)
	{
		prim->have_fill_style = 1;
		vread_32(prim->fill_style);
		vread_32(prim->hatch_style);
	}

	if(offs>end)
	{
		fprintf(stderr, "Error: Ellipse primitive overread!\n");
		return -1;
	}

	goto_end();
	return end;
}

/*
	-- 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,
	my_uint32_t size, struct orcad_prim** const out_prim, const long start,
	const int V)
{
	my_uint16_t cnt;
	my_uint16_t i;
	my_uint32_t line_style;
	my_uint32_t line_width;
	my_uint32_t fill_style;
	my_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_32(rctx, offs, &line_style)) ||
			0>(offs=orcad_read_field_32(rctx, offs, &line_width)))
		{
			fprintf(stderr, "Error: Could not read polygon style\n");
			return -1;
		}

		size -= sizeof(line_style);
		size -= sizeof(line_width);
	}

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

		size -= sizeof(fill_style);
		size -= sizeof(hatch_style);
	}

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

	size -= sizeof(cnt);

	if(size!=(2*sizeof(my_uint16_t)*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,
	my_uint32_t size, struct orcad_prim** const out_prim, const long start)
{
	const long end = offs + size;
	long offs2;
	int v;

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

	size -= 4;

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

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

	fio_fseek(rctx, end);
	return end;
}

/*
	-- 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,
	my_uint32_t size, struct orcad_prim** const out_prim, const long start,
	const int V)
{
	my_uint16_t cnt;
	my_uint16_t i;
	my_uint32_t line_style;
	my_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_32(rctx, offs, &line_style)) ||
			0>(offs=orcad_read_field_32(rctx, offs, &line_width)))
		{
			fprintf(stderr, "Error: Could not read bezier style\n");
			return -1;
		}

		size -= sizeof(line_style);
		size -= sizeof(line_width);
	}

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

	size -= sizeof(cnt);

	if(size!=(2*sizeof(my_uint16_t)*cnt) || 4>cnt || 0!=((cnt-1)%3))
	{
		/* 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,
	my_uint32_t size, struct orcad_prim** const out_prim, const long start)
{
	const long end = offs + size;
	long offs2;
	int v;

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

	size -= 4;

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

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

	fio_fseek(rctx, end);
	return end;
}

long orcad_read_primitive(io_orcad_rctx_t* const rctx, long offs,
	struct orcad_prim** const out_prim)
{
	const long start_offs = offs;

	my_uint8_t ptype[2];
	my_uint32_t size;

	/* it seems, the primitve type is stored twice */

	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(ptype[0]!=ptype[1])
	{
		fprintf(stderr, "Error: Primitive types do not match\n");
		return -1;
	}

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

	/* size include 'size' itself, we don't need it */
	size -= sizeof(size);

	switch(ptype[0])
	{
	case ORCAD_PRIMITIVE_TEXT:
		offs = orcad_read_prim_text(rctx, offs, size, out_prim, start_offs);
		break;

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

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

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

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

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

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

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

	if(0>offs)
	{
		return offs;
	}

	return orcad_skip_magic(rctx, offs);
}
