// Copyright (c) 2012, Thomas Goyne <plorkyeran@aegisub.org>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// Aegisub Project http://www.aegisub.org/

#include "parser.h"

#include <boost/phoenix/core.hpp>
#include <boost/phoenix/operator.hpp>
#include <boost/phoenix/fusion.hpp>
#include <boost/phoenix/statement.hpp>

#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/lex_lexertl.hpp>

// dialogue_parser.h needs to be included last because of windows.h memes
#include "libaegisub/color.h"
#include "libaegisub/ass/dialogue_parser.h"

BOOST_FUSION_ADAPT_STRUCT(
	agi::Color,
	(unsigned char, r)
	(unsigned char, g)
	(unsigned char, b)
	(unsigned char, a)
)

namespace {
using namespace boost::spirit;

/// Convert a abgr value in an int or unsigned int to an agi::Color
struct unpack_colors : public boost::static_visitor<agi::Color> {
	template<typename T> struct result { using type = agi::Color; };

	template<class T> agi::Color operator()(T arg) const {
		return boost::apply_visitor(*this, arg);
	}

	agi::Color operator()(int abgr) const { return (*this)((unsigned)abgr); }
	agi::Color operator()(unsigned int abgr) const {
		return agi::Color(abgr & 0xFF, (abgr >> 8) & 0xFF, (abgr >> 16) & 0xFF, (abgr >> 24) & 0xFF);
	}
};

template<typename Iterator>
struct color_grammar : qi::grammar<Iterator, agi::Color()> {
	qi::rule<Iterator, agi::Color()> color;

	qi::rule<Iterator, agi::Color()> css_color;
	qi::rule<Iterator, agi::Color()> ass_color;

	qi::rule<Iterator, unsigned char()> rgb_component;
	qi::rule<Iterator, unsigned char()> rgb_percent;
	qi::rule<Iterator, unsigned char()> hex_byte;
	qi::rule<Iterator, unsigned char()> hex_char;

	qi::rule<Iterator> comma;
	qi::rule<Iterator> blank;

#define HEX_PARSER(type, len) qi::uint_parser<unsigned type, 16, len, len>()

	color_grammar() : color_grammar::base_type(color) {
		color = css_color | ass_color;

		boost::phoenix::function<unpack_colors> unpack;

		// Order is important here; int_ (for SSA) needs to come before the ASS
		// option as decimal numbers of the appropriate length are also valid
		// hex numbers
		// ASS with alpha needs to come before ASS without alpha for the same
		// reason
		ass_color = (
			int_
			| -lit('&') >> -(lit('H') | lit('h')) >> (HEX_PARSER(int, 8) | HEX_PARSER(int, 6)) >> -lit('&')
		)[_val = unpack(_1)] >> blank >> qi::eoi;

		css_color
			= "rgb(" >> blank >> rgb_component >> comma >> rgb_component >> comma >> rgb_component >> blank >> ')'
			| "rgba(" >> blank >> rgb_component >> comma >> rgb_component >> comma >> rgb_component >> comma >> rgb_component >> blank >> ')'
			| '#' >> hex_byte >> hex_byte >> hex_byte
			| '#' >> hex_char >> hex_char >> hex_char
			;

		hex_char = HEX_PARSER(int, 1)[_val = _1 * 16 + _1];
		hex_byte = HEX_PARSER(int, 2);
		rgb_component = qi::uint_parser<unsigned char, 10, 1, 3>();

		comma = *qi::blank >> "," >> *qi::blank;
		blank = *qi::blank;
	}
};

template <typename Lexer>
struct dialogue_tokens final : lex::lexer<Lexer> {
	int paren_depth = 0;

	template<typename KT>
	void init(KT &&kara_templater) {
		using lex::_state;
		using lex::char_;
		using lex::string;
		using namespace boost::phoenix;
		using namespace agi::ass::DialogueTokenType;

		this->self
			= string("\\\\[nNh]", LINE_BREAK)
			| char_('{', OVR_BEGIN)[(ref(paren_depth) = 0, _state = "OVR")]
			| kara_templater
			| string(".", TEXT)
			;

		this->self("OVR")
			= char_('{', ERROR)
			| char_('}', OVR_END)[_state = "INITIAL"]
			| char_('\\', TAG_START)[_state = "TAGSTART"]
			| string("\\s+", WHITESPACE)
			| kara_templater
			| string(".", COMMENT)
			;

		this->self("ARG")
			= char_('{', ERROR)
			| char_('}', OVR_END)[_state = "INITIAL"]
			| char_('(', OPEN_PAREN)[++ref(paren_depth)]
			| char_(')', CLOSE_PAREN)[(--ref(paren_depth), if_(ref(paren_depth) == 0)[_state = "OVR"])]
			| char_('\\', TAG_START)[_state = "TAGSTART"]
			| char_(',', ARG_SEP)
			| string("\\s+", WHITESPACE)
			| string(".", ARG)
			| kara_templater
			;

		this->self("TAGSTART")
			= string("\\s+", WHITESPACE)
			| string("r|fn", TAG_NAME)[_state = "ARG"]
			| char_('\\', TAG_START)
			| char_('}', OVR_END)[_state = "INITIAL"]
			| string("[a-z0-9]", TAG_NAME)[_state = "TAGNAME"]
			| string(".", COMMENT)[_state = "OVR"]
			| kara_templater
			;

		this->self("TAGNAME")
			= string("[a-z]+", TAG_NAME)[_state = "ARG"]
			| char_('(', OPEN_PAREN)[(++ref(paren_depth), _state = "ARG")]
			| char_(')', CLOSE_PAREN)[(--ref(paren_depth), if_(ref(paren_depth) == 0)[_state = "OVR"])]
			| char_('}', OVR_END)[_state = "INITIAL"]
			| char_('\\', TAG_START)[_state = "TAGSTART"]
			| string(".", ARG)[_state = "ARG"]
			| kara_templater
			;
	}

	dialogue_tokens(bool karaoke_templater)  {
		using lex::string;
		using namespace agi::ass::DialogueTokenType;

		if (karaoke_templater)
			init(string("![^!]*!", KARAOKE_TEMPLATE) | string("\\$[A-Za-z_]+", KARAOKE_VARIABLE));
		else
			init(lex::char_('\1'));
	}
};

template<typename Parser, typename T>
bool do_try_parse(std::string_view str, Parser parser, T *out) {
	using namespace boost::spirit::qi;
	T res;
	auto cstr = str.data(), end = str.data() + str.size();

	bool parsed = parse(cstr, end, parser, res);
	if (parsed && cstr == end) {
		*out = res;
		return true;
	}

	return false;
}

}

namespace agi {
namespace parser {
	bool parse(Color &dst, std::string_view str) {
		if (str.empty()) return false;
		auto begin = str.data(), end = str.data() + str.size();
		bool parsed = parse(begin, end, color_grammar<const char *>(), dst);
		return parsed && begin == end;
	}
}

namespace ass {
	std::vector<DialogueToken> TokenizeDialogueBody(std::string_view str, bool karaoke_templater) {
		static const dialogue_tokens<lex::lexertl::actor_lexer<>> kt(true);
		static const dialogue_tokens<lex::lexertl::actor_lexer<>> not_kt(false);
		auto const& tokenizer = karaoke_templater ? kt : not_kt;

		char const *first = str.data();
		char const *last = first + str.size();
		std::vector<DialogueToken> data;
		auto it = tokenizer.begin(first, last), end = tokenizer.end();

		for (; it != end && token_is_valid(*it); ++it) {
			int id = it->id();
			ptrdiff_t len = it->value().end() - it->value().begin();
			assert(len > 0);
			if (data.empty() || data.back().type != id)
				data.push_back(DialogueToken{id, static_cast<size_t>(len)});
			else
				data.back().length += len;
		}

		return data;
	}
}

namespace util {
	// from util.h
	bool try_parse(std::string_view str, double *out) {
		return do_try_parse(str, boost::spirit::qi::double_, out);
	}

	bool try_parse(std::string_view str, int *out) {
		return do_try_parse(str, boost::spirit::qi::int_, out);
	}
}
}
