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

#ifndef GLK_COMPREHEND_GAME_DATA_H
#define GLK_COMPREHEND_GAME_DATA_H

#include "glk/comprehend/file_buf.h"
#include "common/serializer.h"
#include "common/str-array.h"

namespace Glk {
namespace Comprehend {

#define MAX_FLAGS 256
#define MAX_VARIABLES 128
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))

class ComprehendGame;

enum {
	DIRECTION_NORTH,
	DIRECTION_SOUTH,
	DIRECTION_EAST,
	DIRECTION_WEST,
	DIRECTION_UP,
	DIRECTION_DOWN,
	DIRECTION_IN,
	DIRECTION_OUT,
	NR_DIRECTIONS
};


enum ScriptOpcode {
	OPCODE_UNKNOWN,
	OPCODE_HAVE_OBJECT,
	OPCODE_OR,
	OPCODE_IN_ROOM,
	OPCODE_VAR_EQ1,
	OPCODE_VAR_EQ2,
	OPCODE_VAR_GT1,
	OPCODE_VAR_GT2,
	OPCODE_VAR_GTE1,
	OPCODE_VAR_GTE2,
	OPCODE_CURRENT_IS_OBJECT,
	OPCODE_OBJECT_PRESENT,
	OPCODE_ELSE,
	OPCODE_OBJECT_IN_ROOM,
	OPCODE_CURRENT_OBJECT_NOT_VALID,
	OPCODE_INVENTORY_FULL,
	OPCODE_INVENTORY_FULL_X,
	OPCODE_TEST_FLAG,
	OPCODE_CURRENT_OBJECT_IN_ROOM,
	OPCODE_HAVE_CURRENT_OBJECT,
	OPCODE_OBJECT_IS_NOT_NOWHERE,
	OPCODE_CURRENT_OBJECT_PRESENT,
	OPCODE_TEST_ROOM_FLAG,
	OPCODE_NOT_HAVE_OBJECT,
	OPCODE_NOT_IN_ROOM,
	OPCODE_CURRENT_OBJECT_NOT_IN_ROOM,
	OPCODE_OBJECT_NOT_IN_ROOM,
	OPCODE_TEST_NOT_FLAG,
	OPCODE_NOT_HAVE_CURRENT_OBJECT,
	OPCODE_OBJECT_IS_NOWHERE,
	OPCODE_OBJECT_NOT_PRESENT,
	OPCODE_CURRENT_OBJECT_IS_NOWHERE,
	OPCODE_CURRENT_OBJECT_NOT_PRESENT,
	OPCODE_CURRENT_OBJECT_NOT_TAKEABLE,
	OPCODE_TEST_NOT_ROOM_FLAG,
	OPCODE_INVENTORY,
	OPCODE_TAKE_OBJECT,
	OPCODE_MOVE_OBJECT_TO_ROOM,
	OPCODE_SAVE_ACTION,
	OPCODE_CLEAR_LINE,
	OPCODE_MOVE_TO_ROOM,
	OPCODE_VAR_ADD,
	OPCODE_SET_ROOM_DESCRIPTION,
	OPCODE_MOVE_OBJECT_TO_CURRENT_ROOM,
	OPCODE_VAR_SUB,
	OPCODE_SET_OBJECT_DESCRIPTION,
	OPCODE_SET_OBJECT_LONG_DESCRIPTION,
	OPCODE_MOVE_DEFAULT,
	OPCODE_PRINT,
	OPCODE_REMOVE_OBJECT,
	OPCODE_SET_FLAG,
	OPCODE_CALL_FUNC,
	OPCODE_CALL_FUNC2,
	OPCODE_TURN_TICK,
	OPCODE_CLEAR_FLAG,
	OPCODE_INVENTORY_ROOM,
	OPCODE_TAKE_CURRENT_OBJECT,
	OPCODE_SPECIAL,
	OPCODE_DROP_OBJECT,
	OPCODE_DROP_CURRENT_OBJECT,
	OPCODE_SET_ROOM_GRAPHIC,
	OPCODE_SET_OBJECT_GRAPHIC,
	OPCODE_REMOVE_CURRENT_OBJECT,
	OPCODE_MOVE_DIR,
	OPCODE_VAR_INC,
	OPCODE_VAR_DEC,
	OPCODE_MOVE_CURRENT_OBJECT_TO_ROOM,
	OPCODE_DESCRIBE_CURRENT_OBJECT,
	OPCODE_SET_STRING_REPLACEMENT1,
	OPCODE_SET_STRING_REPLACEMENT2,
	OPCODE_SET_STRING_REPLACEMENT3,
	OPCODE_SET_CURRENT_NOUN_STRING_REPLACEMENT,
	OPCODE_DRAW_ROOM,
	OPCODE_DRAW_OBJECT,
	OPCODE_WAIT_KEY,
	OPCODE_TEST_FALSE,
	OPCODE_CAN_TAKE,
	OPCODE_TOO_HEAVY,
	OPCODE_OBJECT_TAKEABLE,
	OPCODE_OBJECT_CAN_TAKE,
	OPCODE_CLEAR_INVISIBLE,
	OPCODE_SET_INVISIBLE,
	OPCODE_CLEAR_CAN_TAKE,
	OPCODE_SET_CAN_TAKE,
	OPCODE_CLEAR_FLAG40,
	OPCODE_SET_FLAG40,
	OPCODE_RANDOM_MSG,
	OPCODE_SET_WORD,
	OPCODE_CLEAR_WORD
};

/* Game state update flags */
#define UPDATE_GRAPHICS (1 << 0) /* Implies UPDATE_GRAPHICS_ITEMS */
#define UPDATE_GRAPHICS_ITEMS (1 << 1)
#define UPDATE_ROOM_DESC (1 << 2)
#define UPDATE_ITEM_LIST (1 << 3)
#define UPDATE_ALL (~0U)

/* Action types */
enum {
	ACTION_VERB_VERB_NOUN_NOUN,
	ACTION_VERB_NOUN_JOIN_NOUN,
	ACTION_VERB_JOIN_NOUN,
	ACTION_VERB_DIR_NOUN,
	ACTION_VERB_NOUN_NOUN,
	ACTION_VERB_NOUN,
	ACTION_VERB_OPT_NOUN
};

/* Standard strings (main string table) */
#define STRING_CANT_GO 0
#define STRING_DONT_UNDERSTAND 1
#define STRING_YOU_SEE 2
#define STRING_INVENTORY 3
#define STRING_INVENTORY_EMPTY 4
#define STRING_BEFORE_CONTINUE 5
#define STRING_SAVE_GAME 6
#define STRING_RESTORE_GAME 7

/* Special variables */
#define VAR_INVENTORY_WEIGHT 0
#define VAR_INVENTORY_LIMIT 1
#define VAR_TURN_COUNT 2

/* Special rooms */
#define ROOM_INVENTORY 0x00
#define ROOM_CONTAINER 0xfe
#define ROOM_NOWHERE 0xff

/* Item flags */
enum ItemFlag {
	ITEMF_WEIGHT_MASK = 0x7,
	ITEMF_CAN_TAKE    = 1 << 3,
	ITEMF_UNKNOWN     = 1 << 6,
	ITEMF_INVISIBLE   = 1 << 7
};

/* Word types */
#define WORD_TYPE_VERB 0x01
#define WORD_TYPE_JOIN 0x02
#define WORD_TYPE_FEMALE 0x10
#define WORD_TYPE_MALE 0x20
#define WORD_TYPE_NOUN 0x40
#define WORD_TYPE_NOUN_PLURAL 0x80
#define WORD_TYPE_NOUN_MASK (WORD_TYPE_FEMALE | WORD_TYPE_MALE | \
							 WORD_TYPE_NOUN | WORD_TYPE_NOUN_PLURAL)

struct FunctionState {
	bool _testResult;
	bool _elseResult;
	uint _orCount;
	bool _and;
	bool _inCommand;
	bool _executed;
	bool _notComparison;

	FunctionState() {
		clear();
	}

	void clear();
};

struct Room {
	uint8 _direction[NR_DIRECTIONS];
	uint8 _flags;
	uint8 _graphic;
	uint16 _stringDesc;

	Room() {
		clear();
	}

	void clear();
};

struct Item {
	uint16 _stringDesc;
	uint16 _longString; /* Only used by version 2 */
	uint8 _room;
	uint8 _flags;
	uint8 _word;
	uint8 _graphic;

	Item() {
		clear();
	}

	void clear();

	void synchronize(Common::Serializer &s);
};

struct WordIndex {
	uint8 _index;
	uint8 _type;

	WordIndex() {
		clear();
	}

	void clear() {
		_index = _type = 0;
	}

	bool operator==(WordIndex &src) {
		return _index == src._index && _type == src._type;
	}

	bool operator()() const {
		return _index != 0;
	}
};

struct Word : public WordIndex {
	char _word[7];

	Word() : WordIndex() {
		Word::clear();
	}

	void clear();

	void load(FileBuffer *fb);

	Word &operator=(const WordIndex &src);
};

struct WordMap {
	/* <word[0]>, <word[1]> == <word[2]> */
	WordIndex _word[3];
	uint8 _flags;

	WordMap() {
		clear();
	}

	void clear();
};

struct Action {
	size_t _nr_words;
	uint8 _words[4];
	uint16 _function;

	Action() {
		clear();
	}

	void clear();
};

struct Instruction {
	uint8 _opcode;
	size_t _nr_operands;
	uint8 _operand[3];
	bool _isCommand;

	Instruction() {
		clear();
	}

	Instruction(byte opcode, byte op1 = 0, byte op2 = 0, byte op3 = 0);

	void clear();
};

typedef Common::Array<Instruction> Function;

typedef Common::StringArray StringTable;

struct StringFile {
	Common::String _filename;
	uint32 _baseOffset;
	uint32 _endOffset;

	StringFile() : _baseOffset(0), _endOffset(0) {
	}
	StringFile(const char *fname, uint32 baseOfs = 0, uint32 endO = 0) :
		_filename(fname), _baseOffset(baseOfs), _endOffset(endO) {
	}
};

struct GameHeader {
	uint16 magic;

	uint16 room_desc_table;
	uint16 room_direction_table[NR_DIRECTIONS];
	uint16 room_flags_table;
	uint16 room_graphics_table;

	size_t nr_items;
	uint16 addr_item_locations;
	uint16 addr_item_flags;
	uint16 addr_item_word;
	uint16 addr_item_strings;
	uint16 addr_item_graphics;

	uint16 addr_dictionary;
	uint16 addr_word_map;
	uint16 addr_word_map_target;

	uint16 addr_strings;
	uint16 addr_strings_end;

	uint16 addr_actions[7];

	uint16 addr_vm; // FIXME - functions

	GameHeader() {
		clear();
	}

	void clear();
};

typedef Common::Array<Action> ActionTable;

class GameData {
private:
	uint16 _magicWord;
protected:
	Common::String _gameDataFile;
	Common::Array<StringFile> _stringFiles;
	Common::StringArray _locationGraphicFiles;
	Common::StringArray _itemGraphicFiles;
	Common::String _titleGraphicFile;
	uint _colorTable;

public:
	GameHeader _header;

	uint _comprehendVersion;

	Common::Array<Room> _rooms;
	uint8 _currentRoom;
	uint8 _startRoom;
	uint8 _itemCount;
	uint8 _totalInventoryWeight;

	Common::Array<Item> _items;
	Common::Array<Word> _words;

	StringTable _strings;
	StringTable _strings2;

	bool _flags[MAX_FLAGS];
	uint16 _variables[MAX_VARIABLES];

	uint8 _currentReplaceWord;
	uint8 _wordFlags;
	uint _updateFlags;

	Common::Array<WordMap> _wordMaps;
	Common::Array<ActionTable> _actions;
	Common::Array<Function> _functions;
	Common::StringArray _replaceWords;

private:
	size_t opcode_nr_operands(uint8 opcode) const {
		// Number of operands is encoded in the low 2 bits
		return opcode & 0x3;
	}

	bool opcode_is_command(uint8 opcode) const {
		/* If the MSB is set the instruction is a command */
		return opcode & 0x80;
	}

	void load_extra_string_files();
	void load_extra_string_file(const StringFile &stringFile);
	void parse_header_le16(FileBuffer *fb, uint16 *val);
	uint8 parse_vm_instruction(FileBuffer *fb, Instruction *instr);
	void parse_function(FileBuffer *fb, Function *func);
	void parse_vm(FileBuffer *fb);
	void parse_action_tables(FileBuffer *fb);
	void parse_dictionary(FileBuffer *fb);
	void parse_word_map(FileBuffer *fb);
	void parse_items(FileBuffer *fb);
	void parse_rooms(FileBuffer *fb);
	uint64 string_get_chunk(uint8 *string);
	char decode_string_elem(uint8 c, bool capital, bool special);

	void parse_string_table(FileBuffer *fb, uint start_addr,
		uint32 end_addr, StringTable *table);
	void parse_variables(FileBuffer *fb);
	void parse_flags(FileBuffer *fb);
	void parse_replace_words(FileBuffer *fb);

	void loadGameData();

protected:
	/**
	 * Game strings are stored using 5-bit characters. By default a character
	 * value maps to the lower-case letter table. If a character has the value 0x1e
	 * then the next character is upper-case. An upper-case space is used to
	 * specify that the character should be replaced at runtime (like a '%s'
	 * specifier). If a character has the value 0x1f then the next character is
	 * taken from the symbols table.
	 */
	Common::String parseString(FileBuffer *fb);

	/**
	 * The main game data file header has the offsets for where each bit of
	 * game data is. The offsets have a magic constant value added to them.
	 */
	virtual void parse_header(FileBuffer *fb);

public:
	GameData() {
		clearGame();
	}
	virtual ~GameData() {
		clearGame();
	}

	void clearGame();
	void loadGame();
};

} // namespace Comprehend
} // namespace Glk

#endif
