//
// PageReader.cpp
//
// Copyright (c) 2008, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier:	BSL-1.0
//


#include "PageReader.h"
#include "Page.h"
#include "Poco/FileStream.h"
#include "Poco/CountingStream.h"
#include "Poco/Path.h"
#include "Poco/Exception.h"
#include "Poco/Ascii.h"


const std::string PageReader::MARKUP_BEGIN("\tresponseStream << \"");
const std::string PageReader::MARKUP_END("\";\n");
const std::string PageReader::EXPR_BEGIN("\tresponseStream << (");
const std::string PageReader::EXPR_END(");\n");
const std::string PageReader::ESC_EXPR_BEGIN("\t_escapeStream << (");
const std::string PageReader::ESC_EXPR_END(");\n");


PageReader::PageReader(Page& page, const std::string& path):
	_page(page),
	_pParent(nullptr),
	_path(path),
	_line(0),
	_emitLineDirectives(false)
{
	_attrs.reserve(4096);
}


PageReader::PageReader(const PageReader& parent, const std::string& path):
	_page(parent._page),
	_pParent(&parent),
	_path(path),
	_line(0),
	_emitLineDirectives(false)
{
	_attrs.reserve(4096);
}


PageReader::~PageReader()
{
}


void PageReader::emitLineDirectives(bool flag)
{
	_emitLineDirectives = flag;
}


void PageReader::parse(std::istream& pageStream)
{
	ParsingState state = STATE_MARKUP;

	_page.handler() << MARKUP_BEGIN;

	Poco::CountingInputStream countingPageStream(pageStream);
	std::string token;
	nextToken(countingPageStream, token);
	while (!token.empty())
	{
		_line = countingPageStream.getCurrentLineNumber();
		if (token == "<%")
		{
			if (state == STATE_MARKUP)
			{
				_page.handler() << MARKUP_END;
				generateLineDirective(_page.handler());
				state = STATE_BLOCK;
			}
			else _page.handler() << token;
		}
		else if (token == "<%%")
		{
			if (state == STATE_MARKUP)
			{
				_page.handler() << MARKUP_END;
				generateLineDirective(_page.preHandler());
				state = STATE_PREHANDLER;
			}
			else _page.handler() << token;
		}
		else if (token == "<%!")
		{
			if (state == STATE_MARKUP)
			{
				_page.handler() << MARKUP_END;
				generateLineDirective(_page.implDecls());
				state = STATE_IMPLDECL;
			}
			else _page.handler() << token;
		}
		else if (token == "<%!!")
		{
			if (state == STATE_MARKUP)
			{
				_page.handler() << MARKUP_END;
				generateLineDirective(_page.headerDecls());
				state = STATE_HDRDECL;
			}
			else _page.handler() << token;
		}
		else if (token == "<%--")
		{
			if (state == STATE_MARKUP)
			{
				_page.handler() << MARKUP_END;
				state = STATE_COMMENT;
			}
			else _page.handler() << token;
		}
		else if (token == "<%@")
		{
			if (state == STATE_MARKUP)
			{
				_page.handler() << MARKUP_END;
				state = STATE_ATTR;
				_attrs.clear();
			}
			else _page.handler() << token;
		}
		else if (token == "<%=")
		{
			if (state == STATE_MARKUP)
			{
				_page.handler() << MARKUP_END;
				generateLineDirective(_page.handler());
				if (escape())
				{
					_page.handler() << ESC_EXPR_BEGIN;
					state = STATE_ESC_EXPR;
				}
				else
				{
					_page.handler() << EXPR_BEGIN;
					state = STATE_EXPR;
				}
			}
			else _page.handler() << token;
		}
		else if (token == "<%-")
		{
			if (state == STATE_MARKUP)
			{
				_page.handler() << MARKUP_END;
				generateLineDirective(_page.handler());
				_page.handler() << EXPR_BEGIN;
				state = STATE_EXPR;
			}
			else _page.handler() << token;
		}
		else if (token == "%>")
		{
			if (state == STATE_EXPR)
			{
				_page.handler() << EXPR_END;
				_page.handler() << MARKUP_BEGIN;
				state = STATE_MARKUP;
			}
			else if (state == STATE_ESC_EXPR)
			{
				_page.handler() << ESC_EXPR_END;
				_page.handler() << MARKUP_BEGIN;
				state = STATE_MARKUP;
			}
			else if (state == STATE_ATTR)
			{
				parseAttributes();
				_attrs.clear();
				_page.handler() << MARKUP_BEGIN;
				state = STATE_MARKUP;
			}
			else if (state != STATE_MARKUP)
			{
				_page.handler() << MARKUP_BEGIN;
				state = STATE_MARKUP;
			}
			else _page.handler() << token;
		}
		else
		{
			switch (state)
			{
			case STATE_MARKUP:
				if (token == "\n")
				{
					_page.handler() << "\\n";
					_page.handler() << MARKUP_END;
					_page.handler() << MARKUP_BEGIN;
				}
				else if (token == "\t")
				{
					_page.handler() << "\\t";
				}
				else if (token == "\"")
				{
					_page.handler() << "\\\"";
				}
				else if (token == "\\")
				{
					_page.handler() << "\\\\";
				}
				else if (token != "\r")
				{
					_page.handler() << token;
				}
				break;
			case STATE_IMPLDECL:
				_page.implDecls() << token;
				break;
			case STATE_HDRDECL:
				_page.headerDecls() << token;
				break;
			case STATE_PREHANDLER:
				_page.preHandler() << token;
				break;
			case STATE_BLOCK:
				_page.handler() << token;
				break;
			case STATE_EXPR:
			case STATE_ESC_EXPR:
				_page.handler() << token;
				break;
			case STATE_COMMENT:
				break;
			case STATE_ATTR:
				_attrs += token;
				break;
			}
		}
		nextToken(countingPageStream, token);
	}

	if (state == STATE_MARKUP)
	{
		_page.handler() << MARKUP_END;
	}
	else throw Poco::SyntaxException("unclosed meta or code block", where());
}


void PageReader::parseAttributes()
{
	static const int eof = std::char_traits<char>::eof();

	std::string basename;
	std::istringstream istr(_attrs);
	int ch = istr.get();
	while (ch != eof && Poco::Ascii::isSpace(ch)) ch = istr.get();
	while (ch != eof && Poco::Ascii::isAlphaNumeric(ch)) { basename += (char) ch; ch = istr.get(); }
	while (ch != eof && Poco::Ascii::isSpace(ch)) ch = istr.get();
	while (ch != eof)
	{
		std::string name(basename + ".");
		std::string value;
		while (ch != eof && Poco::Ascii::isAlphaNumeric(ch)) { name += (char) ch; ch = istr.get(); }
		while (ch != eof && Poco::Ascii::isSpace(ch)) ch = istr.get();
		if (ch != '=') throw Poco::SyntaxException("bad attribute syntax: '=' expected", where());
		ch = istr.get();
		while (ch != eof && Poco::Ascii::isSpace(ch)) ch = istr.get();
		if (ch == '"')
		{
			ch = istr.get();
			while (ch != eof && ch != '"') { value += (char) ch; ch = istr.get(); }
			if (ch != '"') throw Poco::SyntaxException("bad attribute syntax: '\"' expected", where());
		}
		else if (ch == '\'')
		{
			ch = istr.get();
			while (ch != eof && ch != '\'') { value += (char) ch; ch = istr.get(); }
			if (ch != '\'') throw Poco::SyntaxException("bad attribute syntax: ''' expected", where());
		}
		else throw Poco::SyntaxException("bad attribute syntax: '\"' or ''' expected", where());
		ch = istr.get();
		handleAttribute(name, value);
		while (ch != eof && Poco::Ascii::isSpace(ch)) ch = istr.get();
	}
}


void PageReader::nextToken(std::istream& istr, std::string& token)
{
	token.clear();
	int ch = istr.get();
	if (ch != -1)
	{
		if (ch == '<' && istr.peek() == '%')
		{
			token += "<%";
			istr.get();
			ch = istr.peek();
			switch (ch)
			{
			case '%':
			case '@':
			case '=':
				ch = istr.get();
				token += (char) ch;
				break;
			case '!':
				ch = istr.get();
				token += (char) ch;
				if (istr.peek() == '!')
				{
					ch = istr.get();
					token += (char) ch;
				}
				break;
			case '-':
				ch = istr.get();
				token += (char) ch;
				if (istr.peek() == '-')
				{
					ch = istr.get();
					token += (char) ch;
				}
				break;
			}
		}
		else if (ch == '%' && istr.peek() == '>')
		{
			token += "%>";
			istr.get();
		}
		else token += (char) ch;
	}
}


void PageReader::handleAttribute(const std::string& name, const std::string& value)
{
	if (name == "include.page" || name == "include.file")
	{
		include(value);
	}
	else if (name == "header.include")
	{
		_page.headerDecls() << "#include \"" << value << "\"\n";
	}
	else if (name == "header.sinclude")
	{
		_page.headerDecls() << "#include <" << value << ">\n";
	}
	else if (name == "impl.include")
	{
		_page.implDecls() << "#include \"" << value << "\"\n";
	}
	else if (name == "impl.sinclude")
	{
		_page.implDecls() << "#include <" << value << ">\n";
	}
	else
	{
		_page.set(name, value);
	}
}


void PageReader::include(const std::string& path)
{
	Poco::Path currentPath(_path);
	Poco::Path includePath(path);
	currentPath.resolve(includePath);

	_page.handler() << "\t// begin include " << currentPath.toString() << "\n";

	Poco::FileInputStream includeStream(currentPath.toString());
	PageReader includeReader(*this, currentPath.toString());
	includeReader.emitLineDirectives(_emitLineDirectives);
	includeReader.parse(includeStream);

	_page.handler() << "\t// end include " << currentPath.toString() << "\n";
}


std::string PageReader::where() const
{
	std::stringstream result;
	result << "in file '" << _path << "', line " << _line;
	const PageReader* pParent = _pParent;
	while (pParent)
	{
		result << "\n\tincluded from file '"<<  pParent->_path << "', line " << pParent->_line;
		pParent = pParent->_pParent;
	}
	return result.str();
}


void PageReader::generateLineDirective(std::ostream& ostr)
{
	if (_emitLineDirectives)
	{
		Poco::Path p(_path);
		p.makeAbsolute();
		std::string absPath = p.toString();
		ostr << "#line " << _line << " \"";
		for (std::string::const_iterator it = absPath.begin(); it != absPath.end(); ++it)
		{
			if (*it == '\\')
				ostr << "\\\\";
			else
				ostr << *it;
		}
		ostr << "\"\n";
	}
}


bool PageReader::escape() const
{
	return _page.getBool("page.escape", false);
}
