/* 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 "graphics/fonts/ttf.h"
#include "bagel/boflib/app.h"
#include "bagel/boflib/gfx/text.h"

namespace Bagel {

#define START_SIZE 8
#define MONO_FONT "LiberationMono-Regular.ttf"
#define SERIF_FONT_REGULAR "LiberationSans-Regular.ttf"
#define SERIF_FONT_BOLD "LiberationSans-Bold.ttf"
#define TAB_SIZE 50

int CBofText::_tabStop;
bool CBofText::_initialized;
Graphics::Font *CBofText::_defaultFonts[NUM_POINT_SIZES];
Graphics::Font *CBofText::_fixedFonts[NUM_POINT_SIZES];


ErrorCode CBofText::initialize() {
	_initialized = true;
	_tabStop = 20;		// tabstops every 20 pixels
	Common::fill(_defaultFonts, _defaultFonts + NUM_POINT_SIZES,
		(Graphics::Font *)nullptr);
	Common::fill(_fixedFonts, _fixedFonts + NUM_POINT_SIZES,
		(Graphics::Font *)nullptr);

	return ERR_NONE;
}

ErrorCode CBofText::shutdown() {
	for (int i = 0; i < NUM_POINT_SIZES; i++) {
		delete _defaultFonts[i];
		delete _fixedFonts[i];
	}

	_initialized = false;

	return ERR_NONE;
}

CBofText::CBofText() {
	initializeFields();		// Initialize stuff
}

CBofText::CBofText(const CBofRect *pRect, int nJustify, uint32 nFormatFlags) {
	// Can't access null pointers
	assert(pRect != nullptr);

	// Initialize stuff
	initializeFields();

	// Build the work areas
	setupText(pRect, nJustify, nFormatFlags);
}

CBofText::~CBofText() {
	delete _pWork;
	_pWork = nullptr;

	delete _pBackground;
	_pBackground = nullptr;
}

void CBofText::initializeFields() {
	_pBackground = nullptr;
	_pWork = nullptr;
	_bSaved = false;

	_cPosition = CBofPoint(0, 0);
	_cSize = CBofSize(0, 0);
	_cRect.setRect(0, 0, 0, 0);

	_cShadowColor = RGB(0, 0, 0);
	_nShadow_DX = 0;
	_nShadow_DY = 0;

	_nJustify = JUSTIFY_LEFT;

	_nFormatFlags = FORMAT_DEFAULT;
	_bMultiLine = false;

	_nCurSize = 10;
	_nCurWeight = TEXT_DONTCARE;
	_cTextColor = CTEXT_COLOR;
}

ErrorCode CBofText::setupText(const CBofRect *pRect, int nJustify, uint32 nFormatFlags) {
	// Can't access null pointers
	assert(pRect != nullptr);

	_nJustify = nJustify;

	// Setup the fields for location and size of the text area
	_cRect = *pRect;
	_cSize.cx = _cRect.width();
	_cSize.cy = _cRect.height();

	delete _pWork;
	_pWork = nullptr;

	delete _pBackground;
	_pBackground = nullptr;

	CBofPalette *pPalette = CBofApp::getApp()->getPalette();

	// Create a bitmap to serve as our work area as we output text
	_pWork = new CBofBitmap(_cSize.cx, _cSize.cy, pPalette);

	// Create a bitmap to hold the background we overwrite
	_pBackground = new CBofBitmap(_cSize.cx, _cSize.cy, pPalette);

	return _errCode;
}

ErrorCode CBofText::setupTextOpt(const CBofRect *pRect, int nJustify, uint32 nFormatFlags) {
	// Can't access null pointers
	assert(pRect != nullptr);

	_nJustify = nJustify;
	_nFormatFlags = nFormatFlags;

	// Setup the fields for location and size of the text area
	_cRect = *pRect;
	_cSize.cx = _cRect.width();
	_cSize.cy = _cRect.height();

	return _errCode;
}

ErrorCode CBofText::erase(CBofWindow *pWnd) {
	// Can't access null pointers
	assert(pWnd != nullptr);

	if (_pBackground != nullptr && _bSaved) {
		// Simply splat the background art back where it came from
		_errCode = _pBackground->paint(pWnd, &_cRect);
	}

	return _errCode;
}

ErrorCode CBofText::erase(CBofBitmap *pBmp) {
	// Can't access null pointers
	assert(pBmp != nullptr);

	if (_pBackground != nullptr && _bSaved) {
		// Simply splat the background art back where it came from
		_errCode = _pBackground->paint(pBmp, &_cRect);
	}

	return _errCode;
}

ErrorCode CBofText::display(CBofWindow *pWnd, const char *pszText, const int nSize, const int nWeight, const RGBCOLOR cColor, int nFont) {
	assert(isValidObject(this));

	// Can't access null pointers
	assert(pWnd != nullptr);

	_cTextColor = cColor;

	return displayText(pWnd, pszText, &_cRect, nSize, nWeight, false, nFont);
}

ErrorCode CBofText::display(CBofWindow *pWnd) {
	assert(isValidObject(this));
	assert(pWnd != nullptr);

	return display(pWnd, _cCurString, _nCurSize, _nCurWeight, _cTextColor);
}

ErrorCode CBofText::display(CBofBitmap *pBmp) {
	assert(isValidObject(this));
	assert(pBmp != nullptr);

	return display(pBmp, _cCurString, _nCurSize, _nCurWeight, _cTextColor);
}

ErrorCode CBofText::display(CBofBitmap *pBmp, const char *pszText, const int nSize, const int nWeight, const RGBCOLOR cColor, int nFont) {
	// Can't access null pointers
	assert(pBmp != nullptr);

	_cTextColor = cColor;

	return displayText(pBmp, pszText, &_cRect, nSize, nWeight, false, nFont);
}

ErrorCode CBofText::displayShadowed(CBofWindow *pWnd, const char *pszText, const int nSize, const int nWeight, const RGBCOLOR cColor, const RGBCOLOR cShadow, const int nDX, const int nDY, int nFont) {
	// Can't access null pointers
	assert(pWnd != nullptr);

	_cTextColor = cColor;
	_cShadowColor = cShadow;
	_nShadow_DX = nDX;
	_nShadow_DY = nDY;

	return displayText(pWnd, pszText, &_cRect, nSize, nWeight, true, nFont);
}

ErrorCode CBofText::displayText(CBofWindow *pWnd, const char *pszText, CBofRect *pRect, const int nSize, const int nWeight, const bool bShadowed, int nFont) {
	assert(isValidObject(this));
	assert(pWnd != nullptr);
	assert(pszText != nullptr);
	assert(pRect != nullptr);

	CBofRect cRect(0, 0, pRect->width() - 1, pRect->height() - 1);

	assert(_pBackground != nullptr);
	assert(_pWork != nullptr);

	if (!_bSaved) {
		CBofBitmap::setUseBackdrop(true);
		_pBackground->captureScreen(pWnd, pRect);
		CBofBitmap::setUseBackdrop(false);
		_bSaved = true;
	}

	_pBackground->paint(_pWork, 0, 0);

	displayTextEx(_pWork, pszText, &cRect, nSize, nWeight, bShadowed, nFont);

	_pWork->paint(pWnd, pRect);

	return _errCode;
}

ErrorCode CBofText::displayText(CBofBitmap *pBmp, const char *pszText, CBofRect *pRect, const int nSize, const int nWeight, const bool bShadowed, int nFont) {
	assert(isValidObject(this));
	assert(pBmp != nullptr);
	assert(pszText != nullptr);
	assert(pRect != nullptr);

	CBofRect cRect(0, 0, pRect->width() - 1, pRect->height() - 1);

	assert(_pWork != nullptr);

	assert(_pBackground != nullptr);

	if (!_bSaved) {
		CBofRect r = _pBackground->getRect();
		pBmp->paint(_pBackground, &r, pRect);
		_bSaved = true;
	}

	_pBackground->paint(_pWork, 0, 0);

	displayTextEx(_pWork, pszText, &cRect, nSize, nWeight, bShadowed, nFont);

	_pWork->paint(pBmp, pRect);

	return _errCode;
}

Graphics::Font *CBofText::getFont(int nFont, int nSize, int nWeight) {
	Graphics::Font *font;

	// Attempt to use one of the fonts that we pre-allocated
	if (nFont != FONT_MONO) {
		font = _defaultFonts[nSize - START_SIZE];
	} else {
		font = _fixedFonts[nSize - START_SIZE];
	}

	// Last resort - create the font now
	if (font == nullptr) {
		if (nFont != FONT_MONO) {
			font = Graphics::loadTTFFontFromArchive(SERIF_FONT_REGULAR, nSize, Graphics::kTTFSizeModeCell);
			_defaultFonts[nSize - START_SIZE] = font;
		} else {
			font = Graphics::loadTTFFontFromArchive(MONO_FONT, nSize, Graphics::kTTFSizeModeCell);
			_fixedFonts[nSize - START_SIZE] = font;
		}
	}

	return font;
}

ErrorCode CBofText::displayTextEx(CBofBitmap *pBmp, const char *pszText, CBofRect *pRect, const int nSize, const int nWeight, const bool bShadowed, int nFont) {
	assert(isValidObject(this));

	// can't access null pointers
	assert(pBmp != nullptr);
	assert(pszText != nullptr);
	assert(pRect != nullptr);

	Graphics::ManagedSurface surface = pBmp->getSurface();
	Graphics::Font *font = getFont(nFont, nSize, nWeight);
	int color;

	// Split lines
	Common::U32StringArray lines;
	font->wordWrapText(Common::U32String(pszText, Common::kWindows1252), pRect->width(), lines);

	// Iterate the lines to get the maximum width
	int maxWidth = 0;
	for (uint i = 0; i < lines.size(); ++i)
		maxWidth = MAX(maxWidth, font->getStringWidth(lines[i]));
	Common::Point textInfo(maxWidth, (int)lines.size() * font->getFontHeight());

	_cPosition.y = (_cSize.cy - textInfo.y) >> 1;

	Graphics::TextAlign align = Graphics::kTextAlignLeft;
	switch (_nJustify) {
	case JUSTIFY_CENTER:
		_cPosition.x = (_cSize.cx - textInfo.x) >> 1;
		align = Graphics::kTextAlignCenter;
		break;

	case JUSTIFY_LEFT:
		// align left
		_cPosition.x = 0;
		break;

	case JUSTIFY_RIGHT:
		_cPosition.x = _cSize.cx - textInfo.x;
		align = Graphics::kTextAlignRight;
		break;

	case JUSTIFY_WRAP:
		// Align left
		_bMultiLine = true;
		break;

	default:
		break;
	}

	// text starts relative to area for painting
	_cPosition += pRect->topLeft();

	// Note: Under ScummVM, even single line drawing uses the multiLine code
	Common::Rect newRect = *pRect;

	if ((_nFormatFlags & FORMAT_TOP_CENTER) == FORMAT_TOP_CENTER) {
		int h = lines.size() * font->getFontHeight();
		newRect.top = (newRect.top + newRect.bottom) / 2 - h / 2;
		newRect.bottom = newRect.top + h;
	}

	Common::Rect shadowRect = newRect;
	shadowRect.translate(_nShadow_DX, _nShadow_DY);

	for (uint i = 0; i < lines.size(); ++i) {
		const Common::U32String &line = lines[i];

		if (bShadowed) {
			color = CBofApp::getApp()->getPalette()->getNearestIndex(_cShadowColor);
			displayLine(font, surface, line, shadowRect.left, shadowRect.top,
				shadowRect.width(), color, align);
		}

		color = CBofApp::getApp()->getPalette()->getNearestIndex(_cTextColor);
		displayLine(font, surface, line, newRect.left, newRect.top,
			newRect.width(), color, align);

		newRect.top += font->getFontHeight();
		shadowRect.top += font->getFontHeight();
	}

	return _errCode;
}

void CBofText::displayLine(Graphics::Font *font, Graphics::ManagedSurface &surface,
		const Common::U32String &line, int left, int top, int width, int color, Graphics::TextAlign align) {
	if (!line.contains('\t')) {
		font->drawString(&surface, line, left, top, width, color, align);

	} else {
		// Special rendering of tabbed text
		Common::U32String str = line;

		while (!str.empty()) {
			if (str[0] == '\t') {
				// Move to next tab stop
				left = (left + TAB_SIZE) / TAB_SIZE * TAB_SIZE;
				str.deleteChar(0);

			} else {
				Common::U32String fragment;
				size_t tab = str.findFirstOf('\t');
				if (tab == Common::U32String::npos) {
					fragment = str;
					str.clear();
				} else {
					fragment = Common::U32String(str.c_str(), str.c_str() + tab);
					str = Common::U32String(str.c_str() + tab);
				}

				int fragmentWidth = font->getStringWidth(fragment);
				font->drawString(&surface, fragment, left, top, width, color, align);

				left += fragmentWidth;
				width -= fragmentWidth;
			}
		}
	}
}

ErrorCode paintText(CBofWindow *pWnd, CBofRect *pRect, const char *pszString, const int nSize, const int nWeight, const RGBCOLOR cColor, int nJustify, uint32 nFormatFlags, int nFont) {
	assert(pWnd != nullptr);
	assert(pRect != nullptr);

	CBofText cText(pRect, nJustify, nFormatFlags);
	return cText.display(pWnd, pszString, nSize, nWeight, cColor, nFont);
}

ErrorCode paintText(CBofBitmap *pBmp, CBofRect *pRect, const char *pszString, const int nSize, const int nWeight, const RGBCOLOR cColor, int nJustify, uint32 nFormatFlags, int nFont) {
	assert(pBmp != nullptr);
	assert(pRect != nullptr);

	CBofText cText;
	cText.setupTextOpt(pRect, nJustify, nFormatFlags);

	cText.setColor(cColor);

	return cText.displayTextEx(pBmp, pszString, pRect, nSize, nWeight, false, nFont);
}

ErrorCode paintShadowedText(CBofBitmap *pBmp, CBofRect *pRect, const char *pszString, const int nSize, const int nWeight, const RGBCOLOR cColor, int nJustify, uint32 nFormatFlags, int nFont) {
	assert(pBmp != nullptr);
	assert(pRect != nullptr);

	CBofText cText;
	cText.setupTextOpt(pRect, nJustify, nFormatFlags);

	cText.setColor(cColor);
	cText.setShadowColor(CTEXT_SHADOW_COLOR);
	cText.setShadowSize(CTEXT_SHADOW_DX, CTEXT_SHADOW_DY);

	return cText.displayTextEx(pBmp, pszString, pRect, nSize, nWeight, true, nFont);
}

CBofRect calculateTextRect(CBofWindow *pWnd, const CBofString *pStr, int nSize, int nFont) {
	return calculateTextRect(pWnd->getRect(), pStr, nSize, nFont);
}

CBofRect calculateTextRect(CBofRect rect, const CBofString *pStr, int nSize, int nFont) {
	// Get the font to use
	Graphics::Font *font = CBofText::getFont(nFont, nSize, TEXT_NORMAL);

	// Wrap the text as necessary
	Common::U32StringArray lines;
	font->wordWrapText(Common::U32String(pStr->getBuffer(), Common::kWindows1252), rect.width(), lines);

	// Iterate the lines to get the maximum width
	int maxWidth = 0;
	for (uint i = 0; i < lines.size(); ++i)
		maxWidth = MAX(maxWidth, font->getStringWidth(lines[i]));

	return CBofRect(0, 0, maxWidth, (int)lines.size() * font->getFontHeight());
}

} // namespace Bagel
