/* 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 "bagel/baglib/text_object.h"
#include "bagel/baglib/master_win.h"
#include "bagel/baglib/pan_window.h"
#include "bagel/baglib/zoom_pda.h"
#include "bagel/baglib/base_pda.h"
#include "bagel/baglib/menu_dlg.h"
#include "bagel/baglib/bagel.h"
#include "bagel/baglib/rp_object.h"
#include "bagel/boflib/gfx/text.h"

namespace Bagel {

extern bool g_pauseTimerFl;

CBagTextObject::CBagTextObject() : CBagObject() {
	_xObjType = TEXT_OBJ;
	_nDX = 80;
	_nDY = 20;
	_psText = nullptr;
	CBagObject::setOverCursor(1); // Switch to cursor 1, 4 doesn't exist.

	_nPointSize = 16;
	_nFGColor = CTEXT_COLOR;
	_psInitInfo = nullptr;
	_bCaption = false;
	_bTitle = false;
	_bReAttach = false;
	_nTextFont = FONT_DEFAULT;

	setRPObject(nullptr);
}

CBagTextObject::~CBagTextObject() {
	delete _psInitInfo;
	_psInitInfo = nullptr;

	CBagTextObject::detach();
}

CBofRect CBagTextObject::getRect() {
	CBofPoint p = getPosition();
	CBofSize s = getSize();
	CBofRect r = CBofRect(p, s);
	return r;
}

ErrorCode CBagTextObject::update(CBofBitmap *pBmp, CBofPoint pt, CBofRect *pSrcRect, int) {
	assert(isValidObject(this));
	assert(pBmp != nullptr);
	assert(pSrcRect != nullptr);

	// assume no error
	ErrorCode errorCode = ERR_NONE;

	if ((pBmp != nullptr) && isAttached() && !(getText().isEmpty())) {

		if (pBmp->getRect().ptInRect(pt)) {

			CBofRect r(pt, pSrcRect->size());

			int nPointSize = _nPointSize;
			int nFormat = FORMAT_CENTER_LEFT;
			if (!_bTitle) {
				byte c1 = 3;
				byte c2 = 9;

				CBofRect cBevel;
				cBevel.intersectRect(pBmp->getRect(), r);

				int left = cBevel.left;
				int top = cBevel.top;
				int right = cBevel.right;
				int bottom = cBevel.bottom;

				r.left += 6;
				r.top += 3;
				r.right -= 5;
				r.bottom -= 5;

				int i;
				for (i = 1; i <= 3; i++) {
					pBmp->line(left + i, bottom - i, right - i, bottom - i, c1);
					pBmp->line(right - i, bottom - i, right - i, top + i - 1, c1);
				}

				for (i = 1; i <= 3; i++) {
					pBmp->line(left + i, bottom - i, left + i, top + i - 1, c2);
					pBmp->line(left + i, top + i - 1, right - i, top + i - 1, c2);
				}
				nPointSize = 16;
				nFormat = FORMAT_TOP_LEFT;

			} else {
				r.left += 1;
			}

			errorCode = paintText(pBmp, &r, getText(), mapWindowsPointSize(nPointSize), TEXT_NORMAL, _nFGColor, JUSTIFY_WRAP, nFormat, _nTextFont);

			// This object does not need to be updated now...
			setDirty(false);
		}
	}

	return errorCode;
}

ErrorCode CBagTextObject::attach() {
	assert(isValidObject(this));

	if (!getFileName().right(4).find(".TXT") || !getFileName().right(4).find(".txt")) {
		// Prevent memory leak
		delete _psText;
		_psText = nullptr;

		// Allocate a new string
		_psText = new CBofString;

		CBofFile fpTextFile(getFileName());

		if (!fpTextFile.errorOccurred()) {
			// Allocate the buffers
			uint32 nFileLen = fpTextFile.getLength();
			char *pTextBuff = (char *)bofCleanAlloc(nFileLen + 1);

			// Read the text file into buffers
			fpTextFile.read(pTextBuff, nFileLen);
			fpTextFile.close();

			*_psText += pTextBuff;

			if (_psInitInfo != nullptr) {
				CBagVar *pVar = g_VarManager->getVariable(*_psInitInfo);

				if (pVar != nullptr) {
					_bReAttach = true;
					_psText->replaceStr("%s", pVar->getValue());
				}
			}

			bofFree(pTextBuff);

		} else {
			reportError(ERR_FOPEN, "Failed to create a CBofFile for %s", getFileName().getBuffer());
		}

		if (isCaption()) {
			recalcTextRect(true);
		}

	} else {
		// The Text is in the Bagel script, rather than a .txt file
		// Prevent memory leak
		delete _psText;
		_psText = nullptr;

		// Allocate a new string
		_psText = new CBofString;
		*_psText = getFileName();

		// Replace any underscores with spaces
		_psText->replaceChar('_', ' ');

		recalcTextRect(false);
	}

	// If this guy is linked to a residue printing object, make sure he knows
	// we've been attached.
	CBagRPObject *pRPObj = (CBagRPObject *)getRPObject();

	if (pRPObj != nullptr) {
		pRPObj->setTimeSet(false);
	}

	return CBagObject::attach();
}

ErrorCode CBagTextObject::detach() {
	assert(isValidObject(this));

	delete _psText;
	_psText = nullptr;

	return CBagObject::detach();
}

const CBofString &CBagTextObject::getText() {
	if (_psText)
		return *_psText;

	return getFileName();
}

void CBagTextObject::setText(const CBofString &s) {
	if (_psText) {
		*_psText = s;
	} else {
		setFileName(s);
	}

	recalcTextRect(!getFileName().right(4).find(".TXT") || !getFileName().right(4).find(".txt"));
}

//   Takes in info and then removes the relative information and returns the info
//   without the relevant info.
//
ParseCodes CBagTextObject::setInfo(CBagIfstream &istr) {
	bool nObjectUpdated = false;

	while (!istr.eof()) {
		istr.eatWhite();

		char ch = (char)istr.peek();
		switch (ch) {

		//
		//  VAR var - var is a BAGEL CBagVar variable (replaces all %s in text)
		//
		case 'V': {
			char szLocalStr[256];
			szLocalStr[0] = 0;
			CBofString sStr(szLocalStr, 256);

			// Need to use this field, so no one else can
			assert(_psInitInfo == nullptr);

			getAlphaNumFromStream(istr, sStr);

			if (!sStr.find("VAR")) {
				istr.eatWhite();
				getAlphaNumFromStream(istr, sStr);
				setInitInfo(sStr);
				nObjectUpdated = true;
			} else {
				putbackStringOnStream(istr, sStr);
			}
			break;
		}

		//
		//  SIZE n - n point size of the txt
		//
		case 'S': {
			char szLocalStr[256];
			szLocalStr[0] = 0;
			CBofString sStr(szLocalStr, 256);

			getAlphaNumFromStream(istr, sStr);

			if (!sStr.find("SIZE")) {
				istr.eatWhite();
				int n;
				getIntFromStream(istr, n);
				_nPointSize = (byte)n;
				nObjectUpdated = true;

				// WORKAROUND: Reduce the font size of Cilia's full-screen log
				// in ScummVM so that it fits on the screen
				if (_nPointSize == 28 && istr.getSize() == 359105)
					_nPointSize = 26;

			} else {
				putbackStringOnStream(istr, sStr);
			}
			break;
		}

		//
		//  FONT MONO or DEFAULT
		//
		case 'F': {
			char szLocalStr[256];
			szLocalStr[0] = 0;
			CBofString sStr(szLocalStr, 256);

			getAlphaNumFromStream(istr, sStr);

			if (!sStr.find("FONT")) {
				istr.eatWhite();
				int n;
				getIntFromStream(istr, n);
				_nTextFont = MapFont(n);
				nObjectUpdated = true;
			} else {
				putbackStringOnStream(istr, sStr);
			}
			// FIXME: Missing break?
		}
		// fallthrough

		//
		//  AS [CAPTION]  - how to run the link
		//
		case 'A': {
			char szLocalStr[256];
			szLocalStr[0] = 0;
			CBofString sStr(szLocalStr, 256);
			getAlphaNumFromStream(istr, sStr);

			if (!sStr.find("AS")) {
				istr.eatWhite();
				getAlphaNumFromStream(istr, sStr);
				if (!sStr.find("CAPTION")) {
					_bCaption = true;
					nObjectUpdated = true;

				} else if (!sStr.find("TITLE")) {
					_bTitle = true;
					nObjectUpdated = true;

				} else {
					putbackStringOnStream(istr, sStr);
					putbackStringOnStream(istr, "AS ");
				}
			} else {
				putbackStringOnStream(istr, sStr);
			}
			break;
		}

		//
		//  COLOR n - n color index
		//
		case 'C': {
			char szLocalStr[256];
			szLocalStr[0] = 0;
			CBofString sStr(szLocalStr, 256);

			getAlphaNumFromStream(istr, sStr);

			if (!sStr.find("COLOR")) {
				int nColor;
				istr.eatWhite();
				getIntFromStream(istr, nColor);
				setColor(nColor);
				nObjectUpdated = true;
			} else {
				putbackStringOnStream(istr, sStr);
			}
			break;
		}

		//
		// No match return from function
		//
		default: {
			ParseCodes parseCode = CBagObject::setInfo(istr);
			if (parseCode == PARSING_DONE) {
				return PARSING_DONE;
			}

			if (parseCode == UPDATED_OBJECT) {
				nObjectUpdated = true;
			} else { // rc==UNKNOWN_TOKEN
				if (nObjectUpdated)
					return UPDATED_OBJECT;

				return UNKNOWN_TOKEN;
			}
			break;
		}
		}
	}

	return PARSING_DONE;
}

void CBagTextObject::setColor(int nColor) {
	switch (nColor) {
	case 0:
		_nFGColor = RGB(0, 0, 0);
		break; // Black
	case 1:
		_nFGColor = RGB(226, 50, 51);
		break; // Red
	case 2:
		_nFGColor = RGB(255, 255, 255);
		break; // Yellow
	case 3:
		_nFGColor = RGB(255, 255, 255);
		break; // Green
	case 4:
		_nFGColor = RGB(255, 255, 255);
		break; // Green
	case 5:
		_nFGColor = RGB(0, 0, 255);
		break; // Blue
	case 6:
		_nFGColor = RGB(255, 0, 255);
		break;
	case 7:
		_nFGColor = CTEXT_WHITE;
		break; // White
	case 8:
		_nFGColor = CTEXT_YELLOW;
		break; // Yellow (chat highlight)
	default:
		break;
	}
}

void CBagTextObject::setProperty(const CBofString &sProp, int nVal) {
	if (!sProp.find("SIZE"))
		setPointSize(nVal);
	else if (!sProp.find("FONT"))
		setFont(MapFont(nVal));
	else if (!sProp.find("COLOR"))
		setColor(nVal);
	else
		CBagObject::setProperty(sProp, nVal);
}

int CBagTextObject::getProperty(const CBofString &sProp) {
	if (!sProp.find("SIZE"))
		return getPointSize();
	if (!sProp.find("FONT"))
		return getFont();
	if (!sProp.find("COLOR"))
		return getColor();

	return CBagObject::getProperty(sProp);
}

bool CBagTextObject::runObject() {
	char szLocalBuff[256];
	CBofString sStr(szLocalBuff, 256);

	if (_bCaption && isImmediateRun()) {

		// Re-attach this object to get any change in a variable
		// (Must be using the VAR token).
		if (_bReAttach) {
			attach();
		}

		CBagel *pApp = CBagel::getBagApp();
		if (pApp != nullptr) {
			CBagMasterWin *pWin = pApp->getMasterWnd();
			if (pWin != nullptr) {
				CBagStorageDevWnd *pParent = pWin->getCurrentStorageDev();

				CBofRect cRect(80, 10, 80 + 480 /*- 1 */, 10 + getRect().height() - 1 + 5);
				CBofPoint cPoint(0, 0);

				CBofPalette *pPal = pApp->getPalette();
				CBofBitmap cBmp(cRect.width(), cRect.height(), pPal);

				cBmp.fillRect(nullptr, pPal->getNearestIndex(RGB(92, 92, 92)));

				CBagMenuDlg cDlg;
				cDlg.createDlg(pParent, pPal, &cRect);

				update(cDlg.getBackdrop(), cPoint, &cRect);

				sStr = "BPDA_WLD";
				CBagPDA *pPDA = (CBagPDA *)g_SDevManager->getStorageDevice(sStr);

				// If we're in the zoom pda then put this box at the
				// bottom of the zoom rect.
				sStr = "BPDAZ_WLD";
				SBZoomPda *pPDAZ = (SBZoomPda *)g_SDevManager->getStorageDevice(sStr);

				if (pPDAZ && pPDAZ->getZoomed() == true) {
					CBofRect zRect = pPDAZ->getViewRect();
					assert(zRect.height() > 0 && zRect.height() < 480);
					assert(zRect.width() > 0 && zRect.width() < 640);
					cDlg.move(80, zRect.bottom - cRect.height(), true); // xxx
				} else if ((pPDA != nullptr) && (pPDA->isActivated() || pPDA->isActivating())) {
					cDlg.move(80, 10, true);

				} else {
					int x = 80;
					int y = 360 + 10 - cRect.height();
					cDlg.move(x, y, true);
				}
				g_pauseTimerFl = true;
				cDlg.doModal();
				g_pauseTimerFl = false;
			}
		}
	}

	return false;
}

int MapFont(int nFont) {
	if (nFont == 0)
		return FONT_MONO;

	return FONT_DEFAULT;
}

void CBagTextObject::onLButtonUp(uint32 nFlags, CBofPoint *xPoint, void *pv) {
	// If there's a residue printing object, then hand this guy off to
	// him, otherwise, call back to Cbagobj.
	CBagRPObject *pRPObj = (CBagRPObject *)getRPObject();
	if (pRPObj) {
		pRPObj->onLButtonUp(nFlags, xPoint, pv);
		return;
	}

	CBagObject::onLButtonUp(nFlags, xPoint, pv);
}

void CBagTextObject::recalcTextRect(bool bTextFromFile) {
	CBofRect ViewRect;      // The rect of the area where objects are displayed
	CBofSize cDisplaySize;  // Size of rect needed to display font
	CBofSize cSize;         // Size of rect needed to display font

	assert(_psText != nullptr);

	// The window where the object are displayed
	CBagPanWindow *pPanWin = (CBagPanWindow *)(CBagel::getBagApp()->getMasterWnd()->getCurrentGameWindow());
	if (bTextFromFile) {
		if (pPanWin->getDeviceType() == SDEV_GAMEWIN) {
			ViewRect = pPanWin->getViewPort();
		} else {
			ViewRect = pPanWin->getClientRect();
		}
	}

	if (ViewRect.isRectEmpty()) {
		ViewRect.setRect(80, 10, 480 + 80 - 1, 360 + 10 - 1);
	}

	// Get the area spanned by the text (i.e. Get the pixel width and
	// height of the text string).
	CBofRect tmpRect = ViewRect;
	if (!_bTitle) {
		// Exactly match the width used in displayTextEx
		tmpRect.left += 5;
		tmpRect.right = (ViewRect.right == 640 ? PAN_AREA_WIDTH : ViewRect.right) - 5;
	}

	CBofRect textRect = calculateTextRect(tmpRect, _psText, _nPointSize, getFont());
	CBofSize stTextSize(textRect.right, textRect.bottom);

	if (bTextFromFile) {
		// Add fudge factor to make sure that all the text will fit, and not
		// get cut off.  This may cause an extra blank line of text in some
		// captions, but tough diddles, it's still better than truncating
		// some text.

		cSize.cx = stTextSize.cx;
		cSize.cy = stTextSize.cy;
	} else {
		cSize.cx = stTextSize.cx + 9;
		cSize.cy = stTextSize.cy + (_bTitle ? 0 : 7);
		setSize(cSize);
	}

	if (bTextFromFile) {
		cDisplaySize.cx = ViewRect.width();
		cDisplaySize.cy = cSize.cy;

		// If for some reason (CIC, CHAT) we got too large
		// a viewrect, cut it back to the correct width
		if (cDisplaySize.cx > PAN_AREA_WIDTH)
			cDisplaySize.cx = PAN_AREA_WIDTH;

		// Buffer the size a little for spacing etc.
		cDisplaySize.cx -= 5;

		// While the text is wider then the view area
		while (cSize.cx > cDisplaySize.cx) {
			// Increment Display Height to account for another line
			cDisplaySize.cy += cSize.cy;

			// Decrement the size of text by the width of one line
			cSize.cx -= cDisplaySize.cx;
		}

		// Add a little space at the bottom
		cDisplaySize.cy += 5;

		setSize(cDisplaySize);
	}
}

void CBagTextObject::setPSText(CBofString *p) {
	assert(isValidObject(this));

	delete _psText;
	_psText = nullptr;

	if (p != nullptr) {
		_psText = new CBofString(*p);
	}
}

} // namespace Bagel
