/* 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 2
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include "common/debug.h"
#include "common/file.h"

#include "pink/objects/condition.h"
#include "pink/objects/module.h"
#include "pink/objects/side_effect.h"
#include "pink/objects/actions/action_hide.h"
#include "pink/objects/actions/action_play_with_sfx.h"
#include "pink/objects/actions/action_sound.h"
#include "pink/objects/actions/action_talk.h"
#include "pink/objects/actions/action_text.h"
#include "pink/objects/actions/walk_action.h"
#include "pink/objects/actors/audio_info_pda_button.h"
#include "pink/objects/actors/cursor_actor.h"
#include "pink/objects/actors/inventory_actor.h"
#include "pink/objects/actors/lead_actor.h"
#include "pink/objects/actors/pda_button_actor.h"
#include "pink/objects/actors/supporting_actor.h"
#include "pink/objects/handlers/handler.h"
#include "pink/objects/handlers/handler_timer.h"
#include "pink/objects/pages/game_page.h"
#include "pink/objects/sequences/seq_timer.h"
#include "pink/objects/sequences/sequence.h"
#include "pink/objects/walk/walk_location.h"

namespace Pink {

static const struct RuntimeClass {
	const char *name;
	int id;
} classMap[] = {
	{"ActionHide", kActionHide},
	{"ActionLoop", kActionLoop},
	{"ActionPlay", kActionPlay},
	{"ActionPlayWithSfx", kActionPlayWithSfx},
	{"ActionSfx", kActionSfx},
	{"ActionSound", kActionSound},
	{"ActionStill", kActionStill},
	{"ActionTalk", kActionTalk},
	{"ActionText", kActionText},
	{"Actor", kActor},
	{"AudioInfoPDAButton", kAudioInfoPDAButton},
	{"ConditionGameVariable", kConditionGameVariable},
	{"ConditionInventoryItemOwner", kConditionInventoryItemOwner},
	{"ConditionModuleVariable", kConditionModuleVariable},
	{"ConditionNotInventoryItemOwner", kConditionNotInventoryItemOwner},
	{"ConditionNotModuleVariable", kConditionNotModuleVariable},
	{"ConditionNotPageVariable", kConditionNotPageVariable},
	{"ConditionPageVariable", kConditionPageVariable},
	{"CursorActor", kCursorActor},
	{"GamePage", kGamePage},
	{"HandlerLeftClick", kHandlerLeftClick},
	{"HandlerStartPage", kHandlerStartPage},
	{"HandlerTimer", kHandlerTimer},
	{"HandlerTimerActions", kHandlerTimerActions},
	{"HandlerTimerSequences", kHandlerTimerSequences},
	{"HandlerUseClick", kHandlerUseClick},
	{"InventoryActor", kInventoryActor},
	{"InventoryItem", kInventoryItem},
	{"LeadActor", kLeadActor},
	{"ModuleProxy", kModuleProxy},
	{"PDAButtonActor", kPDAButtonActor},
	{"ParlSqPink", kParlSqPink},
	{"PubPink", kPubPink},
	{"SeqTimer", kSeqTimer},
	{"Sequence", kSequence},
	{"SequenceAudio", kSequenceAudio},
	{"SequenceItem", kSequenceItem},
	{"SequenceItemDefaultAction", kSequenceItemDefaultAction},
	{"SequenceItemLeader", kSequenceItemLeader},
	{"SequenceItemLeaderAudio", kSequenceItemLeaderAudio},
	{"SideEffectExit", kSideEffectExit},
	{"SideEffectGameVariable", kSideEffectGameVariable},
	{"SideEffectInventoryItemOwner", kSideEffectInventoryItemOwner},
	{"SideEffectLocation", kSideEffectLocation},
	{"SideEffectModuleVariable", kSideEffectModuleVariable},
	{"SideEffectPageVariable", kSideEffectPageVariable},
	{"SideEffectRandomPageVariable", kSideEffectRandomPageVariable},
	{"SupportingActor", kSupportingActor},
	{"WalkAction", kWalkAction},
	{"WalkLocation", kWalkLocation}
};

static Object *createObject(int objectId) {
	switch (objectId) {
	case kActionHide:
		return new ActionHide;
	case kActionLoop:
		return new ActionLoop;
	case kActionPlay:
		return new ActionPlay;
	case kActionPlayWithSfx:
		return new ActionPlayWithSfx;
	case kActionSfx:
		return new ActionSfx;
	case kActionSound:
		return new ActionSound;
	case kActionStill:
		return new ActionStill;
	case kActionTalk:
		return new ActionTalk;
	case kActionText:
		return new ActionText;
	case kActor:
		return new Actor;
	case kAudioInfoPDAButton:
		return new AudioInfoPDAButton;
	case kConditionGameVariable:
		return new ConditionGameVariable;
	case kConditionInventoryItemOwner:
		return new ConditionInventoryItemOwner;
	case kConditionModuleVariable:
		return new ConditionModuleVariable;
	case kConditionNotInventoryItemOwner:
		return new ConditionNotInventoryItemOwner;
	case kConditionNotModuleVariable:
		return new ConditionNotModuleVariable;
	case kConditionNotPageVariable:
		return new ConditionNotPageVariable;
	case kConditionPageVariable:
		return new ConditionPageVariable;
	case kCursorActor:
		return new CursorActor;
	case kGamePage:
		return new GamePage;
	case kHandlerLeftClick:
		return new HandlerLeftClick;
	case kHandlerStartPage:
		return new HandlerStartPage;
	case kHandlerTimer:
	case kHandlerTimerActions:
		return new HandlerTimerActions; // hack for Peril, but behavior is correct
	case kHandlerTimerSequences:
		return new HandlerTimerSequences;
	case kHandlerUseClick:
		return new HandlerUseClick;
	case kInventoryActor:
		return new InventoryActor;
	case kInventoryItem:
		return new InventoryItem;
	case kLeadActor:
		return new LeadActor;
	case kModuleProxy:
		return new ModuleProxy;
	case kPDAButtonActor:
		return new PDAButtonActor;
	case kParlSqPink:
		return new ParlSqPink;
	case kPubPink:
		return new PubPink;
	case kSeqTimer:
		return new SeqTimer;
	case kSequence:
		return new Sequence;
	case kSequenceAudio:
		return new SequenceAudio;
	case kSequenceItem:
		return new SequenceItem;
	case kSequenceItemDefaultAction:
		return new SequenceItemDefaultAction;
	case kSequenceItemLeader:
		return new SequenceItemLeader;
	case kSequenceItemLeaderAudio:
		return new SequenceItemLeaderAudio;
	case kSideEffectExit:
		return new SideEffectExit;
	case kSideEffectGameVariable:
		return new SideEffectGameVariable;
	case kSideEffectInventoryItemOwner:
		return new SideEffectInventoryItemOwner;
	case kSideEffectLocation:
		return new SideEffectLocation;
	case kSideEffectModuleVariable:
		return new SideEffectModuleVariable;
	case kSideEffectPageVariable:
		return new SideEffectPageVariable;
	case kSideEffectRandomPageVariable:
		return new SideEffectRandomPageVariable;
	case kSupportingActor:
		return new SupportingActor;
	case kWalkAction:
		return new WalkAction;
	case kWalkLocation:
		return new WalkLocation;
	default:
		error("Unknown object id");
		return nullptr;
	}
}

Archive::Archive(Common::SeekableReadStream  *stream)
	: _readStream(stream), _writeStream(nullptr) {
	_objectMap.push_back(0);
	_objectIdMap.push_back(kNullObject);
}

Archive::Archive(Common::WriteStream *stream)
	: _writeStream(stream), _readStream(nullptr) {
	_objectMap.push_back(0);
	_objectIdMap.push_back(kNullObject);
}

void Archive::mapObject(Object *obj) {
	_objectMap.push_back(obj);
	_objectIdMap.push_back(0);
}

Object *Archive::readObject() {
	bool isCopyReturned;
	Object *res = parseObject(isCopyReturned);

	if (res && !isCopyReturned)
		res->deserialize(*this);

	return res;
}

Object *Archive::parseObject(bool &isCopyReturned) {
	char className[kMaxClassLength];
	int objectId = 0;
	Object *res = nullptr;

	uint obTag = _readStream->readUint16LE();

	if (obTag == 0x0000) {
		return nullptr;
	} else if (obTag == 0xffff) {
		/* int schema = */_readStream->readUint16LE();

		int size = _readStream->readUint16LE();
		_readStream->read(className, size);
		className[size] = '\0';

		objectId = findObjectId(className + 1);

		res = createObject(objectId);
		if (!res) error("Class %s is not implemented", className);

		_objectMap.push_back(res);
		_objectIdMap.push_back(objectId);

		_objectMap.push_back(res); // Basically a hack, but behavior is all correct. MFC uses one array for pointers and ids
		_objectIdMap.push_back(objectId);

		isCopyReturned = false;
	} else if ((obTag & 0x8000) == 0) {
		res = _objectMap[obTag];

		isCopyReturned = true;
	} else {
		obTag &= ~0x8000;

		objectId = _objectIdMap[obTag];

		res = createObject(objectId);
		_objectMap.push_back(res);
		_objectIdMap.push_back(objectId);

		isCopyReturned = false;
	}

	return res;
}

static int runtimeClassCmp(const void *key, const void *elem) {
	return strcmp((const char *)key, *(const char * const *)elem);
}

uint Archive::findObjectId(const char *name) {
	RuntimeClass *found = (RuntimeClass *)bsearch(name, classMap, sizeof(classMap) / sizeof(RuntimeClass), sizeof(RuntimeClass), runtimeClassCmp);

	if (!found)
		error("Class %s is not in class Map", name);

	return found->id;
}

Common::String Archive::readString() {
	char buffer[kMaxStringLength];
	byte len = _readStream->readByte();
	assert(len <= kMaxStringLength);
	_readStream->read(buffer, len);
	return Common::String(buffer, len);
}

void Archive::skipString() {
	byte len = _readStream->readByte();
	_readStream->skip(len);
}

void Archive::writeString(const Common::String &string) {
	_writeStream->writeByte(string.size());
	_writeStream->write(string.c_str(), string.size());
}

} // End of namespace Pink