/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * Additional copyright for this file:
 * Copyright (C) 1994-1998 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 3 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, see <http://www.gnu.org/licenses/>.
 */


#include "common/rect.h"

#include "sword2/sword2.h"
#include "sword2/defs.h"
#include "sword2/header.h"
#include "sword2/mouse.h"
#include "sword2/screen.h"

namespace Sword2 {

#define MENUDEEP 40
#define MAXMENUANIMS 8

void Mouse::clearIconArea(int menu, int pocket, Common::Rect *r) {
	byte *buf = _vm->_screen->getScreen();
	int16 screenWide = _vm->_screen->getScreenWide();
	byte menuIconWidth;

	// Initialize menu icon width at correct size
	// depending if we are using pc or psx version.
	if (Sword2Engine::isPsx())
		menuIconWidth = RDMENU_PSXICONWIDE;
	else
		menuIconWidth = RDMENU_ICONWIDE;


	r->top = menu * (RENDERDEEP + MENUDEEP) + (MENUDEEP - RDMENU_ICONDEEP) / 2;
	r->bottom = r->top + RDMENU_ICONDEEP;
	r->left = RDMENU_ICONSTART + pocket * (menuIconWidth + RDMENU_ICONSPACING);
	r->right = r->left + menuIconWidth;

	byte *dst = buf + r->top * screenWide + r->left;

	for (int i = 0; i < RDMENU_ICONDEEP; i++) {
		memset(dst, 0, menuIconWidth);
		dst += screenWide;
	}
}

/**
 * This function should be called regularly to process the menubar system. The
 * rate at which this function is called will dictate how smooth the menu
 * system is.
 */

void Mouse::processMenu() {
	uint8 menu;
	uint8 i, j;
	uint8 frameCount;
	Common::Rect r1, r2;
	static int32 lastTime = 0;

	byte *buf = _vm->_screen->getScreen();
	int16 screenWide = _vm->_screen->getScreenWide();
	byte menuIconWidth;

	if (Sword2Engine::isPsx())
		menuIconWidth = RDMENU_PSXICONWIDE;
	else
		menuIconWidth = RDMENU_ICONWIDE;


	if (lastTime == 0) {
		lastTime = _vm->getMillis();
		frameCount = 1;
	} else {
		int32 delta = _vm->getMillis() - lastTime;

		if (delta > 250) {
			lastTime += delta;
			delta = 250;
			frameCount = 1;
		} else {
			frameCount = (uint8) ((_iconCount + 8) * delta / 750);
			lastTime += frameCount * 750 / (_iconCount + 8);
		}
	}

	// Note: The "almost hidden" menu state exists only so that the menu
	// will be redrawn one last time before it's completely hidden. We do
	// not need a corresponding "almost shown" state because the menu will
	// always be redrawn while it's shown anyway. (We may want to change
	// this later.)

	while (frameCount-- > 0) {
		for (menu = RDMENU_TOP; menu <= RDMENU_BOTTOM; menu++) {
			if (_menuStatus[menu] == RDMENU_HIDDEN || _menuStatus[menu] == RDMENU_ALMOST_HIDDEN || _menuStatus[menu] == RDMENU_SHOWN)
				continue;

			int target, direction, nextState;

			if (_menuStatus[menu] == RDMENU_OPENING) {
				target = MAXMENUANIMS;
				direction = 1;
				nextState = RDMENU_SHOWN;
			} else {
				target = 0;
				direction = -1;
				nextState = RDMENU_ALMOST_HIDDEN;
			}

			bool complete = true;

			// Propagate animation from the first icon...
			for (i = RDMENU_MAXPOCKETS - 1; i > 0; i--) {
				_pocketStatus[menu][i] = _pocketStatus[menu][i - 1];

				if (_pocketStatus[menu][i] != target)
					complete = false;
			}

			if (_pocketStatus[menu][i] != target)
				complete = false;

			// ...and animate the first icon
			if (_pocketStatus[menu][0] != target)
				_pocketStatus[menu][0] += direction;

			if (complete)
				_menuStatus[menu] = nextState;
		}
	}

	for (menu = RDMENU_TOP; menu <= RDMENU_BOTTOM; menu++) {
		if (_menuStatus[menu] == RDMENU_HIDDEN)
			continue;

		if (_menuStatus[menu] == RDMENU_ALMOST_HIDDEN)
			_menuStatus[menu] = RDMENU_HIDDEN;

		// Draw the menu here.
		int32 curx = RDMENU_ICONSTART + menuIconWidth / 2;
		int32 cury = (MENUDEEP / 2) + (RENDERDEEP + MENUDEEP) * menu;

		for (i = 0; i < RDMENU_MAXPOCKETS; i++) {
			if (_icons[menu][i]) {
				int32 xoff, yoff;

				// Since we no longer clear the screen after
				// each frame we need to clear the icon area.

				clearIconArea(menu, i, &r1);

				if (_pocketStatus[menu][i] == MAXMENUANIMS) {
					xoff = (menuIconWidth / 2);
					r2.left = curx - xoff;
					r2.right = r2.left + menuIconWidth;
					yoff = (RDMENU_ICONDEEP / 2);
					r2.top = cury - yoff;
					r2.bottom = r2.top + RDMENU_ICONDEEP;
				} else {
					xoff = (menuIconWidth / 2) * _pocketStatus[menu][i] / MAXMENUANIMS;
					r2.left = curx - xoff;
					r2.right = curx + xoff;
					yoff = (RDMENU_ICONDEEP / 2) * _pocketStatus[menu][i] / MAXMENUANIMS;
					r2.top = cury - yoff;
					r2.bottom = cury + yoff;
				}

				if (xoff != 0 && yoff != 0) {
					byte *dst = buf + r2.top * screenWide + r2.left;
					byte *src = _icons[menu][i];

					if (_pocketStatus[menu][i] != MAXMENUANIMS) {
						_vm->_screen->scaleImageFast(
							dst, screenWide, r2.right - r2.left, r2.bottom - r2.top,
							src, menuIconWidth, menuIconWidth, RDMENU_ICONDEEP);
					} else {
						for (j = 0; j < RDMENU_ICONDEEP; j++) {
							memcpy(dst, src, menuIconWidth);
							src += menuIconWidth;
							dst += screenWide;
						}
					}
				}
				_vm->_screen->updateRect(&r1);
			}
			curx += (RDMENU_ICONSPACING + menuIconWidth);
		}
	}
}

/**
 * This function brings a specified menu into view.
 * @param menu RDMENU_TOP or RDMENU_BOTTOM, depending on which menu to show
 * @return RD_OK, or an error code
 */

int32 Mouse::showMenu(uint8 menu) {

	// Do not show menu in PSX version, as there was really
	// nothing similar in the original game (menu was started
	// using SELECT button in psx pad)
	if (Sword2Engine::isPsx() && menu == RDMENU_TOP)
		return RD_OK;

	// Check for invalid menu parameter
	if (menu > RDMENU_BOTTOM)
		return RDERR_INVALIDMENU;

	// Check that the menu is not currently shown, or in the process of
	// being shown.
	if (_menuStatus[menu] == RDMENU_SHOWN || _menuStatus[menu] == RDMENU_OPENING)
		return RDERR_INVALIDCOMMAND;

	_menuStatus[menu] = RDMENU_OPENING;
	return RD_OK;
}

/**
 * This function hides a specified menu.
 * @param menu RDMENU_TOP or RDMENU_BOTTOM depending on which menu to hide
 * @return RD_OK, or an error code
 */

int32 Mouse::hideMenu(uint8 menu) {

	// In PSX version, do nothing. There is no such menu.
	if (Sword2Engine::isPsx() && menu == RDMENU_TOP)
		return RD_OK;

	// Check for invalid menu parameter
	if (menu > RDMENU_BOTTOM)
		return RDERR_INVALIDMENU;

	// Check that the menu is not currently hidden, or in the process of
	// being hidden.
	if (_menuStatus[menu] == RDMENU_HIDDEN || _menuStatus[menu] == RDMENU_CLOSING)
		return RDERR_INVALIDCOMMAND;

	_menuStatus[menu] = RDMENU_CLOSING;
	return RD_OK;
}

/**
 * This function hides both menus immediately.
 */

void Mouse::closeMenuImmediately() {
	Common::Rect r;
	int i;

	_menuStatus[RDMENU_TOP] = RDMENU_HIDDEN;
	_menuStatus[RDMENU_BOTTOM] = RDMENU_HIDDEN;

	for (i = 0; i < RDMENU_MAXPOCKETS; i++) {
		if (_icons[RDMENU_TOP][i]) {
			clearIconArea(RDMENU_TOP, i, &r);
			_vm->_screen->updateRect(&r);
		}
		if (_icons[RDMENU_BOTTOM][i]) {
			clearIconArea(RDMENU_BOTTOM, i, &r);
			_vm->_screen->updateRect(&r);
		}
	}

	memset(_pocketStatus, 0, sizeof(uint8) * 2 * RDMENU_MAXPOCKETS);
}

/**
 * This function sets a menubar icon.
 * @param menu RDMENU_TOP or RDMENU_BOTTOM, depending on which menu to change
 * @param pocket the menu pocket to change
 * @param icon icon data, or NULL to clear the icon
 * @return RD_OK, or an error code
 */

int32 Mouse::setMenuIcon(uint8 menu, uint8 pocket, byte *icon) {
	Common::Rect r;
	byte menuIconWidth;

	if (Sword2Engine::isPsx())
		menuIconWidth = RDMENU_PSXICONWIDE;
	else
		menuIconWidth = RDMENU_ICONWIDE;

	// Check for invalid menu parameter.
	if (menu > RDMENU_BOTTOM)
		return RDERR_INVALIDMENU;

	// Check for invalid pocket parameter
	if (pocket >= RDMENU_MAXPOCKETS)
		return RDERR_INVALIDPOCKET;

	// If there is an icon in the requested menu/pocket, clear it out.
	if (_icons[menu][pocket]) {
		_iconCount--;
		free(_icons[menu][pocket]);
		_icons[menu][pocket] = nullptr;
		clearIconArea(menu, pocket, &r);
		_vm->_screen->updateRect(&r);
	}

	// Only put the icon in the pocket if it is not NULL
	if (icon != nullptr) {
		_iconCount++;
		_icons[menu][pocket] = (byte *)malloc(menuIconWidth * RDMENU_ICONDEEP);
		if (_icons[menu][pocket] == nullptr)
			return RDERR_OUTOFMEMORY;
		memcpy(_icons[menu][pocket], icon, menuIconWidth * RDMENU_ICONDEEP);
	}

	return RD_OK;
}

} // End of namespace Sword2
