#ifndef SCANNER_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#define SCANNER_H_62B23520_7C8E_11DE_8A39_0800200C9A66

#if defined(_MSC_VER) || \
	(defined(__GNUC__) && (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || (__GNUC__ >= 4))  // GCC supports "pragma once" correctly since 3.4
#pragma once
#endif

#include <cstddef>
#include <ios>
#include <queue>
#include <stack>
#include <string>

#include "ptr_vector.h"
#include "stream.h"
#include "token.h"
#include "yaml-cpp/mark.h"
#include "yaml-cpp/scanneropts.h"

namespace YAML {
class Node;
class RegEx;

/**
 * A scanner transforms a stream of characters into a stream of tokens.
 */
class Scanner {
public:
	explicit Scanner(std::istream &in, const ScannerOpts &opts);
	~Scanner();

	/** Returns true if there are no more tokens to be read. */
	bool empty();

	/** Removes the next token in the queue. */
	void pop();

	/** Returns, but does not remove, the next token in the queue. */
	Token &peek();

	/** Returns the current mark in the input stream. */
	Mark mark() const;

private:
	struct IndentMarker {
		enum INDENT_TYPE { MAP, SEQ, NONE };
		enum STATUS { VALID, INVALID, UNKNOWN };
		IndentMarker(int column_, INDENT_TYPE type_) : column(column_), type(type_), status(VALID), pStartToken(nullptr) {}

		int column;
		INDENT_TYPE type;
		STATUS status;
		Token *pStartToken;
	};

	enum FLOW_MARKER { FLOW_MAP, FLOW_SEQ };

private:
	// scanning

	/**
	 * Scans until there's a valid token at the front of the queue, or the queue
	 * is empty. The state can be checked by {@link #empty}, and the next token
	 * retrieved by {@link #peek}.
	 */
	void EnsureTokensInQueue();

	/**
	 * The main scanning function; this method branches out to scan whatever the
	 * next token should be.
	 */
	void ScanNextToken();

	/** Eats the input stream until it reaches the next token-like thing. */
	void ScanToNextToken();

	/** Sets the initial conditions for starting a stream. */
	void StartStream();

	/** Closes out the stream, finish up, etc. */
	void EndStream();

	Token *PushToken(Token::TYPE type);

	bool InFlowContext() const { return !m_flows.empty(); }
	bool InBlockContext() const { return m_flows.empty(); }
	std::size_t GetFlowLevel() const { return m_flows.size(); }

	Token::TYPE GetStartTokenFor(IndentMarker::INDENT_TYPE type) const;

	/**
	 * Pushes an indentation onto the stack, and enqueues the proper token
	 * (sequence start or mapping start).
	 *
	 * @return the indent marker it generates (if any).
	 */
	IndentMarker *PushIndentTo(int column, IndentMarker::INDENT_TYPE type);

	/**
	 * Pops indentations off the stack until it reaches the current indentation
	 * level, and enqueues the proper token each time. Then pops all invalid
	 * indentations off.
	 */
	void PopIndentToHere();

	/**
	 * Pops all indentations (except for the base empty one) off the stack, and
	 * enqueues the proper token each time.
	 */
	void PopAllIndents();

	/** Pops a single indent, pushing the proper token. */
	void PopIndent();
	int GetTopIndent() const;

	// checking input
	bool CanInsertPotentialSimpleKey() const;
	bool ExistsActiveSimpleKey() const;
	void InsertPotentialSimpleKey();
	void InvalidateSimpleKey();
	bool VerifySimpleKey();
	void PopAllSimpleKeys();

	/**
	 * Throws a ParserException with the current token location (if available),
	 * and does not parse any more tokens.
	 */
	void ThrowParserException(const std::string &msg) const;

	bool IsWhitespaceToBeEaten(char ch);

	/**
	 * Returns the appropriate regex to check if the next token is a value token.
	 */
	const RegEx &GetValueRegex() const;

	struct SimpleKey {
		SimpleKey(const Mark &mark_, std::size_t flowLevel_);

		void Validate();
		void Invalidate();

		Mark mark;
		std::size_t flowLevel;
		IndentMarker *pIndent;
		Token *pMapStart, *pKey;
	};

	// and the tokens
	void ScanDirective();
	void ScanDocStart();
	void ScanDocEnd();
	void ScanBlockSeqStart();
	void ScanBlockMapSTart();
	void ScanBlockEnd();
	void ScanBlockEntry();
	void ScanFlowStart();
	void ScanFlowEnd();
	void ScanFlowEntry();
	void ScanKey();
	void ScanValue();
	void ScanAnchorOrAlias();
	void ScanTag();
	void ScanPlainScalar();
	void ScanQuotedScalar();
	void ScanBlockScalar();

private:
	// the stream
	Stream INPUT;
	const ScannerOpts m_opts;

	// the output (tokens)
	std::queue<Token> m_tokens;

	// state info
	bool m_startedStream, m_endedStream;
	bool m_simpleKeyAllowed;
	bool m_canBeJSONFlow;
	std::stack<SimpleKey> m_simpleKeys;
	std::stack<IndentMarker *> m_indents;
	ptr_vector<IndentMarker> m_indentRefs;	// for "garbage collection"
	std::stack<FLOW_MARKER> m_flows;
};
}  // namespace YAML

#endif	// SCANNER_H_62B23520_7C8E_11DE_8A39_0800200C9A66
