/* 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 TWP_YACK_H
#define TWP_YACK_H

#include "common/array.h"
#include "common/str.h"
#include "common/stream.h"
#include "common/debug.h"
#include "twp/util.h"

namespace Twp {

enum class YackTokenId {
	None,
	NewLine,
	Identifier,
	WaitWhile,
	Int,
	Float,
	Whitespace,
	Colon,
	Condition,
	String,
	Assign,
	Comment,
	Goto,
	Code,
	Dollar,
	End
};

// enumeration that lists all errors that can occur
enum YackError {
	ERR_NONE,              // no error
	ERR_INVALIDYackToken,  // invalid YackToken
	ERR_STRINGEXPECTED,    // string expected
	ERR_COLONEXPECTED,     // `:` expected
	ERR_COMMAEXPECTED,     // `,` expected
	ERR_BRACKETRIEXPECTED, // `]` expected
	ERR_CURLYRIEXPECTED,   // `}` expected
	ERR_QUOTEEXPECTED,     // `"` or `'` expected
	ERR_EOC_EXPECTED,      // `*/` expected
	ERR_EOFEXPECTED,       // EOF expected
	ERR_EXPREXPECTED       // expr expected
};

class YackVisitor;
class YackNode {
public:
	virtual ~YackNode() {}

	virtual void accept(YackVisitor &v) = 0;
};

// Represents a condition
class YCond : public YackNode {
public:
	virtual ~YCond() {}

public:
	int _line;
};

class YCodeCond : public YCond {
public:
	YCodeCond(int line) { _line = line; }
	virtual ~YCodeCond() {}
	virtual void accept(YackVisitor &v) override;

public:
	Common::String _code;
};

class YOnce : public YCond {
public:
	YOnce(int line) { _line = line; }
	virtual ~YOnce() {}
	virtual void accept(YackVisitor &v) override;
};

class YShowOnce : public YCond {
public:
	YShowOnce(int line) { _line = line; }
	virtual ~YShowOnce() {}
	virtual void accept(YackVisitor &v) override;
};

class YOnceEver : public YCond {
public:
	YOnceEver(int line) { _line = line; }
	virtual ~YOnceEver() {}
	virtual void accept(YackVisitor &v) override;
};

class YTempOnce : public YCond {
public:
	YTempOnce(int line) { _line = line; }
	virtual ~YTempOnce() {}
	virtual void accept(YackVisitor &v) override;
};

// Expression
class YExp : public YackNode {
public:
	YExp() {}
	virtual ~YExp() {}
};

class YGoto : public YExp {
public:
	YGoto(int line) { _line = line; }
	virtual ~YGoto() {}
	virtual void accept(YackVisitor &v) override;

public:
	Common::String _name;
	int _line = 0;
};

class YCodeExp : public YExp {
public:
	virtual ~YCodeExp() {}
	virtual void accept(YackVisitor &v) override;

public:
	Common::String _code;
};

class YChoice : public YExp {
public:
	virtual ~YChoice() {}
	virtual void accept(YackVisitor &v) override;

public:
	int _number = 0;
	Common::String _text;
	Common::SharedPtr<YGoto> _goto;
};

class YSay : public YExp {
public:
	virtual ~YSay() {}
	virtual void accept(YackVisitor &v) override;

public:
	Common::String _actor;
	Common::String _text;
};

class YPause : public YExp {
public:
	virtual ~YPause() {}
	virtual void accept(YackVisitor &v) override;

public:
	int _time = 0;
};

class YParrot : public YExp {
public:
	virtual ~YParrot() {}
	virtual void accept(YackVisitor &v) override;

public:
	bool _active = true;
};

class YDialog : public YExp {
public:
	virtual ~YDialog() {}
	virtual void accept(YackVisitor &v) override;

public:
	Common::String _actor;
};

class YOverride : public YExp {
public:
	virtual ~YOverride() {}
	virtual void accept(YackVisitor &v) override;

public:
	Common::String _node;
};

class YShutup : public YExp {
public:
	virtual ~YShutup() {}
	virtual void accept(YackVisitor &v) override;
};

class YAllowObjects : public YExp {
public:
	virtual ~YAllowObjects() {}
	virtual void accept(YackVisitor &v) override;

public:
	bool _active = true;
};

class YLimit : public YExp {
public:
	virtual ~YLimit() {}
	virtual void accept(YackVisitor &v) override;

public:
	int _max = 8;
};

class YWaitWhile : public YExp {
public:
	virtual ~YWaitWhile() {}
	virtual void accept(YackVisitor &v) override;

public:
	Common::String _cond;
};

class YWaitFor : public YExp {
public:
	virtual ~YWaitFor() {}
	virtual void accept(YackVisitor &v) override;

public:
	Common::String _actor;
};

//

class YStatement : public YackNode {
public:
	virtual ~YStatement() override {}

	virtual void accept(YackVisitor &v) override;

public:
	Common::SharedPtr<YExp> _exp;
	Common::Array<Common::SharedPtr<YCond> > _conds;
};

class YLabel : public YackNode {
public:
	YLabel(int line);
	virtual ~YLabel() override;
	virtual void accept(YackVisitor &v) override;

public:
	Common::String _name;
	Common::Array<Common::SharedPtr<YStatement> > _stmts;
	int _line = 0;
};

class YCompilationUnit : public YackNode {
public:
	virtual ~YCompilationUnit() override;
	virtual void accept(YackVisitor &v) override;

public:
	Common::Array<Common::SharedPtr<YLabel> > _labels;
};

class YackVisitor {
public:
	virtual ~YackVisitor() {}

	virtual void visit(const YCompilationUnit &node) { defaultVisit(node); }
	virtual void visit(const YStatement &node) { defaultVisit(node); }
	virtual void visit(const YWaitFor &node) { defaultVisit(node); }
	virtual void visit(const YWaitWhile &node) { defaultVisit(node); }
	virtual void visit(const YLimit &node) { defaultVisit(node); }
	virtual void visit(const YAllowObjects &node) { defaultVisit(node); }
	virtual void visit(const YShutup &node) { defaultVisit(node); }
	virtual void visit(const YOverride &node) { defaultVisit(node); }
	virtual void visit(const YDialog &node) { defaultVisit(node); }
	virtual void visit(const YParrot &node) { defaultVisit(node); }
	virtual void visit(const YPause &node) { defaultVisit(node); }
	virtual void visit(const YSay &node) { defaultVisit(node); }
	virtual void visit(const YChoice &node) { defaultVisit(node); }
	virtual void visit(const YCodeExp &node) { defaultVisit(node); }
	virtual void visit(const YGoto &node) { defaultVisit(node); }
	virtual void visit(const YTempOnce &node) { defaultVisit(node); }
	virtual void visit(const YOnceEver &node) { defaultVisit(node); }
	virtual void visit(const YShowOnce &node) { defaultVisit(node); }
	virtual void visit(const YOnce &node) { defaultVisit(node); }
	virtual void visit(const YCodeCond &node) { defaultVisit(node); }
	virtual void visit(const YLabel &node) { defaultVisit(node); }

	virtual void defaultVisit(const YackNode &node) {}
};

struct YackToken {
	YackTokenId id = YackTokenId::None;
	int64 start = 0;
	int64 end = 0;
	int line = 0;

	Common::String toString() const;
};

class YackTokenReader {
public:
	class Iterator {
	public:
		using value_type = YackToken;
		using difference_type = ptrdiff_t;
		using pointer = YackToken *;
		using reference = YackToken &;

	private:
		YackTokenReader *_reader = nullptr;
		int64 _pos = 0;
		YackToken _token;

	public:
		Iterator() {}
		Iterator(YackTokenReader &reader, int64 pos);
		Iterator(const Iterator &it);
		Iterator &operator++();
		Iterator operator++(int);

		Iterator &operator=(const Iterator &rhs) {
			_pos = rhs._pos;
			_token = rhs._token;
			_reader = rhs._reader;
			return *this;
		}
		bool operator==(const Iterator &rhs) const { return _pos == rhs._pos; }
		bool operator!=(const Iterator &rhs) const { return _pos != rhs._pos; }
		YackToken &operator*();
		const YackToken &operator*() const;
		YackToken *operator->();
	};

	using iterator = Iterator;

public:
	void open(Common::SeekableReadStream *stream);

	iterator begin();
	iterator end();
	Common::String readText(const YackToken &YackToken);

private:
	bool readYackToken(YackToken &YackToken);
	Common::String readText(int64 pos, int64 size);
	YackTokenId readYackTokenId();
	YackTokenId readCode();
	YackTokenId readCondition();
	YackTokenId readDollar();
	YackTokenId readNumber();
	YackTokenId readComment();
	YackTokenId readString();
	YackTokenId readIdentifier(char c);
	byte peek();
	void ignore(int64 n = 1, int delim = EOF);

private:
	Common::SeekableReadStream *_stream = nullptr;
	int _line = 0;

	friend class YackParser;
};

class YackParser {
public:
	YackParser() {}
	YCompilationUnit *parse(Common::SeekableReadStream *stream);

private:
	bool match(const std::initializer_list<YackTokenId> &ids);
	Common::SharedPtr<YLabel> parseLabel();
	Common::SharedPtr<YStatement> parseStatement();
	Common::SharedPtr<YCond> parseCondition();
	Common::SharedPtr<YExp> parseExpression();
	Common::SharedPtr<YSay> parseSayExpression();
	Common::SharedPtr<YExp> parseWaitWhileExpression();
	Common::SharedPtr<YExp> parseInstructionExpression();
	Common::SharedPtr<YGoto> parseGotoExpression();
	Common::SharedPtr<YCodeExp> parseCodeExpression();
	Common::SharedPtr<YChoice> parseChoiceExpression();

private:
	YackTokenReader _reader;
	YackTokenReader::iterator _it;
};

class YackDump : public YackVisitor {
public:
	virtual ~YackDump() {}

	virtual void visit(const YCompilationUnit &node) {
		debug("CompilationUnit:");
		for (const auto &cond : node._labels) {
			cond->accept(*this);
		}
	}
	virtual void visit(const YLabel &node) {
		debug("Label: %s [%d]", node._name.c_str(), node._line);
		for (const auto &stmt : node._stmts) {
			stmt->accept(*this);
		}
	}
	virtual void visit(const YStatement &node) {
		debug("Statement:");
		for (const auto &cond : node._conds) {
			cond->accept(*this);
		}
		node._exp->accept(*this);
	}
	virtual void visit(const YWaitFor &node) { debug("WaifFor %s", node._actor.c_str()); }
	virtual void visit(const YWaitWhile &node) { debug("WaitWhile %s", node._cond.c_str()); }
	virtual void visit(const YLimit &node) { debug("Limit: %d", node._max); }
	virtual void visit(const YAllowObjects &node) { debug("AllowObjects"); }
	virtual void visit(const YShutup &node) { debug("Shutup"); }
	virtual void visit(const YOverride &node) { defaultVisit(node); }
	virtual void visit(const YDialog &node) { debug("Dialog: %s", node._actor.c_str()); }
	virtual void visit(const YParrot &node) { debug("Parrot: %s", node._active ? "YES" : "NO"); }
	virtual void visit(const YPause &node) { debug("Pause: %d", node._time); }
	virtual void visit(const YSay &node) { debug("Say: actor: %s, text = %s", node._actor.c_str(), node._text.c_str()); }
	virtual void visit(const YChoice &node) { debug("Choice %d: %s -> %s", node._number, node._text.c_str(), node._goto->_name.c_str()); }
	virtual void visit(const YCodeExp &node) { debug("Code: %s", node._code.c_str()); }
	virtual void visit(const YGoto &node) { debug("Goto: %s", node._name.c_str()); }
	virtual void visit(const YTempOnce &node) { debug("TempOnce"); }
	virtual void visit(const YOnceEver &node) { debug("OnceEver"); }
	virtual void visit(const YShowOnce &node) { debug("ShowOnce"); }
	virtual void visit(const YOnce &node) { debug("Once"); }
	virtual void visit(const YCodeCond &node) { debug("Cond: %s", node._code.c_str()); }

	virtual void defaultVisit(const YackNode &node) {}
};

} // namespace Twp

#endif
