#ifndef SYMBOL_HPP
#define SYMBOL_HPP

#include <string>

struct interpreter_context;

class symbol_impl {
    friend class symbol;

    virtual symbol_impl* clone() const = 0;
    virtual symbol_impl* evaluate(const interpreter_context&) const = 0;
    virtual std::string serialize(const interpreter_context&) const = 0;
    virtual const std::string& type() const = 0;
    virtual bool is_litteral() const = 0;
    virtual std::pair<size_t,size_t> priority(const interpreter_context&) const = 0;

public :
    virtual ~symbol_impl() {}
};

template<typename...>
struct type_serializer;

class symbol {
    symbol_impl* impl;

public :
    symbol();
    explicit symbol(symbol_impl* s);
    symbol(const symbol& s) = delete;
    symbol& operator = (const symbol& s) = delete;
    symbol(symbol&& s);
    symbol& operator = (symbol&& s);
    ~symbol();

    symbol_impl* release();
    symbol clone() const;
    symbol evaluate(const interpreter_context& context) const;
    std::string serialize(const interpreter_context& context) const;
    const std::string& type() const;
    bool is_litteral() const;
    std::pair<size_t,size_t> priority(const interpreter_context& context) const;

    template<typename T>
    T& as() {
        return *static_cast<T*>(impl);
    }

    template<typename T>
    const T& as() const {
        return *static_cast<T*>(impl);
    }

    explicit operator bool() const;
    bool operator ! () const;

    template<typename ... Args>
    static std::string types() {
        return type_serializer<Args...>::str();
    }

    static const std::string type_name;
};

template<typename T, typename ... Args>
struct type_serializer<T, Args...> {
    static std::string str() {
        return T::type_name + ", " + type_serializer<Args...>::str();
    }
};

template<>
struct type_serializer<> {
    static std::string str() {
        return "";
    }
};

class no_type : public symbol_impl {
public :
    no_type* clone() const override;
    no_type* evaluate(const interpreter_context&) const override;
    std::string serialize(const interpreter_context&) const override;
    const std::string& type() const override;
    bool is_litteral() const override;
    std::pair<size_t,size_t> priority(const interpreter_context&) const override;

    static const std::string type_name;
};

class any_type : public symbol_impl {
public :
    any_type* clone() const override;
    any_type* evaluate(const interpreter_context&) const override;
    std::string serialize(const interpreter_context&) const override;
    const std::string& type() const override;
    bool is_litteral() const override;
    std::pair<size_t,size_t> priority(const interpreter_context&) const override;

    static const std::string type_name;
};

#endif
