#ifndef FORTH_KATA_TOKEN_H
#define FORTH_KATA_TOKEN_H

#include <cstdint>
#include <iosfwd>
#include <stdexcept>
#include <string>
#include <gsl/assert>

namespace forth {

namespace token {

/**
 * Structure representing a number token (An integer literal).
 */
struct Number final
{
    std::int16_t value; ///< The numeric value of the token.

    bool operator==(Number const &rhs) const noexcept;

    bool operator!=(Number const &rhs) const noexcept;
};

/**
 * Overloaded output operator for Number tokens.
 * @param os The output stream.
 * @param number The number.
 * @return The output stream.
 */
std::ostream &operator<<(std::ostream &os, Number const &number);

/**
 * Structure representing a keyword token (Special words with fixed meaning).
 */
struct Keyword final
{
    std::string value;

    bool operator==(Keyword const &rhs) const noexcept;

    bool operator!=(Keyword const &rhs) const noexcept;
};

/**
 * Overloaded output operator for Keyword tokens.
 * @param os The output stream.
 * @param keyword The keyword.
 * @return The output stream.
 */
std::ostream &operator<<(std::ostream &os, Keyword const &keyword);

/**
 * Structure representing a word token (Words that are neither keywords nor numbers).
 */
struct Word final
{
    std::string value;

    bool operator==(Word const &rhs) const noexcept;

    bool operator!=(Word const &rhs) const noexcept;
};

/**
 * Overloaded output operator for Word tokens.
 * @param os The output stream.
 * @param word The word.
 * @return The output stream.
 */
std::ostream &operator<<(std::ostream &os, Word const &word);

} // namespace token

struct WrongTokenException : std::runtime_error
{
    using std::runtime_error::runtime_error;
    using std::runtime_error::operator=;
};

class Token final
{
    enum class Type
    {
        Number,
        Keyword,
        Word,
    };

public:
    Token(token::Number value) noexcept;
    Token(token::Keyword value) noexcept;
    Token(token::Word value) noexcept;

    Token(Token const &src);
    Token(Token &&src) noexcept;

    Token &operator=(Token const &src);
    Token &operator=(Token &&src) noexcept;

    ~Token() noexcept;

    bool IsNumber() const noexcept;
    bool IsKeyword() const noexcept;
    bool IsWord() const noexcept;

    token::Number const &GetNumber() const;
    token::Keyword const &GetKeyword() const;
    token::Word const &GetWord() const;

    bool operator==(Token const &rhs) const noexcept;
    bool operator!=(Token const &rhs) const noexcept;

private:
    void destroy() noexcept;

    template<typename TType>
    void destroy(TType *p)
    {
        Expects(p != nullptr);
        p->~TType();
    }

    Type type;

    union
    {
        token::Number number;
        token::Keyword keyword;
        token::Word word;
    };
};

/**
 * Overloaded output operator for Tokens.
 * @param os The output stream.
 * @param token The token.
 * @return The output stream.
 */
std::ostream &operator<<(std::ostream &os, Token const &token);

} // namespace forth

#endif // !FORTH_KATA_TOKEN_H