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

#ifndef PARALLACTION_PARSER_H
#define PARALLACTION_PARSER_H

#include "common/stream.h"
#include "common/stack.h"
#include "parallaction/objects.h"

namespace Parallaction {

#define MAX_TOKEN_LEN	50
extern int  _numTokens;
extern char _tokens[][MAX_TOKEN_LEN];

class Script {

	Common::ReadStream *_input;
	bool	_disposeSource;
	uint	_line;				// for debug messages

	void clearTokens();
	char *parseNextToken(char *s, char *tok, uint16 count, const char *brk);
	char *readLineIntern(char *buf, size_t bufSize);

public:
	Script(Common::ReadStream *, bool _disposeSource = false);
	~Script();

	char *readLine(char *buf, size_t bufSize);
	uint16 readLineToken(bool errorOnEOF = false);

	void skip(const char* endToken);

	uint	getLine() { return _line; }
};


typedef Common::Functor0<void> Opcode;
typedef Common::Array<const Opcode *>	OpcodeSet;



class Parser {

public:
	Parser() { reset(); }
	~Parser() { reset(); }

	uint	_lookup;

	Common::Stack<OpcodeSet *>	_opcodes;
	Common::Stack<Table *>		_statements;

	OpcodeSet	*_currentOpcodes;
	Table		*_currentStatements;

	void	reset();
	void	pushTables(OpcodeSet *opcodes, Table* statements);
	void	popTables();
	void	parseStatement();

};

#define DECLARE_UNQUALIFIED_ZONE_PARSER(sig) void locZoneParse_##sig()
#define DECLARE_UNQUALIFIED_ANIM_PARSER(sig) void locAnimParse_##sig()
#define DECLARE_UNQUALIFIED_COMMAND_PARSER(sig) void cmdParse_##sig()
#define DECLARE_UNQUALIFIED_LOCATION_PARSER(sig) void locParse_##sig()
#define DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(sig) void instParse_##sig()

#define MAX_FORWARDS	50

class Parallaction_ns;
class Parallaction_br;


class LocationParser_ns {

protected:
	Parallaction_ns*	_vm;
	Script	*_script;
	Parser	*_parser;

	Table		*_zoneTypeNames;
	Table		*_zoneFlagNames;
	uint		_zoneProg;

	// location parser
	OpcodeSet	_locationParsers;
	OpcodeSet	_locationZoneParsers;
	OpcodeSet	_locationAnimParsers;
	OpcodeSet	_commandParsers;
	Table		*_commandsNames;
	Table		*_locationStmt;
	Table		*_locationZoneStmt;
	Table		*_locationAnimStmt;

	struct ParserContext {
		bool		end;

		const char	*filename;
		ZonePtr		z;
		AnimationPtr	a;
		int			nextToken;
		CommandList *list;
		bool		endcommands;
		CommandPtr	cmd;
	} ctxt;

	void warning_unexpected();

	DECLARE_UNQUALIFIED_LOCATION_PARSER(endlocation);
	DECLARE_UNQUALIFIED_LOCATION_PARSER(location);
	DECLARE_UNQUALIFIED_LOCATION_PARSER(disk);
	DECLARE_UNQUALIFIED_LOCATION_PARSER(nodes);
	DECLARE_UNQUALIFIED_LOCATION_PARSER(zone);
	DECLARE_UNQUALIFIED_LOCATION_PARSER(animation);
	DECLARE_UNQUALIFIED_LOCATION_PARSER(localflags);
	DECLARE_UNQUALIFIED_LOCATION_PARSER(commands);
	DECLARE_UNQUALIFIED_LOCATION_PARSER(acommands);
	DECLARE_UNQUALIFIED_LOCATION_PARSER(flags);
	DECLARE_UNQUALIFIED_LOCATION_PARSER(comment);
	DECLARE_UNQUALIFIED_LOCATION_PARSER(endcomment);
	DECLARE_UNQUALIFIED_LOCATION_PARSER(sound);
	DECLARE_UNQUALIFIED_LOCATION_PARSER(music);
	DECLARE_UNQUALIFIED_ZONE_PARSER(limits);
	DECLARE_UNQUALIFIED_ZONE_PARSER(moveto);
	DECLARE_UNQUALIFIED_ZONE_PARSER(type);
	DECLARE_UNQUALIFIED_ZONE_PARSER(commands);
	DECLARE_UNQUALIFIED_ZONE_PARSER(label);
	DECLARE_UNQUALIFIED_ZONE_PARSER(flags);
	DECLARE_UNQUALIFIED_ZONE_PARSER(endzone);
	DECLARE_UNQUALIFIED_ZONE_PARSER(null);
	DECLARE_UNQUALIFIED_ANIM_PARSER(script);
	DECLARE_UNQUALIFIED_ANIM_PARSER(commands);
	DECLARE_UNQUALIFIED_ANIM_PARSER(type);
	DECLARE_UNQUALIFIED_ANIM_PARSER(label);
	DECLARE_UNQUALIFIED_ANIM_PARSER(flags);
	DECLARE_UNQUALIFIED_ANIM_PARSER(file);
	DECLARE_UNQUALIFIED_ANIM_PARSER(position);
	DECLARE_UNQUALIFIED_ANIM_PARSER(moveto);
	DECLARE_UNQUALIFIED_ANIM_PARSER(endanimation);
	DECLARE_UNQUALIFIED_COMMAND_PARSER(flags);
	DECLARE_UNQUALIFIED_COMMAND_PARSER(animation);
	DECLARE_UNQUALIFIED_COMMAND_PARSER(zone);
	DECLARE_UNQUALIFIED_COMMAND_PARSER(location);
	DECLARE_UNQUALIFIED_COMMAND_PARSER(invObject);
	DECLARE_UNQUALIFIED_COMMAND_PARSER(call);
	DECLARE_UNQUALIFIED_COMMAND_PARSER(simple);
	DECLARE_UNQUALIFIED_COMMAND_PARSER(move);
	DECLARE_UNQUALIFIED_COMMAND_PARSER(endcommands);
public:
	virtual void parseGetData(ZonePtr z);
	virtual void parseExamineData(ZonePtr z);
	virtual void parseDoorData(ZonePtr z);
	virtual void parseMergeData(ZonePtr z);
	virtual void parseHearData(ZonePtr z);
	virtual void parseSpeakData(ZonePtr z);
	virtual void parseNoneData(ZonePtr z);
protected:
	Common::String	parseComment();
	Common::String	parseDialogueString();
	Dialogue	*parseDialogue();
	virtual Answer *parseAnswer();
	void		parseAnswerFlags(Answer *answer);
	void		parseAnswerBody(Answer *answer);
	void		parseQuestion(Question *q);

	uint32		buildZoneType(const char *t0, const char* t1);

	void		parseZone(ZoneList &list, char *name);
	virtual void parseZoneTypeBlock(ZonePtr z);
	void		parsePointList(PointList &list);
	void		parseAnimation(AnimationList &list, char *name);
	void		parseCommands(CommandList&);
	void		parseCommandFlags();
	void		parseCommandFlag(CommandPtr cmd, const char *flag, Table *table);
	void		createCommand(uint id);
	void		addCommand();

	void clearSet(OpcodeSet &opcodes) {
		for (Common::Array<const Opcode *>::iterator i = opcodes.begin(); i != opcodes.end(); ++i)
			delete *i;
		opcodes.clear();
	}

public:
	LocationParser_ns(Parallaction_ns *vm) : _vm(vm), _commandsNames(0), _locationStmt(0),
		_locationZoneStmt(0), _locationAnimStmt(0) {
		_script = 0;
		_parser = 0;
		_zoneTypeNames = 0;
		_zoneFlagNames = 0;
		_zoneProg = 0;
	}

	virtual void init();

	virtual ~LocationParser_ns() {
		delete _parser;
		delete _commandsNames;
		delete _locationStmt;
		delete _locationZoneStmt;
		delete _locationAnimStmt;
		delete _zoneTypeNames;
		delete _zoneFlagNames;

		clearSet(_commandParsers);
		clearSet(_locationAnimParsers);
		clearSet(_locationZoneParsers);
		clearSet(_locationParsers);
	}

	void parse(Script *script);

};


struct LocationParserOutput_br {
	BackgroundInfo	*_info;

	Common::String _characterName;
	Common::String _backgroundName;
	Common::String _maskName;
	Common::String _pathName;
};

class LocationParser_br : public LocationParser_ns {

protected:
	Parallaction_br*	_vm;
	Table		*_audioCommandsNames;

	LocationParserOutput_br *_out;

	DECLARE_UNQUALIFIED_LOCATION_PARSER(location);
	DECLARE_UNQUALIFIED_LOCATION_PARSER(zone);
	DECLARE_UNQUALIFIED_LOCATION_PARSER(animation);
	DECLARE_UNQUALIFIED_LOCATION_PARSER(localflags);
	DECLARE_UNQUALIFIED_LOCATION_PARSER(flags);
	DECLARE_UNQUALIFIED_LOCATION_PARSER(comment);
	DECLARE_UNQUALIFIED_LOCATION_PARSER(endcomment);
	DECLARE_UNQUALIFIED_LOCATION_PARSER(sound);
	DECLARE_UNQUALIFIED_LOCATION_PARSER(music);
	DECLARE_UNQUALIFIED_LOCATION_PARSER(redundant);
	DECLARE_UNQUALIFIED_LOCATION_PARSER(ifchar);
	DECLARE_UNQUALIFIED_LOCATION_PARSER(character);
	DECLARE_UNQUALIFIED_LOCATION_PARSER(mask);
	DECLARE_UNQUALIFIED_LOCATION_PARSER(path);
	DECLARE_UNQUALIFIED_LOCATION_PARSER(escape);
	DECLARE_UNQUALIFIED_LOCATION_PARSER(zeta);
	DECLARE_UNQUALIFIED_LOCATION_PARSER(null);
	DECLARE_UNQUALIFIED_COMMAND_PARSER(ifchar);
	DECLARE_UNQUALIFIED_COMMAND_PARSER(endif);
	DECLARE_UNQUALIFIED_COMMAND_PARSER(location);
	DECLARE_UNQUALIFIED_COMMAND_PARSER(toggle);
	DECLARE_UNQUALIFIED_COMMAND_PARSER(string);
	DECLARE_UNQUALIFIED_COMMAND_PARSER(math);
	DECLARE_UNQUALIFIED_COMMAND_PARSER(test);
	DECLARE_UNQUALIFIED_COMMAND_PARSER(music);
	DECLARE_UNQUALIFIED_COMMAND_PARSER(zeta);
	DECLARE_UNQUALIFIED_COMMAND_PARSER(swap);
	DECLARE_UNQUALIFIED_COMMAND_PARSER(give);
	DECLARE_UNQUALIFIED_COMMAND_PARSER(text);
	DECLARE_UNQUALIFIED_COMMAND_PARSER(unary);
	DECLARE_UNQUALIFIED_ZONE_PARSER(limits);
	DECLARE_UNQUALIFIED_ZONE_PARSER(moveto);
	DECLARE_UNQUALIFIED_ZONE_PARSER(type);
	DECLARE_UNQUALIFIED_ANIM_PARSER(file);
	DECLARE_UNQUALIFIED_ANIM_PARSER(position);
	DECLARE_UNQUALIFIED_ANIM_PARSER(moveto);
	DECLARE_UNQUALIFIED_ANIM_PARSER(endanimation);

	virtual void	parseZoneTypeBlock(ZonePtr z);
public:
	virtual void	parsePathData(ZonePtr z);
	virtual void	parseGetData(ZonePtr z);
	virtual void	parseDoorData(ZonePtr z);
	virtual void	parseHearData(ZonePtr z);
	virtual void	parseNoneData(ZonePtr z);
protected:
	void	parseAnswerCounter(Answer *answer);
	virtual Answer *parseAnswer();

public:
	LocationParser_br(Parallaction_br *vm) : LocationParser_ns((Parallaction_ns*)vm), _vm(vm),
		_audioCommandsNames(0), _out(0) {
	}

	virtual void init();

	virtual ~LocationParser_br() {
		delete _audioCommandsNames;
	}

	void parse(Script *script, LocationParserOutput_br *out);

};



class ProgramParser_ns {

protected:
	Parallaction_ns *_vm;
	Parser	*_parser;

	Script	*_script;
	ProgramPtr	_program;

	// program parser
	OpcodeSet	_instructionParsers;
	Table		*_instructionNames;

	uint32		_currentInstruction; // index of the instruction being parsed

	struct ParserContext {
		bool		end;
		AnimationPtr	a;
		InstructionPtr inst;
		LocalVariable *locals;
	} ctxt;

	DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(defLocal);
	DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(animation);
	DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(loop);
	DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(x);
	DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(y);
	DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(z);
	DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(f);
	DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(inc);
	DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(set);
	DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(move);
	DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(put);
	DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(call);
	DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(sound);
	DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(null);
	DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(endscript);

	void		parseInstruction();
	void		parseLValue(ScriptVar &var, const char *str);
	virtual void	parseRValue(ScriptVar &var, const char *str);

	void clearSet(OpcodeSet &opcodes) {
		for (Common::Array<const Opcode *>::iterator i = opcodes.begin(); i != opcodes.end(); ++i)
			delete *i;
		opcodes.clear();
	}

public:
	ProgramParser_ns(Parallaction_ns *vm) : _vm(vm), _parser(0), _instructionNames(0), _script(0), _currentInstruction(0) {
	}

	virtual void init();

	virtual ~ProgramParser_ns() {
		delete _parser;
		delete _instructionNames;

		clearSet(_instructionParsers);
	}

	virtual void parse(Script *script, ProgramPtr program);

};


class ProgramParser_br : public ProgramParser_ns {

protected:
	Parallaction_br *_vm;

	DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(zone);
	DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(color);
	DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(mask);
	DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(print);
	DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(text);
	DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(if_op);
	DECLARE_UNQUALIFIED_INSTRUCTION_PARSER(endif);

	int32 _openIfStatement;
	void beginIfStatement();
	void endIfStatement();

	virtual void parseRValue(ScriptVar &var, const char *str);

public:
	ProgramParser_br(Parallaction_br *vm) : ProgramParser_ns((Parallaction_ns*)vm), _vm(vm) {
	}

	virtual void init();
	virtual void parse(Script *script, ProgramPtr program);
};

} // End of namespace Parallaction

#endif