/* Copyright (C) 1994-2004 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$
 */

#include "common/stdafx.h"
#include "sword2/sword2.h"
#include "sword2/driver/d_draw.h"

namespace Sword2 {

#define MOUSEFLASHFRAME 6

void Graphics::decompressMouse(byte *decomp, byte *comp, int width, int height, int pitch, int xOff, int yOff) {
	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++;
		}
	}
}

void Graphics::drawMouse(void) {
	byte mouseData[MAX_MOUSE_W * MAX_MOUSE_H];

	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. (The SDL backend imposes this
	// restriction)

	if (mouse_width + deltaX > MAX_MOUSE_W)
		deltaX = 80 - mouse_width;
	if (mouse_height + deltaY > MAX_MOUSE_H)
		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, 0, mouse_width * mouse_height);

	if (_luggageAnim)
		decompressMouse(mouseData, (byte *) _luggageAnim + READ_LE_UINT32(_luggageOffset), _luggageAnim->mousew,
				_luggageAnim->mouseh, mouse_width, deltaX, deltaY);

	if (_mouseAnim)
		decompressMouse(mouseData, _mouseSprite, _mouseAnim->mousew, _mouseAnim->mouseh, mouse_width);

	_vm->_system->setMouseCursor(mouseData, mouse_width, mouse_height, hotspot_x, hotspot_y, 0);
}

/**
 * Animates the current mouse pointer
 */

int32 Graphics::animateMouse(void) {
	uint8 prevMouseFrame = _mouseFrame;

	if (!_mouseAnim)
		return RDERR_UNKNOWN;

	if (++_mouseFrame == _mouseAnim->noAnimFrames)
		_mouseFrame = MOUSEFLASHFRAME;

	_mouseSprite = (byte *) _mouseAnim + READ_LE_UINT32(_mouseOffsets + _mouseFrame);

	if (_mouseFrame != prevMouseFrame)
		drawMouse();

	return RD_OK;
}

/**
 * Sets the mouse cursor animation.
 * @param ma a pointer to the animation data, or NULL to clear the current one
 * @param size the size of the mouse animation data
 * @param mouseFlash RDMOUSE_FLASH or RDMOUSE_NOFLASH, depending on whether
 * or not there is a lead-in animation
 */

int32 Graphics::setMouseAnim(byte *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((byte *) _mouseAnim, ma, size);
		_mouseOffsets = (int32 *) ((byte *) _mouseAnim + sizeof(MouseAnim));

		animateMouse();
		drawMouse();

		_vm->_system->showMouse(true);
	} else {
		if (_luggageAnim)
			drawMouse();
		else
			_vm->_system->showMouse(false);
	}

	return RD_OK;
}

/**
 * Sets the "luggage" animation to accompany the mouse animation. Luggage
 * sprites are of the same format as mouse sprites.
 * @param ma a pointer to the animation data, or NULL to clear the current one
 * @param size the size of the animation data
 */

int32 Graphics::setLuggageAnim(byte *ma, int32 size) {
	if (_luggageAnim) {
		free(_luggageAnim);
		_luggageAnim = NULL;
	}

	if (ma)	{
		_luggageAnim = (MouseAnim *) malloc(size);
		if (!_luggageAnim)
			return RDERR_OUTOFMEMORY;

		memcpy((byte *) _luggageAnim, ma, size);
		_luggageOffset = (int32 *) ((byte *) _luggageAnim + sizeof(MouseAnim));

		animateMouse();
		drawMouse();

		_vm->_system->showMouse(true);
	} else {
		if (_mouseAnim)
			drawMouse();
		else
			_vm->_system->showMouse(false);
	}

	return RD_OK;
}

} // End of namespace Sword2