/* 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.
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/tags/release-1-1-1/engines/m4/viewmgr.cpp $
 * $Id: viewmgr.cpp 48029 2010-02-10 11:03:04Z dreammaster $
 *
 */

// TODO: Views have a _coords rect, so I'm not sure if x/y is needed in the onRefresh

#include "m4/m4.h"
#include "m4/viewmgr.h"
#include "m4/mads_anim.h"

namespace M4 {

void returnToMainMenuFn(MadsM4Engine *vm) {
	vm->_palette->resetColorCounts();
	vm->_palette->setMadsSystemPalette();

	vm->loadMenu(MAIN_MENU);
}

RectList::RectList() {
}

RectList::~RectList() {
}

void RectList::addRect(int x1, int y1, int x2, int y2) {
	addRect(Common::Rect(x1, y1, x2, y2));
}

void RectList::addRect(const Common::Rect &rect) {
	/* TODO:
		Implement the following:
			- Don't add the Rect if it's contained in any Rect in the list
			- Split up the Rect if it intersects any Rect in the list
			  and add the resulting partial Rects instead
	*/
	push_back(rect);
}

int RectList::find(const Common::Point &pt) {
	for (uint idx = 0; idx < size(); ++idx) {
		if (this->operator [](idx).contains(pt.x, pt.y))
			return idx;
	}
	return -1;
}

//--------------------------------------------------------------------------

HotkeyList::HotkeyList(View *owner) : _view(owner) {
}

HotkeyList::~HotkeyList() {
	for (uint32 i = 0; i < _hotkeys.size(); i++)
		delete _hotkeys[i];
}

void HotkeyList::add(uint32 key, Hotkey::Callback callback) {
	_hotkeys.push_back(new Hotkey(key, callback));
}

void HotkeyList::remove(uint32 key) {
	for (uint32 i = 0; i < _hotkeys.size(); i++) {
		if (_hotkeys[i]->key == key) {
			delete _hotkeys[i];
			_hotkeys.remove_at(i);
			break;
		}
	}
}

bool HotkeyList::call(uint32 key) {
	for (uint32 i = 0; i < _hotkeys.size(); i++) {
		if (_hotkeys[i]->key == key) {
			if (_hotkeys[i]->callback)
				(_hotkeys[i]->callback)(_vm, _view, key);
			return true;
		}
	}
	return false;
}

//--------------------------------------------------------------------------

// View constructor

View::View(MadsM4Engine *vm, const Common::Rect &viewBounds, bool transparent)
	: M4Surface(viewBounds.width(), viewBounds.height()), _hotkeys(this), _vm(vm) {
	SCREEN_FLAGS_DEFAULT;
	_coords = viewBounds;
	_transparent = transparent;
}

View::View(MadsM4Engine *vm, int x, int y, bool transparent)
	: M4Surface(), _hotkeys(this), _vm(vm) {
	SCREEN_FLAGS_DEFAULT;
	_coords.left = x;
	_coords.top = y;
	_coords.right = _vm->_screen->width();
	_coords.bottom = _vm->_screen->height();
	_transparent = transparent;
}

void View::getCoordinates(Common::Rect &rect) {
	rect = _coords;
}

void View::extract(int *status) {
}

void View::show() {
	_screenFlags.visible = true;
	_vm->_viewManager->moveToFront(this);
	_vm->_viewManager->restore(_coords);
}

void View::hide() {
	_screenFlags.visible = false;
	_vm->_viewManager->restore(_coords);
}

void View::moveToBack() {
	_vm->_viewManager->moveToBack(this);
}

void View::moveAbsolute(int x, int y) {
	// TODO: Handle clipping and offscreen
	Common::Rect oldCoords = _coords;
	_coords.moveTo(x, y);
	_vm->_viewManager->restore(oldCoords);
	_vm->_viewManager->restore(_coords);
}

void View::moveRelative(int x, int y) {
	// TODO: Handle clipping and offscreen
	Common::Rect oldCoords = _coords;
	_coords.translate(x, y);
	_vm->_viewManager->restore(oldCoords);
	_vm->_viewManager->restore(_coords);
}

void View::resize(int newWidth, int newHeight) {
	Common::Rect oldCoords = _coords;
	if (newWidth >= 0)
		_coords.setWidth(newWidth);
	if (newHeight >= 0)
		_coords.setHeight(newHeight);
	_vm->_viewManager->restore(oldCoords);
	_vm->_viewManager->restore(_coords);
}

void View::restore(int x1, int y1, int x2, int y2) {
	_vm->_viewManager->restore(_coords.left + x1, _coords.top + y1, _coords.left + x2, _coords.top + y2);
}

void View::onRefresh(RectList *rects, M4Surface *destSurface) {
	assert(destSurface);

	if (rects == NULL)
		// No rect list specified, so copy entire surface
		copyTo(destSurface, _coords.left, _coords.top, _transparent ? 0 : -1);
	else {
		// Loop through the set of specified rectangles
		RectList::iterator i;
		for (i = rects->begin(); i != rects->end(); ++i) {
			Common::Rect &destRect = *i;
			Common::Rect srcBounds(destRect.left - _coords.left, destRect.top - _coords.top,
				destRect.right - _coords.left, destRect.bottom - _coords.top);
			copyTo(destSurface, srcBounds, destRect.left, destRect.top, _transparent ? 0 : -1);
		}
	}
}

//--------------------------------------------------------------------------

ViewManager::ViewManager(MadsM4Engine *vm): _systemHotkeys(HotkeyList(NULL)), _vm(vm) {
	_captureScreen = NULL;
	_captureEvents = false;
}

ViewManager::~ViewManager() {
	// Delete any remaining active views
	ListIterator i;
	for (i = _views.begin(); i != _views.end(); ++i)
		delete (*i);
}

void ViewManager::addView(View *view) {
	_views.push_back(view);
	moveToFront(view);
}

// Warning: After calling this method, the passed view object will no longer be valid

void ViewManager::deleteView(View *view) {
	_views.remove(view);
	delete view;
}

void ViewManager::handleEvents(const Common::Event &event) {
}

void ViewManager::handleKeyboardEvents(uint32 keycode) {
	Common::Point mousePos = _vm->_mouse->currentPos();
	View *view;
	bool blockedFlag;
	bool foundFlag;
	bool handledFlag;

	// Scan view list for one which accepts or blocks keyboard events. If one is found,
	// then the event is passed to it

	view = NULL;
	handledFlag = false;
	foundFlag = false;
	blockedFlag = false;

	// Loop from the front to back view
	ListIterator i;
	for (i = _views.reverse_begin(); (i != _views.end()) && !foundFlag; --i) {
		view = *i;
		if (!view->isVisible()) continue;

		if (view->screenFlags().blocks & SCREVENT_KEY)
			blockedFlag = true;
		if (view->screenFlags().get & SCREVENT_KEY) {
			foundFlag = true;
			handledFlag = (view->onEvent)(KEVENT_KEY, keycode, mousePos.x, mousePos.y, _captureEvents);
			if (_captureEvents)
				_captureScreen = view;
		}
	}

	// Scan view list for one with a hotkey list, aborting if a view is found that either
	// blocks keyboard events, or has a hotkey list that includes the keycode

	blockedFlag = false;
	for (i = _views.reverse_begin(); (i != _views.end()) && !foundFlag && !blockedFlag; --i) {
		view = *i;
		if (!view->isVisible()) continue;

		if (view->screenFlags().blocks & SCREVENT_KEY)
			blockedFlag = true;
		if (view->screenFlags().get & SCREVENT_KEY) {
			if (view->hotkeys().call(keycode)) {
				handledFlag = true;
				_captureEvents = false;
				//_vm->_dialogs->keyMouseCollision();	// TODO
			}
		}
	}

	// Final check: if no view handled or blocked the key, check against the system hotkey list

	if (!handledFlag && !blockedFlag) {
		handledFlag = _systemHotkeys.call(keycode);
		if (handledFlag) {
			_captureEvents = false;
			//_vm->_dialogs->keyMouseCollision();	// TODO
		}
	}
}

void ViewManager::handleMouseEvents(M4EventType event) {
	Common::Point mousePos = _vm->_mouse->currentPos();
	ListIterator i;
	View *view;
	bool blockedFlag;
	bool foundFlag;

	// If a window sets the _captureEvents flag to true, it will receive all events until
	// it sets it to false, even if it's not the top window
	if (_captureEvents) {
		assert(_captureScreen);
		if (_captureScreen->screenFlags().get & SCREVENT_MOUSE)
			(_captureScreen->onEvent)(event, 0, mousePos.x, mousePos.y, _captureEvents);

	} else {
		blockedFlag = false;
		foundFlag = false;
		view = NULL;

		// Loop from the front to back view
		for (i = _views.reverse_begin(); (i != _views.end()) && !foundFlag && !blockedFlag; --i) {
			view = *i;
			if (!view->isVisible()) continue;

			if (view->screenFlags().blocks & SCREVENT_MOUSE)
				blockedFlag = true;
			if ((view->screenFlags().get & SCREVENT_MOUSE) && view->isInside(mousePos.x, mousePos.y))
				foundFlag = true;
		}

		if (foundFlag)
			view->onEvent(event, 0, mousePos.x, mousePos.y, _captureEvents);
		else
			_captureEvents = false;
		if (_captureEvents)
			_captureScreen = view;
	}
}

void ViewManager::restore(int x1, int y1, int x2, int y2) {
	RectList *rl = new RectList();
	Common::Rect redrawBounds(x1, y1, x2, y2);
	rl->addRect(x1, y1, x2, y2);

	for (ListIterator i = _views.begin(); i != _views.end(); ++i) {
		View *v = *i;

		if (v->isVisible() && v->bounds().intersects(redrawBounds))
			v->onRefresh(rl, _vm->_screen);
	}

	_vm->_screen->update();

}

void ViewManager::restore(const Common::Rect &rect) {
	restore(rect.left, rect.top, rect.right, rect.bottom);
}

void ViewManager::moveToFront(View *view) {
	if (_views.size() < 2)
		return;

	_views.remove(view);

	ListIterator i = _views.begin();
	while ((i != _views.end()) && ((*i)->layer() <= view->layer()))
		++i;

	_views.insert(i, view);
}

void ViewManager::moveToBack(View *view) {
	if (_views.size() < 2)
		return;

	_views.remove(view);

	ListIterator i = _views.begin();
	while ((i != _views.end()) && ((*i)->layer() < view->layer()))
		++i;

	_views.insert(i, view);
}

View *ViewManager::getView(int screenType) {
	ListIterator i = _views.begin();
	while (i != _views.end()) {
		if ((*i)->screenType() == screenType)
			return *i;
		++i;
	}

	return NULL;
}

void ViewManager::updateState() {
	Common::List<View *> viewList = _views;

	for (ListIterator i = viewList.begin(); i != viewList.end(); ++i) {
		if (_vm->_events->quitFlag)
			return;

		View *v = *i;
		v->updateState();
	}
}

void ViewManager::refreshAll() {
	_vm->_screen->clear();

	for (ListIterator i = _views.begin(); i != _views.end(); ++i) {
		View *v = *i;

		if (v->isVisible())
			v->onRefresh(NULL, _vm->_screen);
	}

	_vm->_screen->update();
}

void ViewManager::showTextView(const char *textViewName, bool returnToMainMenu) {
	// Deactivate the scene if it's currently active
	View *view = _vm->_viewManager->getView(VIEWID_SCENE);
	if (view != NULL)
		_vm->_viewManager->deleteView(view);

	// Deactivate the main menu if it's currently active
	view = _vm->_viewManager->getView(VIEWID_MAINMENU);
	if (view != NULL)
		_vm->_viewManager->deleteView(view);

	// Activate the textview view
	_vm->_font->setFont(FONT_CONVERSATION_MADS);
	TextviewView *textView = new TextviewView(_vm);
	_vm->_viewManager->addView(textView);
	if (returnToMainMenu)
		textView->setScript(textViewName, returnToMainMenuFn);
	else
		textView->setScript(textViewName, NULL);
}

void ViewManager::showAnimView(const char *animViewName, bool returnToMainMenu) {
	// Deactivate the scene if it's currently active
	View *view = _vm->_viewManager->getView(VIEWID_SCENE);
	if (view != NULL)
		_vm->_viewManager->deleteView(view);

	// Deactivate the main menu if it's currently active
	view = _vm->_viewManager->getView(VIEWID_MAINMENU);
	if (view != NULL)
		_vm->_viewManager->deleteView(view);

	// Activate the animview view
	AnimviewView *animView = new AnimviewView(_vm);
	_vm->_viewManager->addView(animView);
	if (returnToMainMenu)
		animView->setScript(animViewName, returnToMainMenuFn);
	else
		animView->setScript(animViewName, NULL);
}

} // End of namespace M4
