/****************************************************************************
*
* $RCSfile: Drawing.c $
* $Revision: 1.1 $
* $Date: 1997/03/23 11:32:43 $
* $Author: ssolie $
*
*****************************************************************************
*
* Copyright (c) 1997 Software Evolution.  All Rights Reserved.
*
*****************************************************************************
*
* Drawing.c -- Drawing object source file
*
* This file contains the source code for Drawing objects.  The objects can
* be loaded and manipulated using the supplied interface functions.
*/
#include <exec/memory.h>
#include <exec/lists.h>
#include <intuition/screens.h>
#include <libraries/iffparse.h>
#include <libraries/mathieeesp.h>
#include <utility/hooks.h>

#include <clib/alib_protos.h>
#include <proto/exec.h>
#include <proto/graphics.h>
#include <proto/iffparse.h>
#include <proto/intuition.h>
#include <proto/mathieeesingbas.h>

#include "Drawing.h"
#include "Debug.h"


#define abs(i)			((i) < 0 ? -(i) : (i))
#define ID_DR2D			MAKE_ID('D', 'R', '2', 'D')
#define ID_CPLY			MAKE_ID('C', 'P', 'L', 'Y')


/*** Local constants ***/
const FLOAT ZOOM_SPEED			= 0.015625;		/* 1/64 per pixel */
const FLOAT MIN_MAGNIFICATION	= 0.0625;		/* smallest magnification */
const FLOAT MAX_MAGNIFICATION	= 3.0;			/* largest magnification */
const FLOAT DEF_MAGNIFICATION	= 1.0;			/* default magnification */
const ULONG INDICATOR_POINT		= 0xFFFFFFFF;	/* DR2D indicator point */
const ULONG IND_SPLINE_FLAG		= (1 << 0);		/* DR2D spline flag */
const ULONG IND_MOVETO_FLAG		= (1 << 1);		/* DR2D move-to flag */
const ULONG NUM_SPLINE_POINTS	= 4;			/* number of spline points */
const ULONG MIN_POLYGON_POINTS	= 3;			/* minimum polygon points */


/*** Local data types ***/
typedef union {
	struct {
		FLOAT x;					/* coordinate x value */
		FLOAT y;					/* coordinate y value */
	} coord;

	struct {
		ULONG value;				/* indicator value */
		ULONG flags;				/* indicator flags */
	} indicator;
} DR2DPoint;

typedef struct {
	FLOAT x;						/* x-axis value */
	FLOAT y;						/* y-axis value */
} Coord;

typedef struct {
	struct MinNode node;			/* embedded node */
	Coord *points;					/* array of Coord's */
	UWORD num_points;				/* number of points in the object */
} Polygon;

struct DrawingClass {
	struct Screen *screen;			/* screen pointer */
	struct DrawInfo *draw_info;		/* drawing info. for screen */
	struct MinList polygons;		/* list of Polygon's */

	FLOAT scaling_factor;			/* drawing scaling factor (pixels/inch) */
	FLOAT magnification;			/* magnification */
	FLOAT zoom_speed;				/* zoom speed (per pixel) */
	WORD view_x;					/* x-axis view offset (pixels) */
	WORD view_y;					/* y-axis view offset (pixels) */
};


/*** Local function prototypes ***/
STATIC BOOL loadObjects(Drawing this, STRPTR file_name);
STATIC BOOL readIFFDrawingPolygons(struct IFFHandle *handle,
	struct MinList *list);
STATIC Polygon *createPolygon(DR2DPoint *points, UWORD num_points);
STATIC VOID deletePolygon(Polygon *polygon);
STATIC VOID deletePolygons(struct MinList *list);


/*
 * newDrawing -- Create new Drawing object
 *
 * Creates a new Drawing object ready for use.  Returns a pointer to the
 * new object if successful or NULL on error.
 */
Drawing newDrawing(struct Screen *screen)
{
	Drawing this;

	D(bug("newDrawing(%08lx)\n", screen));

	if ( screen == NULL )
		return(NULL);

	this = AllocVec(sizeof(struct DrawingClass), MEMF_ANY);
	if ( this == NULL )
		return(NULL);

	this->screen		 = screen;
	this->draw_info		 = GetScreenDrawInfo(screen);
	this->scaling_factor = 0.0;
	this->magnification	 = DEF_MAGNIFICATION;
	this->zoom_speed	 = ZOOM_SPEED;
	this->view_x		 = 0;
	this->view_y		 = 0;
	NewList((struct List*)&this->polygons);

	return(this);
}


/*
 * deleteDrawing -- Delete Drawing object
 *
 * This function is used to delete a Drawing object.
 */
VOID deleteDrawing(Drawing this)
{
	D(bug("deleteDrawing(%08lx)\n", this));

	if ( this != NULL )  {
		deletePolygons(&this->polygons);
		FreeScreenDrawInfo(this->screen, this->draw_info);
		FreeVec(this);
	}
}


/*
 * loadDrawing -- Load DR2D file into Drawing
 *
 * This function is used to load a DR2D format file into a Drawing object.
 * Only simple closed polygons will be loaded.  The rectangle describes the
 * extents of the drawing area relative to the rastport origin in pixels.
 * The DR2D file is assumed to be in units of inches.  Returns TRUE if
 * successful or FALSE on error.
 */
BOOL loadDrawing(Drawing this, STRPTR file_name, struct Rectangle *rectangle)
{
	struct MinNode *node;
	Polygon *polygon;
	FLOAT x_extent, y_extent;
	FLOAT min_x, max_x, min_y, max_y;
	FLOAT drawing_extent;
	UWORD view_extent;
	UWORD i;
	BOOL first_point;

	D(bug("loadDrawing(%08lx, %s, %08lx)\n", this, file_name, rectangle));

	if ( this == NULL || file_name == NULL || rectangle == NULL )
		return(FALSE);

	if ( loadObjects(this, file_name) == FALSE )
		return(FALSE);

	/*** Find the extents of the drawing ***/
	first_point = TRUE;
	node = this->polygons.mlh_Head;
	while ( node->mln_Succ != NULL )  {
		polygon = (Polygon*)node;
		node = node->mln_Succ;

		for ( i = 0; i < polygon->num_points; i++ )  {
			if ( first_point == TRUE )  {
				min_x = polygon->points[i].x;
				max_x = polygon->points[i].x;
				min_y = polygon->points[i].y;
				max_y = polygon->points[i].y;
				first_point = FALSE;
			}
			else  {
				if ( polygon->points[i].x > max_x )
					max_x = polygon->points[i].x;
				if ( polygon->points[i].x < min_x )
					min_x = polygon->points[i].x;

                if ( polygon->points[i].y > max_y )
					max_y = polygon->points[i].y;
				if ( polygon->points[i].y < min_x )
					min_y = polygon->points[i].y;
			}
		}
	}

	x_extent = max_x - min_x;
	y_extent = max_y - min_y;

	/*** Calculate initial drawing parameters ***/
	if ( x_extent > y_extent )  {
		drawing_extent = x_extent;
		view_extent = rectangle->MaxX - rectangle->MinX;
	}
	else  {
		drawing_extent = y_extent;
		view_extent = rectangle->MaxY - rectangle->MinY;
	}

	this->view_x = rectangle->MinX;
	this->view_y = rectangle->MinY;

	if ( view_extent == 0 || drawing_extent == 0.0 )
		this->scaling_factor = 1.0;
	else
		this->scaling_factor = (FLOAT)view_extent / drawing_extent;

	return(TRUE);
}


/*
 * renderDrawing -- Render Drawing object into RastPort
 *
 * This function renders a Drawing object into a given RastPort.  The
 * old drawing will be erased and replaced by the new one.  The delta
 * variables will adjust the drawing by the given number of pixels.
 */
VOID renderDrawing(Drawing this, struct RastPort *rp, WORD dx, WORD dy,
	WORD dz)
{
	struct MinNode *node;
	Polygon *polygon;
	FLOAT pixels_per_inch;
	LONG x0, y0, x, y;
	UWORD i;

	D(bug("renderDrawing(%08lx, %08lx, %ld, %ld, %ld)\n", this, rp,
		dx, dy, dz));

	SetRast(rp, (ULONG)this->draw_info->dri_Pens[BACKGROUNDPEN]);
	SetAPen(rp, (ULONG)this->draw_info->dri_Pens[SHINEPEN]);

	this->view_x += dx;
	this->view_y += dy;

	this->magnification += this->zoom_speed * (FLOAT)dz;
	if ( this->magnification < MIN_MAGNIFICATION )
		this->magnification = MIN_MAGNIFICATION;
	else if ( this->magnification > MAX_MAGNIFICATION )
		this->magnification = MAX_MAGNIFICATION;

	pixels_per_inch = this->scaling_factor * this->magnification;

	node = this->polygons.mlh_Head;
	while ( node->mln_Succ != NULL )  {
		polygon = (Polygon*)node;
		node = node->mln_Succ;

		x0 = round(polygon->points[0].x * pixels_per_inch) + this->view_x;
		y0 = round(polygon->points[0].y * pixels_per_inch) + this->view_y;
		Move(rp, x0, y0);

		for ( i = 1; i < polygon->num_points; i++ )  {
			x = round(polygon->points[i].x * pixels_per_inch) + this->view_x;
			y = round(polygon->points[i].y * pixels_per_inch) + this->view_y;
			Draw(rp, x, y);
		}

		Draw(rp, x0, y0);
	}
}


/*
 * loadObjects -- Load drawing primitive objects
 *
 * This function is used to load drawing primitive objects such as closed
 * polygons from a file.  Returns TRUE if successful or FALSE on error.
 */
STATIC BOOL loadObjects(Drawing this, STRPTR file_name)
{
	struct IFFHandle *iff_handle;
	BOOL result;

	D(bug("loadObjects(%08lx, %s)\n", this, file_name));

	if ( this == NULL || file_name == NULL )
		return(FALSE);

	result = FALSE;
	iff_handle = AllocIFF();
	if ( iff_handle != NULL )  {
		iff_handle->iff_Stream = Open(file_name, MODE_OLDFILE);
		if ( iff_handle->iff_Stream != NULL )  {
			InitIFFasDOS(iff_handle);
			if ( OpenIFF(iff_handle, IFFF_READ) == 0 )  {
				result = readIFFDrawingPolygons(iff_handle, &this->polygons);
				CloseIFF(iff_handle);
			}

			Close(iff_handle->iff_Stream);
		}

		FreeIFF(iff_handle);
	}

	return(result);
}


/*
 * readIFFDrawingPolygons -- Read closed polygons from IFF file
 *
 * This function is used to read closed polygons from an IFF DR2D file.
 * Only polygons which are simple CPLY type will be loaded.  Complex CPLY
 * polygons with sub-polygons or bezier curves will be reduced to simple
 * polygons by removing the complex points.  Returns TRUE if successful or
 * FALSE on error.
 */
STATIC BOOL readIFFDrawingPolygons(struct IFFHandle *handle,
	struct MinList *list)
{
	struct ContextNode *chunk;
	struct CollectionItem *collection;
	Polygon *polygon;
	DR2DPoint *points;
	UBYTE *data;
	ULONG nest_count;
	LONG result;
	UWORD num_points;

	D(bug("readIFFDrawingPolygons(%08lx, %08lx)\n", handle, list));

	if ( handle == NULL || list == NULL )
		return(FALSE);

	if ( CollectionChunk(handle, ID_DR2D, ID_CPLY) != 0 ||
		 StopOnExit(handle, ID_DR2D, ID_FORM) != 0 ||
		 StopChunk(handle, ID_DR2D, ID_FORM) != 0 )
		return(FALSE);

	nest_count = 0;
	do  {
		result = ParseIFF(handle, IFFPARSE_SCAN);
		switch ( result )  {
			case 0:
				chunk = CurrentChunk(handle);
				if ( chunk == NULL )
					break;

				switch ( chunk->cn_ID )  {
					case ID_FORM:
						nest_count += 1;
						break;
					default:
						D2(bug(".chunk->cn_ID=%08lx\n", chunk->cn_ID));
				}
				break;
			case IFFERR_EOC:
				nest_count -= 1;
				collection = FindCollection(handle, ID_DR2D, ID_CPLY);
				while ( collection != NULL )  {
					data = collection->ci_Data;
					num_points = *((UWORD*)data);

					data += sizeof(UWORD);
					points = (DR2DPoint*)data;

					polygon = createPolygon(points, num_points);
					if ( polygon != NULL )
						AddHead((struct List*)list, (struct Node*)polygon);
					else  {
						deletePolygons(list);
						return(FALSE);
					}

					collection = collection->ci_Next;
				}
				break;
			default:
				D2(bug(".result=%ld\n", result));
		}
	} while ( nest_count != 0 );

	return(TRUE);
}


/*
 * createPolygon -- Create Polygon given DR2D points
 *
 * This function is used to create a Polygon given a set of DR2D points.
 * Sub-polygons will not be recognized.  Bezier curves will be skipped.
 * Returns a pointer to a Polygon if successful or NULL on error.
 */
STATIC Polygon *createPolygon(DR2DPoint *points, UWORD num_points)
{
	Polygon *polygon;
	UWORD real_num_points;
	UWORD i, j;

	D(bug("createPolygon(%08lx, %lu)\n", points, num_points));

	if ( points == NULL || num_points == 0 )
		return(NULL);

	/*** Count the real points we will be using ***/
 	real_num_points = 0;
 	for ( i = 0; i < num_points; i++ )  {
		if ( points[i].indicator.value != INDICATOR_POINT )
			real_num_points++;
		else  {
			if ( points[i].indicator.flags & IND_SPLINE_FLAG )
				i += NUM_SPLINE_POINTS;
		}
	}

	/*** Make sure we have enough points to create a polygon ***/
	if ( real_num_points < MIN_POLYGON_POINTS )
		return(NULL);

	D2(bug(".real_num_points=%lu\n", real_num_points));

	/*** Create the Polygon using just the simple points ***/
	polygon = AllocVec(sizeof(Polygon), MEMF_ANY);
	if ( polygon == NULL )
		return(NULL);

	polygon->points = AllocVec((ULONG)real_num_points * sizeof(Coord),
		MEMF_ANY);
	if ( polygon->points == NULL )  {
		FreeVec(polygon);
		return(NULL);
	}

	polygon->node.mln_Succ = NULL;
	polygon->node.mln_Pred = NULL;
	polygon->num_points	   = real_num_points;

	/*** Skip any complex points we find ***/
	j = 0;
	for ( i = 0; i < num_points; i++ )  {
		if ( points[i].indicator.value != INDICATOR_POINT )  {
			polygon->points[j].x = points[i].coord.x;
			polygon->points[j].y = points[i].coord.y;
			j++;
		}
		else  {
			if ( points[i].indicator.flags & IND_SPLINE_FLAG )
				i += NUM_SPLINE_POINTS;
		}
	}

	return(polygon);
}


/*
 * deletePolygon -- Delete Polygon
 *
 * This function is used to delete a Polygon.
 */
STATIC VOID deletePolygon(Polygon *polygon)
{
	D(bug("deletePolygon(%08lx)\n", polygon));

	if ( polygon != NULL )  {
		FreeVec(polygon->points);
		FreeVec(polygon);
	}
}


/*
 * deletePolygons -- Delete list of Polygons
 *
 * This function is used to delete a list of Polygons.
 */
STATIC VOID deletePolygons(struct MinList *list)
{
	Polygon *polygon;

	D(bug("deletePolygons(%08lx)\n", list));

	if ( list != NULL )  {
		while ( (polygon = (Polygon*)RemHead((struct List*)list)) != NULL )
			deletePolygon(polygon);
	}
}
