#include <boost/regex.hpp>
#include <t3devlib/t3devlib.h>

namespace t3devlib { namespace gen {

/** Implementation of perl-like regular expressions to be used in a decoder
 * generated by T3DevKit.
 *
 * This class handles the regex compilation and execution. The result of
 * group matching is stored internally.
 *
 * It is designed to be used seamlessly in codets for T3DevKit, thus:
 *  - it can interact with t3devlib data (Buffer, Variable, DecodeError)
 *  - it reports errors using T3DevKit's exceptions
 *  - it returns the length of the matches in bits
 *
 *
 * The regular expression is compiled at the construction of the object. Then
 * it can be used multiple times to search the regex in the buffer (with
 * Match() or AssertMatch()).
 *
 * After a regex search, it is possible to inspect the result at to take
 * actions with other member functions (GetMatchedLength(), MoveAt(), ...)
 *
 * \warning	Apart from moving the cursor, the buffer MUST NOT be modified
 *		between the call of Match() or AssertMatch() and the use of
 *		inspection/action member functions (GetMatchedLength(),
 *		MoveAt() ...)
 */
class Regex {
public:
	typedef std::string::const_iterator	iterator;

	/** Constructor
	 *
	 * This constructor compiles the regular expression given as a
	 * parameter. It is based on the format of perl regular expressions.
	 *
	 * \warning	it is assumed that regex is a static const string
	 */
	Regex(const char* regex)
	 : mSource (regex), mRegex (regex, boost::regex_constants::perl | boost::regex_constants::no_mod_m)
	{}

	/** Try to match the regular expression in the buffer.
	 *
	 * The regular expression is searched in the buffer between the
	 * position of its cursor and the first end marker set (thus between
	 * the positions given by Buffer::GetPosition() and
	 * Buffer::GetEndMarker()) 
	 *
	 * \param buffer	buffer on which the regex shall be searched
	 *
	 * \return	true in case of success
	 */
	bool Match (Buffer& buffer) {
		mStart = iterator (reinterpret_cast<const char*> (buffer.GetValueBin()) + (buffer.GetPosition() / 8));
		mStop = iterator (reinterpret_cast<const char*> (buffer.GetValueBin()) + (buffer.GetEndMarker() / 8));

		int result = boost::regex_search (mStart, mStop, mResults, mRegex);

		return result;
	}

	/** Match the regular expression in the buffer or throw an exeption
	 * if not successful.
	 *
	 * \param buffer	buffer on which the regex shall be searched
	 * \param v		reference of the value being decoded; it is
	 *			used to allow locating the error in case an
	 *			exception is thrown
	 */
	void AssertMatch (Buffer& buffer, Variable* v) throw (DecodeError) {
		if (!Match (buffer)) {
			Error (v, buffer);
		}
	}

	/** Get the length of a group match
	 *
	 * \param	id	index of the '(...)' group to analyse (or 0
	 *			to refer to the whole regular expression)
	 *
	 * \return		the length of the group in bits
	 */
	int GetMatchedLength(int id = 0) {
		return mResults[id].length()*8;
	}

	/** Get the content of a group match
	 *
	 * \param	id	index of the '(...)' group to analyse (or 0
	 *			to refer to the whole regular expression)
	 *
	 * \return		the string matched by the group
	 */
	std::string GetMatchedString(int id = 0) {
		return std::string (mResults[id].first, mResults[id].second);
	}

	/** Move the cursor of the buffer past a group match
	 *
	 * \param	id	index of the '(...)' group to analyse (or 0
	 *			to refer to the whole regular expression)
	 */
	void MovePast (Buffer& buffer, int id = 0)
	{
		buffer.SetPosition ((&*mResults[id].second - reinterpret_cast<const char*> (buffer.GetValueBin())) * 8);
	}
	
	/** Move the cursor of the buffer at the beginning of group match
	 *
	 * \param	id	index of the '(...)' group to analyse (or 0
	 *			to refer to the whole regular expression)
	 */
	void MoveAt (Buffer& buffer, int id = 0)
	{
		buffer.SetPosition ((&*mResults[id].first - reinterpret_cast<const char*> (buffer.GetValueBin())) * 8);
	}

	/** Throw a decoding error
	 *
	 * This function will throw an exception telling that the codec
	 * could not match the regular expression. The error message will
	 * also contains details about the regular expression and the
	 * content of the buffer at the position where the exception could
	 * not be matched.
	 *
	 * \param	v	reference to the value being decoded
	 * 			(used to locate the position of the error)
	 * \param	buffer	buffer on which the regex could no be matched
	 */
	void Error (Variable* v, Buffer& buffer) throw (DecodeError) {

		std::string message ("cannot match regex /");
		message += mSource;
		message += "/ in '";
		if ((mStop - mStart) > 40) {
			message.append (&*mStart, 40);
			message += "...";
		} else {
			message.append (mStart, mStop);
		}
		message += "'\n";
		throw DecodeError (v, message);
	}

private:
	/** source of the regular expression (should be a static string) */
	const char*	mSource;

	/** boost regex object used to implement the regular expression */
	boost::regex	mRegex;

	/** iterators delimiting the positions in the buffer where the regex
	 * is applied (between GetPosition() and GetEndMarker)
	 */
	iterator	mStart, mStop;

	/** boost match result used to store the results of the regex search */
	boost::match_results<iterator>	mResults;
};


}} //namespaces
