/* Copyright (C) 1994-2003 Revolution Software Ltd
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Header$
 */

//=============================================================================
//
//	Filename	:	mouse.c
//	Created		:	17th September 1996
//	By			:	P.R.Porter
//
//	Summary		:	This module holds the interface to the mouse..
//
//	Version	Date		By		Description
//	------- ---------	---		-----------------------------------------------
//	1.0		18-Sep-96	PRP		Initial mouse functions.  Simple logging of
//								previous 16 mouse events implemented.  Mouse
//								drawing is currently hard coded, but animations
//								will be definable at a later date.
//
//	1.1		03-Oct-96	PRP		Changed the mouse position so that mouse y of
//								zero is the top left corner of the screen, and
//								not the top left corner of the top menubar.
//								Also, removed the x and y position from the
//								mouse log.  And changed the MouseEvent function
//								so that it returns a pointer to _mouseEvent
//
//	1.2		04-Oct-96	PRP		Put direct path in for ddraw.h
//
//	1.3		31-Oct-96	PRP		Added code to draw the proper type of mouse
//								sprite, which comprises of the internal
//								function DrawMouse and globally available
//								AnimateMouse and SetMouseAnim.
//
//	1.4		15-Nov-96	PRP		Definition of menubar size is now obtained
//								from menu.h
//
//	1.5		18-Nov-96	PRP		Changed the direct draw interface to
//								IDirectDraw2.
//
//	1.6		25-Nov-96	PRP		Added functionality to set the luggage
//								sprite.
//
//	1.7		06-Jan-97	PRP		Changed the width and height of sprites
//								to be signed.
//
//	1.8		14-Jan-97	JEL		Reset mouse frame when new animation starts.
//
//	1.9		27-Jan-97	PRP		Changed the mouse drawing routines to utilize
//								directDraw surfaces and transparency blitting.
//
//	1.10	10-Feb-97	PRP		Changed the directDraw error reporting so that
//								it works properly.  Also, created the mouse
//								sprite depending upon whether the hardware can
//								blt or not.
//
//	1.11	19-Mar-97	PRP		Fixed a bug which was causing the mouse sprite
//								to be freed up each frame and therefore 
//								decompressed and re-loaded each frame.
//
//	1.12	20-Mar-97	PRP		Added a function to reset the render code when
//								the control panel is entered.
//
//	1.13	09-Apr-97	PRP		Made the mouse animation wrap back to the
//								seventh frame.
//
//	1.14	10-Apr-97	PRP		Added parameter to define whether mouse flashes
//								or not.
//
//	1.15	23-Jul-97	JEL		Added CheckForMouseEvents() to return no. of events outstanding
//
//
//	Functions
//	---------
//
//	--------------------------------------------------------------------------
//
//  _mouseEvent *MouseEvent(void)
//
//	The address of a _mouseEvent pointer is passed in.  If there is a mouse 
//	event in the queue, a the value of the mouse event pointer is set to the
//	address of the event, otherwise, the mouse event pointer is set to NULL.
//
//	--------------------------------------------------------------------------
//
//	int32 SetMouseAnim(uint8 *ma, int32 size)
//
//	A pointer to a valid mouse animation is passed in, along with the size of
//	the header plus sprite data.  Remember to check that the function has 
//	successfully completed, as memory allocation is required.
//	Pass NULL in to clear the mouse sprite.
//
//	--------------------------------------------------------------------------
//
//	int32 SetLuggageAnim(uint8 *ma, int32 size)
//
//	A pointer to a valid luggage animation is passed in, along with the size of
//	the header plus sprite data.  Remember to check that the function has 
//	successfully completed, as memory allocation is required.
//	Pass NULL in to clear the luggage sprite.  Luggage sprites are of the same
//	format as mouse sprites.
//
//	--------------------------------------------------------------------------
//
//	int32 AnimateMouse(void)
//
//	This function animates the current mouse pointer.  If no pointer is 
//	currently defined, an error code is returned.
//
//=============================================================================


#define WIN32_LEAN_AND_MEAN

//#include <windows.h>
//#include <windowsx.h>

//#include "ddraw.h"

#include "stdafx.h"
#include "driver96.h"

#include "d_draw.h"
#include "render.h"
#include "menu.h"
#include "../sword2.h"


#define MAX_MOUSE_EVENTS 16
#define MOUSEFLASHFRAME 6

// FIXME: Does this struct have to be packed?

typedef struct {
	uint8	runTimeComp;	// type of runtime compression used for the frame data
	uint8	noAnimFrames;	// number of frames in the anim
	int8	xHotSpot;		
	int8	yHotSpot;
	uint8	mousew;
	uint8	mouseh;
} _mouseAnim;

int16				mousex;
int16				mousey;

static	uint8		mouseBacklog = 0;
static	uint8		mouseLogPos = 0;
static	uint8		mouseFrame;
static	uint8		*mouseSprite = NULL;
static	_mouseAnim	*mouseAnim = NULL;
static	_mouseAnim	*luggageAnim = NULL;
static	_mouseEvent	mouseLog[MAX_MOUSE_EVENTS];
static  int32		*mouseOffsets;
static	int32		*luggageOffset;

// FIXME: I have no idea how large the mouse cursor can be. Is this enough?

byte _mouseData[128 * 128];





void ResetRenderEngine(void) {
	parallaxScrollx = 0;
	parallaxScrolly = 0;
	scrollx = 0;
	scrolly = 0;

}




//	--------------------------------------------------------------------------
//	Logs the mouse button event passed in buttons.  The button events are 
//	defined as RD_LEFTBUTTONDOWN, RD_LEFTBUTTONUP, RD_RIGHTBUTTONDOWN and
//	RD_RIGHTBUTTONUP.
//	--------------------------------------------------------------------------
void LogMouseEvent(uint16 buttons)

{

	_mouseEvent *me;


	if (mouseBacklog == MAX_MOUSE_EVENTS-1)			// We need to leave the one which is
	{												// the current event alone!
		return;
	}
	
	me = &mouseLog[(mouseBacklog + mouseLogPos) % MAX_MOUSE_EVENTS];
	me->buttons = buttons;
	mouseBacklog += 1;

}



// FIXME: The original code used 0 for transparency, while our backend uses
// 0xFF. That means that parts of the mouse cursor that weren't meant to be
// transparent may be now.

int32 DecompressMouse(uint8 *decomp, uint8 *comp, int width, int height, int pitch, int xOff = 0, int yOff = 0) {
	int32 size = width * height;
	int32 i = 0;
	int x = 0;
	int y = 0;

	while (i < size) {
		if (*comp > 183) {
			decomp[(y + yOff) * pitch + x + xOff] = *comp++;
			if (++x >= width) {
				x = 0;
				y++;
			}
			i++;
		} else {
			x += *comp;
			while (x >= width) {
				y++;
				x -= width;
			}
			i += *comp++;
		}
	}
	return RD_OK;
}



void DrawMouse(void) {
	if (!mouseAnim && !luggageAnim)
		return;

	// When an object is used in the game, the mouse cursor should be a
	// combination of a standard mouse cursor and a luggage cursor.
	//
	// However, judging by the original code luggage cursors can also
	// appear on their own. I have no idea which cases though.

	uint16 mouse_width = 0;
	uint16 mouse_height = 0;
	uint16 hotspot_x = 0;
	uint16 hotspot_y = 0;
	int deltaX = 0;
	int deltaY = 0;

	if (mouseAnim) {
		hotspot_x = mouseAnim->xHotSpot;
		hotspot_y = mouseAnim->yHotSpot;
		mouse_width = mouseAnim->mousew;
		mouse_height = mouseAnim->mouseh;
	}

	if (luggageAnim) {
		if (!mouseAnim) {
			hotspot_x = luggageAnim->xHotSpot;
			hotspot_y = luggageAnim->yHotSpot;
		}
		if (luggageAnim->mousew > mouse_width)
			mouse_width = luggageAnim->mousew;
		if (luggageAnim->mouseh > mouse_height)
			mouse_height = luggageAnim->mouseh;
	}

	if (mouseAnim && luggageAnim) {
		deltaX = mouseAnim->xHotSpot - luggageAnim->xHotSpot;
		deltaY = mouseAnim->yHotSpot - luggageAnim->yHotSpot;
	}

	assert(deltaX >= 0);
	assert(deltaY >= 0);

	// HACK for maximum cursor size
	if (mouse_width + deltaX > 80)
		deltaX = 80 - mouse_width;
	if (mouse_height + deltaY > 80)
		deltaY = 80 - mouse_height;

	mouse_width += deltaX;
	mouse_height += deltaY;

	if ((uint32)(mouse_width * mouse_height) > sizeof(_mouseData)) {
		warning("Mouse cursor too large");
		return;
	}

	memset(_mouseData, 0xFF, mouse_width * mouse_height);

	if (luggageAnim)
		DecompressMouse(_mouseData, (uint8 *) luggageAnim + *luggageOffset, luggageAnim->mousew,
				luggageAnim->mouseh, mouse_width, deltaX, deltaY);

	if (mouseAnim)
		DecompressMouse(_mouseData, mouseSprite, mouseAnim->mousew, mouseAnim->mouseh, mouse_width);

	g_system->set_mouse_cursor(_mouseData, mouse_width, mouse_height, hotspot_x, hotspot_y);
}



_mouseEvent *MouseEvent(void)

{
	_mouseEvent *me;

	if (mouseBacklog)
	{
		me = &mouseLog[mouseLogPos];
		if (++mouseLogPos == MAX_MOUSE_EVENTS)
		{
			mouseLogPos = 0;
		}
		mouseBacklog -= 1;
		return(me);
	}

	return(NULL);

}


uint8 CheckForMouseEvents(void)		// (James23july97)
{
	return (mouseBacklog);	// return the number of mouse events waiting	
}


int32 AnimateMouse(void) {
	uint8 prevMouseFrame = mouseFrame;

	if (!mouseAnim)
		return RDERR_UNKNOWN;

	if (++mouseFrame == mouseAnim->noAnimFrames)
		mouseFrame = MOUSEFLASHFRAME;
	mouseSprite = (uint8 *) mouseAnim + *(mouseOffsets + mouseFrame);

	if (mouseFrame != prevMouseFrame)
		DrawMouse();

	return RD_OK;
}



int32 SetMouseAnim(uint8 *ma, int32 size, int32 mouseFlash) {
	if (mouseAnim) {
		free(mouseAnim);
		mouseAnim = NULL;
	}
	if (ma)	{
		if (mouseFlash == RDMOUSE_FLASH)
			mouseFrame = 0;
		else
			mouseFrame = MOUSEFLASHFRAME;
		mouseAnim = (_mouseAnim *) malloc(size);
		if (!mouseAnim)
			return RDERR_OUTOFMEMORY;

		memcpy((uint8 *) mouseAnim, ma, size);
		mouseOffsets = (int32 *) ((uint8 *) mouseAnim + sizeof(_mouseAnim));

		AnimateMouse();
		DrawMouse();

		g_system->show_mouse(true);
	} else {
		if (luggageAnim)
			DrawMouse();
		else
			g_system->show_mouse(false);
	}
	return RD_OK;
}


int32 SetLuggageAnim(uint8 *ma, int32 size) {
	if (luggageAnim) {
		free(luggageAnim);
		luggageAnim = NULL;
	}
	if (ma)	{
		luggageAnim = (_mouseAnim *) malloc(size);
		if (!luggageAnim)
			return(RDERR_OUTOFMEMORY);

		memcpy((uint8 *) luggageAnim, ma, size);
		luggageOffset = (int32 *) ((uint8 *) luggageAnim + sizeof(_mouseAnim));

		AnimateMouse();
		DrawMouse();

		g_system->show_mouse(true);
	} else {
		if (mouseAnim)
			DrawMouse();
		else
			g_system->show_mouse(false);
	}
	return RD_OK;
}