/* 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/>.
 *
 */

/*
 * This file is based on WME Lite.
 * http://dead-code.org/redir.php?target=wmelite
 * Copyright (c) 2011 Jan Nedoma
 */

#include "engines/wintermute/base/base_scriptable.h"
#include "engines/wintermute/base/scriptables/script_stack.h"
#include "engines/wintermute/base/scriptables/script.h"
#include "engines/wintermute/base/scriptables/script_value.h"
#include "engines/wintermute/base/scriptables/script_ext_mem_buffer.h"
#include "common/file.h"

namespace Wintermute {

IMPLEMENT_PERSISTENT(SXMemBuffer, false)

BaseScriptable *makeSXMemBuffer(BaseGame *inGame, ScStack *stack) {
	return new SXMemBuffer(inGame, stack);
}

//////////////////////////////////////////////////////////////////////////
SXMemBuffer::SXMemBuffer(BaseGame *inGame, ScStack *stack) : BaseScriptable(inGame) {
	stack->correctParams(1);
	_buffer = nullptr;
	_size = 0;

	int newSize = stack->pop()->getInt();
	resize(MAX(0, newSize));
}

//////////////////////////////////////////////////////////////////////////
SXMemBuffer::SXMemBuffer(BaseGame *inGame, void *buffer) : BaseScriptable(inGame) {
	_size = 0;
	_buffer = buffer;
}


//////////////////////////////////////////////////////////////////////////
SXMemBuffer::~SXMemBuffer() {
	cleanup();
}

//////////////////////////////////////////////////////////////////////////
void *SXMemBuffer::scToMemBuffer() {
	return _buffer;
}

//////////////////////////////////////////////////////////////////////////
void SXMemBuffer::cleanup() {
	if (_size) {
		free(_buffer);
	}
	_buffer = nullptr;
	_size = 0;
}

//////////////////////////////////////////////////////////////////////////
bool SXMemBuffer::resize(int newSize) {
	int oldSize = _size;

	if (_size == 0) {
		_buffer = malloc(newSize);
		if (_buffer) {
			_size = newSize;
		}
	} else {
		void *newBuf = realloc(_buffer, newSize);
		if (!newBuf) {
			if (newSize == 0) {
				_buffer = newBuf;
				_size = newSize;
			} else {
				return STATUS_FAILED;
			}
		} else {
			_buffer = newBuf;
			_size = newSize;
		}
	}

	if (_buffer && _size > oldSize) {
		memset((byte *)_buffer + oldSize, 0, _size - oldSize);
	}
	return STATUS_OK;
}

//////////////////////////////////////////////////////////////////////////
bool SXMemBuffer::checkBounds(ScScript *script, int start, int length) {
	if (_buffer == nullptr) {
		script->runtimeError("Cannot use Set/Get methods on an uninitialized memory buffer");
		return false;
	}
	if (_size == 0) {
		return true;
	}

	if (start < 0 || length == 0 || start + length > _size) {
		script->runtimeError("Set/Get method call is out of bounds");
		return false;
	} else {
		return true;
	}
}

//////////////////////////////////////////////////////////////////////////
const char *SXMemBuffer::scToString() {
	return "[membuffer object]";
}


//////////////////////////////////////////////////////////////////////////
bool SXMemBuffer::scCallMethod(ScScript *script, ScStack *stack, ScStack *thisStack, const char *name) {
	//////////////////////////////////////////////////////////////////////////
	// SetSize
	//////////////////////////////////////////////////////////////////////////
	if (strcmp(name, "SetSize") == 0) {
		stack->correctParams(1);
		int newSize = stack->pop()->getInt();
		newSize = MAX(0, newSize);
		if (DID_SUCCEED(resize(newSize))) {
			stack->pushBool(true);
		} else {
			stack->pushBool(false);
		}

		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// GetBool
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "GetBool") == 0) {
		stack->correctParams(1);
		int start = stack->pop()->getInt();
		if (!checkBounds(script, start, sizeof(byte))) {
			stack->pushNULL();
		} else {
			stack->pushBool(*((byte *)_buffer + start) != 0);
		}

		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// GetByte
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "GetByte") == 0) {
		stack->correctParams(1);
		int start = stack->pop()->getInt();
		if (!checkBounds(script, start, sizeof(byte))) {
			stack->pushNULL();
		} else {
			stack->pushInt(*(byte *)((byte *)_buffer + start));
		}

		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// GetShort
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "GetShort") == 0) {
		stack->correctParams(1);
		int start = stack->pop()->getInt();
		if (!checkBounds(script, start, sizeof(short))) {
			stack->pushNULL();
		} else {
			stack->pushInt(65536 + * (short *)((byte *)_buffer + start));
		}

		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// GetInt / GetLong
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "GetInt") == 0 || strcmp(name, "GetLong") == 0) {
		stack->correctParams(1);
		int start = stack->pop()->getInt();
		if (!checkBounds(script, start, sizeof(int))) {
			stack->pushNULL();
		} else {
			stack->pushInt(*(int *)((byte *)_buffer + start));
		}

		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// GetFloat
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "GetFloat") == 0) {
		stack->correctParams(1);
		int start = stack->pop()->getInt();
		if (!checkBounds(script, start, sizeof(float))) {
			stack->pushNULL();
		} else {
			stack->pushFloat(*(float *)((byte *)_buffer + start));
		}

		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// GetDouble
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "GetDouble") == 0) {
		stack->correctParams(1);
		int start = stack->pop()->getInt();
		if (!checkBounds(script, start, sizeof(double))) {
			stack->pushNULL();
		} else {
			stack->pushFloat(*(double *)((byte *)_buffer + start));
		}

		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// GetString
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "GetString") == 0) {
		stack->correctParams(2);
		int start = stack->pop()->getInt();
		int length = stack->pop()->getInt();

		// find end of string
		if (length == 0 && start >= 0 && start < _size) {
			for (int i = start; i < _size; i++) {
				if (((char *)_buffer)[i] == '\0') {
					length = i - start;
					break;
				}
			}
		}

		if (!checkBounds(script, start, length)) {
			stack->pushNULL();
		} else {
			char *str = new char[length + 1];
			strncpy(str, (const char *)_buffer + start, length);
			str[length] = '\0';
			stack->pushString(str);
			delete[] str;
		}
		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// GetPointer
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "GetPointer") == 0) {
		stack->correctParams(1);
		int start = stack->pop()->getInt();
		if (!checkBounds(script, start, sizeof(void *))) {
			stack->pushNULL();
		} else {
			void *pointer = *(void **)((byte *)_buffer + start);
			SXMemBuffer *buf = new SXMemBuffer(_game, pointer);
			stack->pushNative(buf, false);
		}
		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// SetBool
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "SetBool") == 0) {
		stack->correctParams(2);
		int start = stack->pop()->getInt();
		bool val = stack->pop()->getBool();

		if (!checkBounds(script, start, sizeof(byte))) {
			stack->pushBool(false);
		} else {
			*((byte *)_buffer + start) = (byte)val;
			stack->pushBool(true);
		}
		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// SetByte
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "SetByte") == 0) {
		stack->correctParams(2);
		int start = stack->pop()->getInt();
		byte val = (byte)stack->pop()->getInt();

		if (!checkBounds(script, start, sizeof(byte))) {
			stack->pushBool(false);
		} else {
			*(byte *)((byte *)_buffer + start) = val;
			stack->pushBool(true);
		}
		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// SetShort
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "SetShort") == 0) {
		stack->correctParams(2);
		int start = stack->pop()->getInt();
		short val = (short)stack->pop()->getInt();

		if (!checkBounds(script, start, sizeof(short))) {
			stack->pushBool(false);
		} else {
			*(short *)((byte *)_buffer + start) = val;
			stack->pushBool(true);
		}
		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// SetInt / SetLong
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "SetInt") == 0 || strcmp(name, "SetLong") == 0) {
		stack->correctParams(2);
		int start = stack->pop()->getInt();
		int val = stack->pop()->getInt();

		if (!checkBounds(script, start, sizeof(int))) {
			stack->pushBool(false);
		} else {
			*(int *)((byte *)_buffer + start) = val;
			stack->pushBool(true);
		}
		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// SetFloat
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "SetFloat") == 0) {
		stack->correctParams(2);
		int start = stack->pop()->getInt();
		float val = (float)stack->pop()->getFloat();

		if (!checkBounds(script, start, sizeof(float))) {
			stack->pushBool(false);
		} else {
			*(float *)((byte *)_buffer + start) = val;
			stack->pushBool(true);
		}
		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// SetDouble
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "SetDouble") == 0) {
		stack->correctParams(2);
		int start = stack->pop()->getInt();
		double val = stack->pop()->getFloat();

		if (!checkBounds(script, start, sizeof(double))) {
			stack->pushBool(false);
		} else {
			*(double *)((byte *)_buffer + start) = val;
			stack->pushBool(true);
		}
		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// SetString
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "SetString") == 0) {
		stack->correctParams(2);
		int start = stack->pop()->getInt();
		const char *val = stack->pop()->getString();

		if (!checkBounds(script, start, strlen(val) + 1)) {
			stack->pushBool(false);
		} else {
			memcpy((byte *)_buffer + start, val, strlen(val) + 1);
			stack->pushBool(true);
		}
		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// SetPointer
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "SetPointer") == 0) {
		stack->correctParams(2);
		int start = stack->pop()->getInt();
		/* ScValue *val = */ stack->pop();

		if (!checkBounds(script, start, sizeof(void *))) {
			stack->pushBool(false);
		} else {
			/*
			int pointer = (int)Val->getMemBuffer();
			memcpy((byte *)_buffer+Start, &Pointer, sizeof(void*));
			stack->pushBool(true);
			*/
			// TODO: fix
			debug(3, "SXMemBuffer::ScCallMethod - SetPointer Bounds FIXME");
			stack->pushBool(false);
		}
		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// DEBUG_Dump
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "DEBUG_Dump") == 0) {
		stack->correctParams(0);
		if (_buffer && _size) {
			warning("SXMemBuffer::ScCallMethod - DEBUG_Dump");
			Common::DumpFile f;
			f.open("buffer.bin");
			f.write(_buffer, _size);
			f.close();
		}
		stack->pushNULL();
		return STATUS_OK;
	} else {
		return STATUS_FAILED;
	}
}


//////////////////////////////////////////////////////////////////////////
ScValue *SXMemBuffer::scGetProperty(const char *name) {
	_scValue->setNULL();

	//////////////////////////////////////////////////////////////////////////
	// Type (RO)
	//////////////////////////////////////////////////////////////////////////
	if (strcmp(name, "Type") == 0) {
		_scValue->setString("membuffer");
		return _scValue;
	}

	//////////////////////////////////////////////////////////////////////////
	// Size (RO)
	//////////////////////////////////////////////////////////////////////////
	if (strcmp(name, "Size") == 0) {
		_scValue->setInt(_size);
		return _scValue;
	} else {
		return BaseScriptable::scGetProperty(name);
	}
}


//////////////////////////////////////////////////////////////////////////
bool SXMemBuffer::scSetProperty(const char *name, ScValue *value) {
	/*
	//////////////////////////////////////////////////////////////////////////
	// Length
	//////////////////////////////////////////////////////////////////////////
	if (strcmp(name, "Length")==0) {
	    int origLength = _length;
	    _length = MAX(value->getInt(0), 0);

	    char propName[20];
	    if (_length < origLength) {
	        for(int i=_length; i < origLength; i++) {
	            Common::sprintf_s(propName, "%d", i);
	            _values->DeleteProp(propName);
	        }
	    }
	    return STATUS_OK;
	}
	else*/ return BaseScriptable::scSetProperty(name, value);
}


//////////////////////////////////////////////////////////////////////////
bool SXMemBuffer::persist(BasePersistenceManager *persistMgr) {

	BaseScriptable::persist(persistMgr);

	persistMgr->transferSint32(TMEMBER(_size));

	if (persistMgr->getIsSaving()) {
		if (_size > 0) {
			persistMgr->putBytes((byte *)_buffer, _size);
		}
	} else {
		if (_size > 0) {
			_buffer = malloc(_size);
			persistMgr->getBytes((byte *)_buffer, _size);
		} else {
			_buffer = nullptr;
		}
	}

	return STATUS_OK;
}


//////////////////////////////////////////////////////////////////////////
int SXMemBuffer::scCompare(BaseScriptable *val) {
	if (_buffer == val->scToMemBuffer()) {
		return 0;
	} else {
		return 1;
	}
}

} // End of namespace Wintermute
