/*
------------------------------------------------------------
	Fixed Rate Pig - a fixed logic frame rate demo
------------------------------------------------------------
 * Copyright (C) 2004 David Olofson <david@olofson.net>
 *
 * This software is released under the terms of the GPL.
 *
 * Contact author for permission if you want to use this
 * software, or work derived from it, under other terms.
 */

#ifndef	PIG_ENGINE_H
#define	PIG_ENGINE_H

#include "SDL.h"
#include <math.h>
#ifndef M_PI
#	define M_PI 3.14159265358979323846	/* pi */
#endif
#include "dirty.h"


/*----------------------------------------------------------
	Game Engine
----------------------------------------------------------*/

typedef struct PIG_object PIG_object;
typedef struct PIG_engine PIG_engine;


/* Interpolated point */
typedef struct PIG_ipoint
{
	/* From the last logic frame: */
	float	ox, oy;		/* Position */

	/* From the last/current rendered frame: */
	int	gimage;		/* Sprite frame index */
	float	gx, gy;		/* Interpolated position */
} PIG_ipoint;


/*
 * Game logic events
 *
 * PREFRAME:
 *	Occurs once per logic frame, before collision and
 *	off-screen detection, and before timer handlers.
 *
 * TIMERx:
 *	Occurs whenever timer x expires. Timers are one-
 *	shot, but can be reloaded by the handler for
 *	periodic action. Timer events are handled before
 *	before collision and off-screen detection.
 *
 * HIT_TILE:
 *	Occurs when the hot-spot of an object hits a
 *	marked side of a tile, and the corresponding bit
 *	in 'tilemask' is set.
 *
 * HIT_OBJECT:
 *	Occurs when the collision circle of an object
 *	intersects the collision circle of another object,
 *	provided one or more bits in 'hitgroup' of the
 *	other object matches bits in 'hitmask'.
 *
 * OFFSCREEN:
 *	Occurs when an object is off-screen. This takes
 *	in account the hot-spot and bounding rectangle of
 *	the current sprite frame.
 *
 * POSTFRAME:
 *	Occurs once per logic frame, after collision
 *	detection, off-screen detection and all other
 *	events.
 *
 */
#define	PIG_TIMERS	3
typedef enum
{
	PIG_PREFRAME,
	PIG_TIMER0,
	PIG_TIMER1,
	PIG_TIMER2,
	PIG_HIT_TILE,
	PIG_HIT_OBJECT,
	PIG_OFFSCREEN,
	PIG_POSTFRAME
} PIG_events;


typedef enum
{
	PIG_NONE =	0,

	/* Bit positions */
	PIG_TOP_B =	0,
	PIG_BOTTOM_B =	1,
	PIG_LEFT_B =	2,
	PIG_RIGHT_B =	3,

	/* Masks */
	PIG_TOP =	1 << PIG_TOP_B,
	PIG_BOTTOM =	1 << PIG_BOTTOM_B,
	PIG_LEFT =	1 << PIG_LEFT_B,
	PIG_RIGHT =	1 << PIG_RIGHT_B,

	/* Combined masks */
	PIG_TL =	PIG_TOP | PIG_LEFT,
	PIG_TR =	PIG_TOP | PIG_RIGHT,
	PIG_BL =	PIG_BOTTOM | PIG_LEFT,
	PIG_BR =	PIG_BOTTOM | PIG_RIGHT,
	PIG_ALL =	0xf,
} PIG_sides;


typedef enum
{
	PIG_UNCHANGED =	-10000000,
	PIG_MIN =	-10000001,
	PIG_CENTER =	-10000002,
	PIG_MAX =	-10000003
} PIG_values;


/* Collision info */
typedef struct
{
	float		ff;	/* Fractional frame */
	int		x, y;	/* Exact position */
	PIG_sides	sides;	/* Side of tile hit */
} PIG_cinfo;


typedef struct PIG_event
{
	PIG_events	type;

	/* For HIT_TILE, HIT_OBJECT and OFFSCREEN: */
	PIG_cinfo	cinfo;	/* Detailed collision info */

	/* For HIT_OBJECT: */
	PIG_object	*obj;	/* Which object? */
}  PIG_event;


/* Logic object */
struct PIG_object
{
	PIG_engine	*owner;
	PIG_object	*next, *prev;

	int		id;		/* Unique ID. 0 means "free". */

	int		ibase;		/* Sprite frame base index */
	int		image;		/* Sprite frame offset */
	float		x, y;		/* Position */
	float		vx, vy;		/* Speed */
	float		ax, ay;		/* Acceleration */
	PIG_ipoint	ip;
	int		tilemask;	/* Sprite/tile mask [PIG_ALL] */

	int		hitmask;	/* Sprite/sprite mask [0] */
	int		hitgroup;	/* Sprite/sprite group [0] */

	int		timer[PIG_TIMERS];	/* Down-counting timers */
	int		age;		/* Age timer (logic frames) */

	int		score;
	int		power;
	int		target;
	int		state;

	void (*handler)(PIG_object *po, const PIG_event *ev);

	void		*userdata;
};


/* Level map */
typedef struct PIG_map
{
	PIG_engine	*owner;

	int		w, h;		/* Size of map (tiles) */
	unsigned char	*map;		/* 2D aray of tile indices */
	unsigned char	*hit;		/* 2D aray of collision flags */

	int		tw, th;		/* Size of one tile (pixels) */
	SDL_Surface	*tiles;		/* Tile palette image */
	unsigned char	hitinfo[256];	/* Collision info for the tiles */
} PIG_map;


/* Sprite frame */
typedef struct PIG_sprite
{
	int		w, h;		/* Size of sprite (pixels) */
	int		hotx, hoty;	/* Hot-spot offset (pixels) */
	int		radius;		/* Collision zone radius (pixels) */
	SDL_Surface	*surface;
} PIG_sprite;

/* Engine */
struct PIG_engine
{
	/* Video stuff */
	SDL_Surface	*screen;
	SDL_Surface	*buffer;	/* For h/w surface displays */
	SDL_Surface	*surface;	/* Where to render to */
	int		pages;		/* # of display VRAM buffers */
	SDL_Rect	view;		/* Viewport pos & size (pixels) */
	int		page;		/* Current page (double buffer) */
	PIG_dirtytable	*pagedirty[2];	/* One table for each page */
	PIG_dirtytable	*workdirty;	/* The work dirtytable */

	/* "Live" switches */
	int		interpolation;
	int		direct;		/* 1 ==> render directly to screen */
	int		show_dirtyrects;

	/* Time */
	double		time;		/* Logic time (frames) */
	int		frame;		/* Logic time; integer part */

	/* Background graphics */
	PIG_map		*map;

	/* Sprites and stuff */
	PIG_object	*objects;
	PIG_object	*object_pool;
	int		object_id_counter;
	int		nsprites;
	PIG_sprite	**sprites;

	/* Logic frame global handlers */
	void (*before_objects)(PIG_engine *pe);
	void (*after_objects)(PIG_engine *pe);

	/* Space for user data */
	void		*userdata;
};


/*
 * Engine
 */
PIG_engine *pig_open(SDL_Surface *screen);
void pig_close(PIG_engine *pe);

/* Set viewport size and position */
void pig_viewport(PIG_engine *pe, int x, int y, int w, int h);

/* Start engine at logic time 'frame' */
void pig_start(PIG_engine *pe, int frame);

/*
 * Load a sprite palette image. The image is chopped up into
 * sprites, based on 'sw' and 'sh', and added as new frames
 * in the sprite bank. Default values:
 *	Hot-spot:		(sw/2, sh/2)
 *	Collision radius:	0.2 * (sw + sh)
 *
 * Passing 0 for 'sw' and/or 'sh' makes pig_sprites() take
 * the respective value from the image width and/or height.
 *
 * Returns the index of the first frame loaded.
 */
int pig_sprites(PIG_engine *pe, const char *filename, int sw, int sh);

/* Set hot-spot of sprite 'frame' to (hotx, hoty) */
int pig_hotspot(PIG_engine *pe, int frame, int hotx, int hoty);

/* Set sprite/sprite collision zone radius of 'frame' */
int pig_radius(PIG_engine *pe, int frame, int radius);

/* Advance logic time by 'frames' logic frames */
void pig_animate(PIG_engine *pe, float frames);

/*
 * Manually add a dirtyrect for pig_refresh().
 * 'dr' can be outside the engine viewport.
 */
void pig_dirty(PIG_engine *pe, SDL_Rect *dr);

/*
 * Do what's needed to deal with the dirtyrects
 * and then make the new frame visible.
 */
void pig_flip(PIG_engine *pe);

/*
 * Refresh the viewport and any additional dirtyrects.
 *
 * Note that this does not refresh the entire viewport;
 * only the areas that have actually changed!
 */
void pig_refresh(PIG_engine *pe);

/*
 * Refresh the whole viewport, including sprites.
 */
void pig_refresh_all(PIG_engine *pe);

/* Render a sprite "manually", bypassing the engine */
void pig_draw_sprite(PIG_engine *pe, int frame, int x, int y);

/*
 * Get the collision flags for the tile at (x, y),
 * where the unit of x and y is pixels. The return
 * is the PIG_sides flags for the tile, or PIG_NONE
 * if (x, y) is outside the map.
 */
int pig_test_map(PIG_engine *pe, int x, int y);

/*
 * Find the first "collidable" tile side when going from
 * (x1, y1) to (x2, y2). 'mask' determines which tile sides
 * are considered for collisions.
 *
 * Returns the side(s) hit, if any tile was hit. If the return
 * is non-zero, the PIG_cinfo struct at 'ci' contains detailed
 * information about the collision.
 */
int pig_test_map_vector(PIG_engine *pe, int x1, int y1, int x2, int y2,
		int mask, PIG_cinfo *ci);


/*
 * Map
 */
PIG_map *pig_map_open(PIG_engine *pe, int w, int h);
void pig_map_close(PIG_map *pm);

/* Load a tile palette image */
int pig_map_tiles(PIG_map *pm, const char *filename, int tw, int th);

/*
 * Set tile collision info for 'count' tiles, starting at
 * 'first'. Each tile in the tile palette has a set of
 * PIG_sides flags that determine which sides the tile are
 * considered for sprite/map collisions.
 */
void pig_map_collisions(PIG_map *pm, unsigned first, unsigned count,
		PIG_sides sides);

/*
 * Load a map from a string (one byte/tile). 'trans'
 * is a string used for translating 'data' into integer
 * tile indices. Each position in 'trans' corresponds
 * to one tile in the tile palette.
 */
int pig_map_from_string(PIG_map *pm, const char *trans, const char *data);


/*
 * Object
 */

/*
 * Create an object with the initial position (x, y). If
 * 'last' is 1, the object will end up last in the
 * processing and rendering order, otherwise, first.
 *
 * Note that relative processing order is very important
 * to objects that chase each other and stuff like that!
 * If they're placed in the "wrong" order, the tracking
 * objects get an extra frame of reaction time, which is
 * annoying if it's not what you intend.
 */
PIG_object *pig_object_open(PIG_engine *pe, int x, int y, int last);

/*
 * Delete an object.
 *
 * Note that objects are never actually deleted. Instead,
 * they are placed in a free pool, where pig_object_open()
 * looks for objects to recycle.
 *
 * In fact, they are not even freed when you ask for it,
 * but rather kept around until the next rendered frame,
 * so they can be removed from the screen correctly.
 */
void pig_object_close(PIG_object *po);

/*
 * Close all objects.
 */
void pig_object_close_all(PIG_engine *pe);

/*
 * Find object by 'id', starting at object 'start'.
 *
 * The search starts at 'start' and is done in both
 * directions in parallel, assuming that the matching
 * object is near 'start' in the list. (It usually is
 * when dealing with linked objects.)
 *
 * Returns NULL if the object was not found.
 */
PIG_object *pig_object_find(PIG_object *start, int id);

#endif	/* PIG_ENGINE_H */