/* 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/spacebar/baglib/object.h"
#include "bagel/spacebar/baglib/menu_dlg.h"
#include "bagel/spacebar/baglib/master_win.h"
#include "bagel/boflib/file_functions.h"

namespace Bagel {
namespace SpaceBar {

extern bool g_noMenuFl;

CBofString getStringTypeOfObject(BagObjectType n) {
	switch (n) {
	case BASE_OBJ:
		return "UKN";
	case BMP_OBJ:
		return "BMP";
	case SPRITE_OBJ:
		return "SPR";
	case BUTTON_OBJ:
		return "BUT";
	case SOUND_OBJ:
		return "SND";
	case LINK_OBJ:
		return "LNK";
	case RESPRNT_OBJ:
		return "RPO";
	case DOSSIER_OBJ:
		return "DOS";
	case CHAR_OBJ:
		return "CHR";
	case AREA_OBJ:
		return "ARE";
	case MOVIE_OBJ:
		return "MOVIE";
	case VAR_OBJ:
		return "VAR";
	case COMMAND_OBJ:
		return "COMMAND";
	case THING_OBJ:
		return "TNG";
	default:
		return "UKN";
	};
}

CBagObject::CBagObject() {
	_xObjType = BASE_OBJ;
	_nX = -1;
	_nY = -1;

	// All objects now default to timeless
	_nProperties = TIMELESS;

	_nId = 0;
	_nOverCursor = 0;

	// Init state
	_nState = 0;

	_pMenu = nullptr;
	_pEvalExpr = nullptr;
	_bInteractive = true;
	_bNoMenu = false;

	// Allocate this on an as-needed basis...
	_psName = nullptr;

	// Object is dirty by default, doesn't break anything else
	// Some objects such as the pda message light are always updated
	setDirty(true);
	setAlwaysUpdate(false);

	// All messages start as not message waiting.
	setMsgWaiting(false);
}

CBagObject::~CBagObject() {
	delete _pMenu;
	_pMenu = nullptr;

	if (_psName && (_psName != &_sFileName)) {
		delete _psName;
	}

	_psName = nullptr;
}

ErrorCode CBagObject::attach() {
	setVisible();
	return CBagParseObject::attach();
}

ErrorCode CBagObject::detach() {
	setVisible(false);
	return CBagParseObject::detach();
}

ErrorCode CBagObject::update(CBofBitmap * /*pBmp*/, CBofPoint /*pt*/, CBofRect * /*pSrcRect*/, int /*nMaskColor*/) {
	return ERR_NONE;
}

bool CBagObject::onObjInteraction(CBagObject *, CBagStorageDev *) {
	return false;
}

void CBagObject::setTimeless(bool b) {
	setProperty(TIMELESS, b);
}

bool CBagObject::isForeGround() {
	return isProperty(FOREGROUND);
}

void CBagObject::setForeGround(bool b) {
	setProperty(FOREGROUND, b);
}

int CBagObject::getProperties() {
	return _nProperties;
}

void CBagObject::setProperties(int nProperties) {
	_nProperties = (uint16)nProperties;
}

const CBofString *CBagObject::getInitInfo() const {
	return nullptr;
}

int CBagObject::getProperty(const CBofString &sProp) {
	if (!sProp.find("STATE"))
		return getState();

	if (!sProp.find("MODAL"))
		return isModal();

	return 0;
}

void CBagObject::setProperty(const CBofString &sProp, int nVal) {
	if (!sProp.find("STATE"))
		setState(nVal);
	else if (!sProp.find("TIMELESS")) {
		if (nVal)
			setTimeless();
		else
			setTimeless(false);
	} else if (!sProp.find("MODAL")) {
		if (nVal)
			setModal();
		else
			setModal(false);
	}
}

void CBagObject::setProperty(BAG_OBJECT_PROPERTIES xProp, bool bVal) {
	if (bVal)
		_nProperties |= xProp;
	else
		_nProperties &= ~xProp;
}

bool CBagObject::runObject() {
	if (isTimeless())
		return true;

	g_VarManager->incrementTimers();

	return true;
}

void CBagObject::setRefId(int id) {
	assert(id >= 0 && id <= 0xFFFF);
	_nId = (uint16)id;
}

void CBagObject::setOverCursor(int curs) {
	_nOverCursor = (byte)curs;
}

void CBagObject::setState(int state) {
	assert(ABS(state) < 0x8000);
	_nState = (int16)state;
}

void CBagObject::setPosition(const CBofPoint &pos) {
	_nX = (int16)pos.x;
	_nY = (int16)pos.y;
}

ParseCodes CBagObject::setInfo(CBagIfstream &istr) {
	ParseCodes parseCode = UNKNOWN_TOKEN;

	while (!istr.eof()) {
		istr.eatWhite();
		const char ch = (char)istr.getCh();
		switch (ch) {
		//
		//  =filename.ext
		//
		case '=': {
			parseCode = UPDATED_OBJECT;
			char szLocalBuff[256];
			szLocalBuff[0] = 0;
			CBofString s(szLocalBuff, 256);
			getAlphaNumFromStream(istr, s);
			fixPathName(s);
			setFileName(s);
			break;
		}
		//
		//  { menu objects .... }  - Add menu items
		//
		case '{': {
			parseCode = UPDATED_OBJECT;
			if (!_pMenu) {
				_pMenu = new CBagMenu;

				// Try to cut down the number of Storage Devices by
				// removing these unused ones from the list.
				//
				g_SDevManager->unregisterStorageDev(_pMenu);
			}

			istr.putBack();

			char szBuff[256];
			Common::sprintf_s(szBuff, "Menu:%d", CBagMasterWin::_menuCount++);
			CBofString s(szBuff, 256);

			_pMenu->loadFileFromStream(istr, s, false);
		}
		break;
		//
		//  ^id;  - Set id
		//
		case '^': {
			parseCode = UPDATED_OBJECT;
			const char c = (char)istr.peek();
			if (Common::isDigit(c)) {
				int nId;
				getIntFromStream(istr, nId);
				setRefId(nId);
			} else {
				char szLocalBuff[256];
				szLocalBuff[0] = 0;
				CBofString s(szLocalBuff, 256);
				getAlphaNumFromStream(istr, s);
				setRefName(s);
			}
			break;
		}
		//
		//  *state;  - Set state
		//
		case '*': {
			parseCode = UPDATED_OBJECT;
			int nState;
			getIntFromStream(istr, nState);
			setState(nState);
			break;
		}
		//
		//  %cursor;  - Set cursor
		//
		case '%': {
			parseCode = UPDATED_OBJECT;
			int nCursor;
			getIntFromStream(istr, nCursor);
			setOverCursor(nCursor);
			break;
		}
		//
		//  [left,top,right,bottom]  - Set position
		//
		case '[': {
			parseCode = UPDATED_OBJECT;
			CBofRect r;
			istr.putBack();
			getRectFromStream(istr, r);
			setPosition(r.topLeft());
			if (r.width() && r.height())
				setSize(CBofSize(r.width(), r.height()));
			break;
		}
		//
		//  IS [NOT] [MOVABLE|MODAL|VISIBLE|STRETCHABLE|HIGHLIGHT|ACTIVE|TRANSPARENT|HIDE_ON_CLICK|IMMEDIATE_RUN|LOCAL|CONSTANT_UPDATE|PRELOAD|FOREGROUND]
		//
		case 'I': {
			if (istr.peek() != 'S') {
				istr.putBack();

				return parseCode;
			}
			char szLocalBuff[256];
			szLocalBuff[0] = 0;
			CBofString s(szLocalBuff, 256);
			bool b = true;
			istr.getCh();
			istr.eatWhite();
			getAlphaNumFromStream(istr, s);
			if (!s.find("NOT")) {
				getAlphaNumFromStream(istr, s);
				istr.eatWhite();
				b = false;
			}
			if (!s.find("MOVABLE")) {
				setMovable(b);
			} else if (!s.find("MODAL")) {
				setModal(b);
			} else if (!s.find("VISIBLE")) {
				setVisible(b);
			} else if (!s.find("STRETCHABLE")) {
				setStretchable(b);
			} else if (!s.find("HIGHLIGHT")) {
				setHighlight(b);
			} else if (!s.find("ACTIVE")) {
				setActive(b);
			} else if (!s.find("TRANSPARENT")) {
				setTransparent(b);
			} else if (!s.find("HIDE_ON_CLICK")) {
				setHideOnClick(b);
			} else if (!s.find("IMMEDIATE_RUN")) {
				setImmediateRun(b);
			} else if (!s.find("TIMELESS")) {
				setTimeless(b);
			} else if (!s.find("LOCAL")) {
				setLocal(b);
			} else if (!s.find("CONSTANT_UPDATE")) {
				setConstantUpdate(b);
			} else if (!s.find("PRELOAD")) {
				setPreload(b);
			} else if (!s.find("NOMENU")) {
				_bNoMenu = true;

			} else if (!s.find("FOREGROUND")) {
				setForeGround(b);
			} else {
				putbackStringOnStream(istr, s);
				if (!b)
					putbackStringOnStream(istr, " NOT ");
				putbackStringOnStream(istr, "IS ");

				return parseCode;
			}
			parseCode = UPDATED_OBJECT;
			break;
		}
		//
		//  ; is the end of the line, exit this object
		//
		case ';':
			return PARSING_DONE;

		//
		//  no match return from function
		//
		default:
			istr.putBack();
			return parseCode;
		}
	}

	return parseCode;
}

void CBagObject::onLButtonUp(uint32 nFlags, CBofPoint */*xPoint*/, void *) {
	if (getMenuPtr()) {

		CBofPoint pt = getMousePos();
		CBofWindow *pWnd = CBofApp::getApp()->getMainWindow();
		pWnd->screenToClient(&pt);

		// Just send the mouse pos
		CBofRect r = getRect();
		getMenuPtr()->trackPopupMenu(nFlags, pt.x, pt.y, pWnd, nullptr, &r);

	} else {
		g_noMenuFl = true;
	}

	runObject();
}

bool CBagObject::onMouseMove(uint32 /*nFlags*/, CBofPoint /*xPoint*/, void *) {
	return false;
}

bool CBagObject::onMouseOver(uint32, CBofPoint, void *) {
	return false;
}

CBofPoint CBagObject::getPosition() {
	return CBofPoint(_nX, _nY);
}

int CBagObject::getRefId() {
	return _nId;
}

int CBagObject::getOverCursor() {
	return _nOverCursor;
}

int CBagObject::getState() {
	return _nState;
}

CBofRect CBagObject::getRect() {
	return CBofRect(_nX, _nY, _nX - 1, _nY - 1);
}

const CBofString &CBagObject::getFileName() {
	return _sFileName;
}

CBagMenu *CBagObject::getMenuPtr() {
	return _pMenu;
}

const CBofString &CBagObject::getRefName() {
	if (_psName) {
		return *_psName;
	}

	return _emptyString;
}

// Since we have an inordinate number of bag objects that replicate the
// filename as the objects name, if this is the case, then use a pointer
// to the filename.
void CBagObject::setRefName(const CBofString &s) {
	// If we got an old one hanging around then trash it.
	if (_psName && _psName != &_sFileName) {
		delete _psName;
	}
	_psName = nullptr;

	// If it's the same as the filename, then point to the filename
	if (!_sFileName.isEmpty() && _sFileName.compare(s) == 0) {
		_psName = &_sFileName;
	} else if (!s.isEmpty()) {
		_psName = new CBofString(s);
	}
}

} // namespace SpaceBar
} // namespace Bagel
