#ifndef SYMBOL_HPP
#define SYMBOL_HPP

#include <string>
#include <memory>
#include <map>
#include "cxstring.hpp"

struct interpreter_context;

using type_t = unsigned char;

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_name() const = 0;
    virtual type_t type_id() const = 0;
    virtual bool is_litteral() const = 0;
    virtual std::pair<std::size_t,std::size_t> priority(const interpreter_context&) const = 0;

public :
    virtual ~symbol_impl() {}
};

struct type_helper {
    static type_t get_type_id(const std::string& name);
    static std::string get_type_name(type_t t);

private :
    static std::map<type_t, std::string> type_map;
};

template<typename T>
class symbol_impl_proxy : public symbol_impl {
    const std::string& type_name() const override {
        return T::static_type_name();
    }

    type_t type_id() const override {
        return T::static_type_id();
    }

public :
    virtual ~symbol_impl_proxy() {}

    static type_t static_type_id() {
        static type_t type = type_helper::get_type_id(T::static_type_name());
        return type;
    }
};

class symbol {
    std::unique_ptr<symbol_impl> impl;

public :
    symbol() = default;
    explicit symbol(symbol_impl* s);

    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_name() const;
    type_t type_id() const;
    bool is_litteral() const;
    std::pair<std::size_t,std::size_t> priority(const interpreter_context& context) const;

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

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

    template<typename T>
    bool type_is() const {
        return type_id() == T::static_type_id();
    }

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

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

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

class variadic_type : public symbol_impl_proxy<variadic_type> {
public :
    variadic_type* clone() const override;
    variadic_type* evaluate(const interpreter_context&) const override;
    std::string serialize(const interpreter_context&) const override;
    static const std::string& static_type_name();
    bool is_litteral() const override;
    std::pair<std::size_t,std::size_t> priority(const interpreter_context&) const override;
};

template<typename ... Args>
struct check_variadic;

template<typename T>
struct check_variadic_impl {
    static const bool value = true;
};
template<>
struct check_variadic_impl<variadic_type> {
    static const bool value = false;
};

template<typename T, typename ... Args>
struct check_variadic<T, Args...> {
    static const bool value = check_variadic_impl<T>::value && check_variadic<Args...>::value;
};
template<typename T>
struct check_variadic<T> {
    static const bool value = true;
};
template<>
struct check_variadic<> {
    static const bool value = true;
};

#endif
