/* 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 "mads/conversations.h"
#include "mads/mads.h"
#include "mads/compression.h"
#include "common/file.h"
#include "common/util.h"	// for Common::hexdump

namespace MADS {

#define MAX_SPEAKERS 5

enum DialogCommands {
	cmdNodeEnd = 0,
	//
	cmdHide = 2,
	cmdUnhide = 3,
	cmdMessage = 4,
	//
	//
	cmdGoto = 7,
	//
	cmdAssign = 9,
	cmdDialogEnd = 255
};

struct ConvDialog {
	int16 textLineIndex;	// 0-based
	int16 speechIndex;		// 1-based
	uint16 nodeOffset;		// offset in section 6
	uint16 nodeSize;		// size in section 6
};

struct ConvNode {
	uint16 index;
	uint16 dialogCount;
	int16 unk1;
	int16 unk2;
	int16 unk3;

	Common::Array<ConvDialog> dialogs;
};

struct ConvData {
	uint16 nodeCount;		// conversation nodes, each one containing several dialog options and messages
	uint16 dialogCount;		// messages (non-selectable) + texts (selectable)
	uint16 messageCount;	// messages (non-selectable)
	uint16 textLineCount;
	uint16 unk2;
	uint16 importCount;
	uint16 speakerCount;
	Common::String portraits[MAX_SPEAKERS];
	bool speakerExists[MAX_SPEAKERS];
	Common::String speechFile;
	Common::Array<Common::String> textLines;
	Common::Array<ConvNode> convNodes;
};

GameConversation::GameConversation(MADSEngine *vm)
	: _vm(vm) {
	_running = _restoreRunning = 0;
	_nextStartNode = nullptr;
}

GameConversation::~GameConversation() {
}

void GameConversation::get(int id) {
	Common::File inFile;
	Common::String fileName = Common::String::format("CONV%03d.CNV", id);
	// TODO: Also handle the .CND file

	inFile.open(fileName);
	MadsPack convFileUnpacked(&inFile);
	Common::SeekableReadStream *convFile = convFileUnpacked.getItemStream(0);

	char buffer[16];

	ConvData conv;

	// **** Section 0: Header *************************************************
	conv.nodeCount = convFile->readUint16LE();
	conv.dialogCount = convFile->readUint16LE();
	conv.messageCount = convFile->readUint16LE();
	conv.textLineCount = convFile->readUint16LE();
	conv.unk2 = convFile->readUint16LE();
	conv.importCount = convFile->readUint16LE();
	conv.speakerCount = convFile->readUint16LE();

	//debug("Conv %d has %d nodes, %d dialogs, %d messages, %d text lines, %d unk2, %d imports and %d speakers",
	//		id, conv.nodeCount, conv.dialogCount, conv.messageCount, conv.textLineCount, conv.unk2, conv.importCount, conv.speakerCount);

	for (uint16 i = 0; i < MAX_SPEAKERS; i++) {
		convFile->read(buffer, 16);
		conv.portraits[i] = buffer;
		//debug("Speaker %d, portrait %s", i, conv.portraits[i].c_str());
	}

	for (uint16 i = 0; i < MAX_SPEAKERS; i++) {
		conv.speakerExists[i] = convFile->readUint16LE();
		//debug("Speaker %d exists: %d", i, conv.speakerExists[i]);
	}

	convFile->read(buffer, 14);
	conv.speechFile = Common::String(buffer);
	//debug("Speech file %s", conv.speechFile.c_str());

	uint16 textLength = convFile->readUint16LE();	// Total text length in section 5
	convFile->skip(2);	// TODO: unknown
	uint16 commandLength = convFile->readUint16LE();	// Total length of commands in section 6
	//debug("Node entry commands length: %d", commandLength);

#if 0
	debug("Section 0 unknown bytes");
	byte *tmp0 = new byte[26];
	convFile->read(tmp0, 26);
	Common::hexdump(tmp0, 26);
	delete[] tmp0;
	delete convFile;
#else
	warning("Section 0 unknown bytes");
#endif
	// **** Section 1: Nodes **************************************************
	convFile = convFileUnpacked.getItemStream(1);

	for (uint16 i = 0; i < conv.nodeCount; i++) {
		ConvNode node;
		node.index = convFile->readUint16LE();
		node.dialogCount = convFile->readUint16LE();
		node.unk1 = convFile->readSint16LE();	// TODO
		node.unk2 = convFile->readSint16LE();	// TODO
		node.unk3 = convFile->readSint16LE();	// TODO
		conv.convNodes.push_back(node);
		//debug("Node %d, index %d, entries %d - %d, %d, %d", i, node.index, node.dialogCount, node.unk1, node.unk2, node.unk3);
	}
	delete convFile;

	// **** Section 2: Dialogs ************************************************
	convFile = convFileUnpacked.getItemStream(2);
	assert(convFile->size() == conv.dialogCount * 8);

	for (uint16 i = 0; i < conv.nodeCount; i++) {
		uint16 dialogCount = conv.convNodes[i].dialogCount;

		for (uint16 j = 0; j < dialogCount; j++) {
			ConvDialog dialog;
			dialog.textLineIndex = convFile->readSint16LE();
			dialog.speechIndex = convFile->readSint16LE();
			dialog.nodeOffset = convFile->readUint16LE();
			dialog.nodeSize = convFile->readUint16LE();
			conv.convNodes[i].dialogs.push_back(dialog);
			//debug("Node %d, dialog %d: text line %d, speech index %d, node offset %d, node size %d", j, i, dialog.textLineIndex, dialog.speechIndex, dialog.nodeOffset, dialog.nodeSize);
		}
	}
	delete convFile;

	// **** Section 3: ???? ***************************************************
#if 0
	debug("Section 3");
	convFile = convFileUnpacked.getItemStream(3);
	byte *tmp1 = new byte[convFile->size()];
	convFile->read(tmp1, convFile->size());
	Common::hexdump(tmp1, convFile->size());
	delete[] tmp1;
	delete convFile;
#else
	warning("Section 3 - TODO");
#endif
	// **** Section 4: Text line offsets **************************************
	convFile = convFileUnpacked.getItemStream(4);
	assert(convFile->size() == conv.textLineCount * 2);

	uint16 *textLineOffsets = new uint16[conv.textLineCount];	// deleted below in section 5
	for (uint16 i = 0; i < conv.textLineCount; i++)
		textLineOffsets[i] = convFile->readUint16LE();

	delete convFile;

	// **** Section 5: Text lines *********************************************
	convFile = convFileUnpacked.getItemStream(5);
	assert(convFile->size() == textLength);

	Common::String textLine;
	conv.textLines.resize(conv.textLineCount);
	char textLineBuffer[256];
	uint16 nextOffset;
	for (uint16 i = 0; i < conv.textLineCount; i++) {
		nextOffset = (i != conv.textLineCount - 1) ? textLineOffsets[i + 1] : convFile->size();
		convFile->read(textLineBuffer, nextOffset - textLineOffsets[i]);
		conv.textLines[i] = Common::String(textLineBuffer);	
		//debug("Text line %d: %s", i, conv.textLines[i].c_str());
	}

	delete[] textLineOffsets;
	delete convFile;

	// **** Section 6: Node entry commands ************************************
	convFile = convFileUnpacked.getItemStream(6);
	assert(convFile->size() == commandLength);

	for (uint16 i = 0; i < conv.nodeCount; i++) {
		uint16 dialogCount = conv.convNodes[i].dialogCount;

		for (uint16 j = 0; j < dialogCount; j++) {
			//ConvDialog dialog = conv.convNodes[i].dialogs[j];
			byte command;
			uint16 chk;

			do {
				command = convFile->readByte();
				chk = convFile->readUint16BE();
				if (chk != 0xFF00 && chk != 0x0000) {
					warning("Error while reading conversation node entries - bailing out");
					break;
				}

				switch (command) {
				case cmdNodeEnd:
					//debug("Node end");
					break;
				case cmdDialogEnd:
					//debug("Dialog end");
					break;
				case cmdHide: {
					byte count = convFile->readByte();
					for (byte k = 0; k < count; k++) {
						/*uint16 nodeRef = */convFile->readUint16LE();
						//debug("Hide node %d", nodeRef);
					}

					}
					break;
				case cmdUnhide: {
					byte count = convFile->readByte();
					for (byte k = 0; k < count; k++) {
						/*uint16 nodeRef = */convFile->readUint16LE();
						//debug("Unhide node %d", nodeRef);
					}

					}
					break;
				case cmdMessage:
					//debug("Message");
					convFile->skip(7);	// TODO
					break;
				case cmdGoto: {
					convFile->skip(3);	// unused?
					/*byte nodeRef = */convFile->readByte();
					//debug("Goto %d", nodeRef);
					}
					break;
				case cmdAssign: {
					convFile->skip(3);	// unused?
					/*uint16 value = */convFile->readUint16LE();
					/*uint16 variable = */convFile->readUint16LE();
					//debug("Variable %d = %d", variable, value);
					}
					break;
				default:
					error("Unknown conversation command %d", command);
					break;
				}
			} while (command != cmdNodeEnd && command != cmdDialogEnd);
		}
	}

	delete convFile;
	inFile.close();

	/*
	// DEBUG: Show the very first message, and play the very first speech
	_vm->_audio->setSoundGroup(conv.speechFile);
	uint16 firstText = 0, firstSpeech = 1;

	for (uint16 i = 0; i < conv.convNodes.size(); i++) {
		for (uint16 k = 0; k < conv.convNodes[i].dialogs.size(); k++) {
			if (conv.convNodes[i].dialogs[k].textLineIndex >= 0) {
				firstText = conv.convNodes[i].dialogs[k].textLineIndex;
				firstSpeech = conv.convNodes[i].dialogs[k].speechIndex;
				break;
			}
		}
	}

	_vm->_audio->playSound(firstSpeech - 1);

	TextDialog *dialog = new TextDialog(_vm, FONT_INTERFACE, Common::Point(0, 100), 30);
	dialog->addLine(conv.textLines[firstText]);
	dialog->show();
	delete dialog;
	*/

	warning("TODO GameConversation::get");
}

void GameConversation::run(int id) {
	warning("TODO GameConversation::run");
}

void GameConversation::stop() {
	warning("TODO GameConversation::stop");
}

void GameConversation::exportPointer(int *val) {
	warning("TODO GameConversation::exportPointer");
}

void GameConversation::exportValue(int val) {
	warning("TODO GameConversation::exportValue");
}

void GameConversation::setHeroTrigger(int val) {
	_vm->_game->_trigger = val;	// HACK
	_running = -1;	// HACK
	warning("TODO: GameConversation::setHeroTrigger");
}

void GameConversation::setInterlocutorTrigger(int val) {
	warning("TODO: GameConversation::setInterlocutorTrigger");
}

int* GameConversation::getVariable(int idx) {
	warning("TODO: GameConversation::getVariable");
	return nullptr;
}

void GameConversation::hold() {
	warning("TODO: GameConversation::hold");
}

void GameConversation::release() {
	warning("TODO: GameConversation::release");
}

void GameConversation::reset(int id) {
	warning("TODO: GameConversation::reset");
}

void GameConversation::abortConv() {
	warning("TODO: GameConversation::abort");
}
} // End of namespace MADS