/* 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 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 "titanic/support/screen_manager.h"
#include "titanic/support/video_surface.h"
#include "titanic/titanic.h"
#include "graphics/screen.h"

namespace Titanic {

CScreenManager *CScreenManager::_screenManagerPtr;
CScreenManager *CScreenManager::_currentScreenManagerPtr;

CScreenManager::CScreenManager(TitanicEngine *vm): _vm(vm) {
	_screenManagerPtr = nullptr;
	_currentScreenManagerPtr = nullptr;

	_frontRenderSurface = nullptr;
	_mouseCursor = nullptr;
	_textCursor = nullptr;
	_inputHandler = nullptr;
	_fontNumber = 0;

	_screenManagerPtr = this;
}

CScreenManager::~CScreenManager() {
	_screenManagerPtr = nullptr;
}

void CScreenManager::setWindowHandle(int v) {
	// Not needed
}

bool CScreenManager::resetWindowHandle(int v) {
	hideCursor();
	return true;
}

CScreenManager *CScreenManager::setCurrent() {
	if (!_currentScreenManagerPtr)
		_currentScreenManagerPtr = _screenManagerPtr;

	return _currentScreenManagerPtr;
}

void CScreenManager::setSurfaceBounds(SurfaceNum surfaceNum, const Rect &r) {
	if (surfaceNum >= 0 && surfaceNum < (int)_backSurfaces.size())
		_backSurfaces[surfaceNum]._bounds = r;
	else if (surfaceNum == SURFACE_PRIMARY)
		_frontSurfaceBounds = r;
}

int CScreenManager::setFontNumber(int fontNumber) {
	int oldFontNumber = _fontNumber;
	_fontNumber = fontNumber;
	return oldFontNumber;
}

void CScreenManager::preLoad() {
	if (_textCursor)
		_textCursor->hide();
}

/*------------------------------------------------------------------------*/

OSScreenManager::OSScreenManager(TitanicEngine *vm): CScreenManager(vm),
		_directDrawManager(vm, false) {
	_field48 = 0;
	_field4C = 0;
	_field50 = 0;
	_field54 = 0;
}

OSScreenManager::~OSScreenManager() {
	destroyFrontAndBackBuffers();
	delete _mouseCursor;
	delete _textCursor;
}

void OSScreenManager::setMode(int width, int height, int bpp, uint numBackSurfaces, bool flag2) {
	assert(bpp == 16);
	destroyFrontAndBackBuffers();
	_directDrawManager.initVideo(width, height, bpp, numBackSurfaces);

	_vm->_screen->create(width, height, g_system->getScreenFormat());
	_frontRenderSurface = new OSVideoSurface(this, nullptr);
	_frontRenderSurface->setSurface(this, _directDrawManager._mainSurface);

	_backSurfaces.resize(numBackSurfaces);
	for (uint idx = 0; idx < numBackSurfaces; ++idx) {
		_backSurfaces[idx]._surface = new OSVideoSurface(this, nullptr);
		_backSurfaces[idx]._surface->setSurface(this, _directDrawManager._backSurfaces[idx]);
	}

	// Load fonts
	_fonts[0].load(149);
	_fonts[1].load(151);
	_fonts[2].load(152);
	_fonts[3].load(153);

	// Load the cursors
	loadCursors();
}

void OSScreenManager::drawCursors() {
	// The original did both text and mouse cursor drawing here.
	// For ScummVM, we only need to worry about the text cursor
	_textCursor->draw();
}

DirectDrawSurface *OSScreenManager::getDDSurface(SurfaceNum surfaceNum) {
	if (surfaceNum == SURFACE_PRIMARY)
		return _directDrawManager._mainSurface;
	else if (surfaceNum < (int)_backSurfaces.size())
		return _directDrawManager._backSurfaces[surfaceNum];
	else
		return nullptr;
}

CVideoSurface *OSScreenManager::lockSurface(SurfaceNum surfaceNum) {
	CVideoSurface *surface = getSurface(surfaceNum);
	surface->lock();
	return surface;
}

void OSScreenManager::unlockSurface(CVideoSurface *surface) {
	surface->unlock();
}

CVideoSurface *OSScreenManager::getSurface(SurfaceNum surfaceNum) const {
	if (surfaceNum == SURFACE_PRIMARY)
		return _frontRenderSurface;
	else if (surfaceNum >= 0 && surfaceNum < (int)_backSurfaces.size())
		return _backSurfaces[surfaceNum]._surface;
	else
		return nullptr;
}

void OSScreenManager::fillRect(SurfaceNum surfaceNum, Rect *rect, byte r, byte g, byte b) {
	DirectDrawSurface *surface = getDDSurface(surfaceNum);
	if (!surface)
		return;

	// If bounds are provided, clip and use them. Otherwise, use entire surface area
	Rect surfaceRect(0, 0, surface->getWidth(), surface->getHeight());
	Rect tempRect;

	if (rect) {
		tempRect = *rect;
		tempRect.clip(surfaceRect);
	} else {
		tempRect = surfaceRect;
	}

	// Constrain the fill area to the set modification area of the surface
	Rect surfaceBounds = (surfaceNum == SURFACE_PRIMARY) ? _frontSurfaceBounds :
		_backSurfaces[surfaceNum]._bounds;
	if (!surfaceBounds.isEmpty())
		tempRect.constrain(surfaceBounds);

	// If there is any area defined, clear it
	if (tempRect.isValidRect())
		surface->fillRect(&tempRect, r, g, b);
}

void OSScreenManager::blitFrom(SurfaceNum surfaceNum, CVideoSurface *src,
		const Point *destPos, const Rect *srcRect) {
	// Get the dest surface
	CVideoSurface *destSurface = _frontRenderSurface;
	if (surfaceNum < -1)
		return;
	if (surfaceNum >= 0 && surfaceNum < (int)_backSurfaces.size())
		destSurface = _backSurfaces[surfaceNum]._surface;
	if (!destSurface->hasSurface())
		return;

	Point destPoint = destPos ? *destPos : Point(0, 0);
	Rect srcBounds = srcRect ? *srcRect : Rect(0, 0, src->getWidth(), src->getHeight());
	Rect *bounds = &srcBounds;
	Rect rect2;

	Rect surfaceBounds = (surfaceNum == SURFACE_PRIMARY) ? _frontSurfaceBounds :
		_backSurfaces[surfaceNum]._bounds;

	if (!surfaceBounds.isEmpty()) {
		// Perform clipping to the bounds of the back surface
		rect2 = srcBounds;
		rect2.translate(-srcBounds.left, -srcBounds.top);
		rect2.translate(destPoint.x, destPoint.y);
		rect2.constrain(surfaceBounds);

		rect2.translate(-destPoint.x, -destPoint.y);
		rect2.translate(srcBounds.left, srcBounds.top);

		if (rect2.isEmpty())
			return;

		destPoint.x += rect2.left - srcBounds.left;
		destPoint.y += rect2.top - srcBounds.top;
		bounds = &rect2;
	}

	if (!bounds->isEmpty())
		destSurface->blitFrom(destPoint, src, bounds);
}

void OSScreenManager::blitFrom(SurfaceNum surfaceNum, const Rect *rect, CVideoSurface *src, int v) {
	// Get the dest surface
	CVideoSurface *destSurface = _frontRenderSurface;
	if (surfaceNum < -1)
		return;
	if (surfaceNum >= 0 && surfaceNum < (int)_backSurfaces.size())
		destSurface = _backSurfaces[surfaceNum]._surface;
	if (!destSurface->hasSurface())
		return;

	if (!rect->isEmpty())
		destSurface->blitFrom(Point(rect->left, rect->top), src, rect);
}

int OSScreenManager::writeString(int surfaceNum, const Rect &destRect,
		int yOffset, const CString &str, CTextCursor *textCursor) {
	CVideoSurface *surface;
	Rect bounds;

	if (surfaceNum >= 0 && surfaceNum < (int)_backSurfaces.size()) {
		surface = _backSurfaces[surfaceNum]._surface;
		bounds = _backSurfaces[surfaceNum]._bounds;
	} else if (surfaceNum == SURFACE_PRIMARY) {
		surface = _frontRenderSurface;
		bounds = _frontSurfaceBounds;
	} else {
		return -1;
	}

	return _fonts[_fontNumber].writeString(surface, destRect, bounds,
		yOffset, str, textCursor);
}

void OSScreenManager::writeString(int surfaceNum, const Point &destPos,
		const Rect &clipRect, const CString &str, int lineWidth) {
	CVideoSurface *surface;
	Rect bounds;

	if (surfaceNum >= 0 && surfaceNum < (int)_backSurfaces.size()) {
		surface = _backSurfaces[surfaceNum]._surface;
		bounds = _backSurfaces[surfaceNum]._bounds;
	} else if (surfaceNum == -1) {
		surface = _frontRenderSurface;
		bounds = Rect(0, 0, surface->getWidth(), surface->getHeight());
	} else {
		return;
	}

	Rect destRect = clipRect;
	destRect.constrain(bounds);

	_fonts[_fontNumber].writeString(surface, destPos, destRect, str, lineWidth);
}

void OSScreenManager::setFontColor(byte r, byte g, byte b) {
	_fonts[_fontNumber].setColor(r, g, b);
}

int OSScreenManager::getTextBounds(const CString &str, int maxWidth, Point *sizeOut) const {
	return _fonts[_fontNumber].getTextBounds(str, maxWidth, sizeOut);
}

int OSScreenManager::getFontHeight() const {
	return _fonts[_fontNumber]._fontHeight;
}

int OSScreenManager::stringWidth(const CString &str) {
	return _fonts[_fontNumber].stringWidth(str);
}

void OSScreenManager::frameRect(SurfaceNum surfaceNum, const Rect &rect, byte r, byte g, byte b) {
	Rect top(rect.left, rect.top, rect.right, rect.top + 1);
	fillRect(surfaceNum, &top, r, g, b);
	Rect bottom(rect.left, rect.bottom - 1, rect.right, rect.bottom);
	fillRect(surfaceNum, &bottom, r, g, b);
	Rect left(rect.left, rect.top, rect.left + 1, rect.bottom);
	fillRect(surfaceNum, &left, r, g, b);
	Rect right(rect.right - 1, rect.top, rect.right, rect.bottom);
}

void OSScreenManager::clearSurface(SurfaceNum surfaceNum, Rect *bounds) {
	if (surfaceNum == SURFACE_PRIMARY)
		_directDrawManager._mainSurface->fill(bounds, 0);
	else if (surfaceNum >= 0 && surfaceNum < (int)_backSurfaces.size())
		_directDrawManager._backSurfaces[surfaceNum]->fill(bounds, 0);
}

void OSScreenManager::resizeSurface(CVideoSurface *surface, int width, int height, int bpp) {
	DirectDrawSurface *ddSurface = _directDrawManager.createSurface(width, height, bpp, 0);
	surface->setSurface(this, ddSurface);
}

CVideoSurface *OSScreenManager::createSurface(int w, int h, int bpp) {
	DirectDrawSurface *ddSurface = _directDrawManager.createSurface(w, h, bpp, 0);
	return new OSVideoSurface(this, ddSurface);
}

CVideoSurface *OSScreenManager::createSurface(const CResourceKey &key) {
	return new OSVideoSurface(this, key);
}

void OSScreenManager::showCursor() {
	CScreenManager::_screenManagerPtr->_mouseCursor->unsuppressCursor();
}

void OSScreenManager::hideCursor() {
	CScreenManager::_screenManagerPtr->_mouseCursor->suppressCursor();
}

void OSScreenManager::destroyFrontAndBackBuffers() {
	delete _frontRenderSurface;
	_frontRenderSurface = nullptr;

	for (uint idx = 0; idx < _backSurfaces.size(); ++idx)
		delete _backSurfaces[idx]._surface;
	_backSurfaces.clear();
}

void OSScreenManager::loadCursors() {
	if (_mouseCursor) {
		hideCursor();
		delete _mouseCursor;
	}
	_mouseCursor = new CMouseCursor(this);

	if (!_textCursor) {
		_textCursor = new CTextCursor(this);
	}
}

} // End of namespace Titanic
