#ifndef CPPTL_JSON_READER_H_INCLUDED
# define CPPTL_JSON_READER_H_INCLUDED

# include "features.h"
# include "value.h"
# include <deque>
# include <stack>
# include <string>
# include <iostream>

namespace Json {

	/** \brief Unserialize a <a HREF="http://www.json.org">JSON</a> document into a Value.
	 *
	 */
	class JSON_API Reader {
	public:
		typedef char Char;
		typedef const Char *Location;

		/** \brief Constructs a Reader allowing all features
		 * for parsing.
		 */
		Reader();

		/** \brief Constructs a Reader allowing the specified feature set
		 * for parsing.
		 */
		Reader(const Features &features);

		/** \brief Read a Value from a <a HREF="http://www.json.org">JSON</a> document.
		 * \param document UTF-8 encoded string containing the document to read.
		 * \param root [out] Contains the root value of the document if it was
		 *             successfully parsed.
		 * \param collectComments \c true to collect comment and allow writing them back during
		 *                        serialization, \c false to discard comments.
		 *                        This parameter is ignored if Features::allowComments_
		 *                        is \c false.
		 * \return \c true if the document was successfully parsed, \c false if an error occurred.
		 */
		bool parse(const std::string &document,
		           Value &root,
		           bool collectComments = true);

		/** \brief Read a Value from a <a HREF="http://www.json.org">JSON</a> document.
		 * \param document UTF-8 encoded string containing the document to read.
		 * \param root [out] Contains the root value of the document if it was
		 *             successfully parsed.
		 * \param collectComments \c true to collect comment and allow writing them back during
		 *                        serialization, \c false to discard comments.
		 *                        This parameter is ignored if Features::allowComments_
		 *                        is \c false.
		 * \return \c true if the document was successfully parsed, \c false if an error occurred.
		 */
		bool parse(const char *beginDoc, const char *endDoc,
		           Value &root,
		           bool collectComments = true);

		/// \brief Parse from input stream.
		/// \see Json::operator>>(std::istream&, Json::Value&).
		bool parse(std::istream &is,
		           Value &root,
		           bool collectComments = true);

		/** \brief Returns a user friendly string that list errors in the parsed document.
		 * \return Formatted error message with the list of errors with their location in
		 *         the parsed document. An empty string is returned if no error occurred
		 *         during parsing.
		 */
		std::string getFormatedErrorMessages() const;

	private:
		enum TokenType {
			tokenEndOfStream = 0,
			tokenObjectBegin,
			tokenObjectEnd,
			tokenArrayBegin,
			tokenArrayEnd,
			tokenString,
			tokenNumber,
			tokenTrue,
			tokenFalse,
			tokenNull,
			tokenArraySeparator,
			tokenMemberSeparator,
			tokenComment,
			tokenError
		};

		class Token {
		public:
			TokenType type_;
			Location start_;
			Location end_;
		};

		class ErrorInfo {
		public:
			Token token_;
			std::string message_;
			Location extra_;
		};

		typedef std::deque <ErrorInfo> Errors;

		bool expectToken(TokenType type, Token &token, const char *message);

		bool readToken(Token &token);

		void skipSpaces();

		bool match(Location pattern,
		           int patternLength);

		bool readComment();

		bool readCStyleComment();

		bool readCppStyleComment();

		bool readString();

		void readNumber();

		bool readValue();

		bool readObject(Token &token);

		bool readArray(Token &token);

		bool decodeNumber(Token &token);

		bool decodeString(Token &token);

		bool decodeString(Token &token, std::string &decoded);

		bool decodeDouble(Token &token);

		bool decodeUnicodeCodePoint(Token &token,
		                            Location &current,
		                            Location end,
		                            unsigned int &unicode);

		bool decodeUnicodeEscapeSequence(Token &token,
		                                 Location &current,
		                                 Location end,
		                                 unsigned int &unicode);

		bool addError(const std::string &message,
		              Token &token,
		              Location extra = 0);

		bool recoverFromError(TokenType skipUntilToken);

		bool addErrorAndRecover(const std::string &message,
		                        Token &token,
		                        TokenType skipUntilToken);

		void skipUntilSpace();

		Value &currentValue();

		Char getNextChar();

		void getLocationLineAndColumn(Location location,
		                              int &line,
		                              int &column) const;

		std::string getLocationLineAndColumn(Location location) const;

		void addComment(Location begin,
		                Location end,
		                CommentPlacement placement);

		void skipCommentTokens(Token &token);

		typedef std::stack<Value *> Nodes;
		Nodes nodes_;
		Errors errors_;
		std::string document_;
		Location begin_;
		Location end_;
		Location current_;
		Location lastValueEnd_;
		Value *lastValue_;
		std::string commentsBefore_;
		Features features_;
		bool collectComments_;
	};

	/** \brief Read from 'sin' into 'root'.

	 Always keep comments from the input JSON.

	 This can be used to read a file into a particular sub-object.
	 For example:
	 \code
	 Json::Value root;
	 cin >> root["dir"]["file"];
	 cout << root;
	 \endcode
	 Result:
	 \verbatim
	 {
	 "dir": {
		 "file": {
		 // The input stream JSON would be nested here.
		 }
	 }
	 }
	 \endverbatim
	 \throw std::exception on parse error.
	 \see Json::operator<<()
	*/
	std::istream &operator>>(std::istream &, Value &);

} // namespace Json

#endif // CPPTL_JSON_READER_H_INCLUDED
