#ifndef SYMBOL_HPP
#define SYMBOL_HPP

#include <string>

struct interpreter_context;

class symbol_impl {
public :
    virtual ~symbol_impl() {}

    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;
};

template<typename...>
struct type_serializer;

class symbol {
    symbol_impl* impl;

public :
    symbol() : impl(nullptr) {}
    explicit symbol(symbol_impl* s) : impl(s) {}
    symbol(const symbol& s) = delete;
    symbol& operator = (const symbol& s) = delete;
    symbol(symbol&& s) : impl(s.release()) {}
    symbol& operator = (symbol&& s) {
        impl = s.release();
        return *this;
    }
    ~symbol() {
        delete impl;
    }

    symbol_impl* release() {
        symbol_impl* tmp = impl;
        impl = nullptr;
        return tmp;
    }

    symbol clone() const {
        if (impl)
            return symbol(impl->clone());
        else
            return symbol();
    }

    symbol evaluate(const interpreter_context& context) const {
        if (impl)
            return symbol(impl->evaluate(context));
        else
            return symbol();
    }

    std::string serialize(const interpreter_context& context) const {
        if (impl)
            return impl->serialize(context);
        else
            return std::string();
    }

    const std::string& type() const {
        if (impl)
            return impl->type();
        else
            return type_name;
    }

    bool is_litteral() const {
        if (impl)
            return impl->is_litteral();
        else
            return true;
    }

    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 {
        return impl != nullptr;
    }

    bool operator ! () const {
        return impl == nullptr;
    }

    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 {
        return new no_type();
    }

    no_type* evaluate(const interpreter_context&) const override {
        return clone();
    }

    std::string serialize(const interpreter_context&) const override {
        return "";
    }

    const std::string& type() const override {
        return type_name;
    }

    bool is_litteral() const override {
        return true;
    }

    static const std::string type_name;
};

class any_type : public symbol_impl {
public :
    any_type* clone() const override {
        return new any_type();
    }

    any_type* evaluate(const interpreter_context&) const override {
        return clone();
    }

    std::string serialize(const interpreter_context&) const override {
        return "";
    }

    const std::string& type() const override {
        return type_name;
    }

    bool is_litteral() const override {
        return false;
    }

    static const std::string type_name;
};

#endif
