#include <tuple>
#include <memory>
#include <string>
#include <iostream>
#include <unordered_map>
#include <typeinfo>
#include <typeindex>
#include <functional>

// -----

class Arguments {
  public:
    struct ArgTupleBase
    {
    };

    template<typename... Ts>
    struct ArgTuple : public ArgTupleBase {
        std::tuple<Ts...> args;

        ArgTuple(Ts... ts) : args(std::make_tuple(ts...))
        {
        }

        // -----

        const std::tuple<Ts...>& get() const
        {
            return args;
        }
    };

    // -----

    template<typename... Ts>
    Arguments(Ts... ts) : args(new ArgTuple<Ts...>(ts...)), valid(sizeof...(ts) != 0)
    {
    }

    // -----

    // Indicates whether it holds any valid arguments.
    explicit operator bool() const
    {
        return valid;
    }

    // -----

    const std::unique_ptr<ArgTupleBase>& get() const
    {
        return args;
    }

  private:
    std::unique_ptr<ArgTupleBase> args;
    bool valid;
};

// -----

// Two example components.
class One {
    int i;
    bool b;

  public:
    One(int i, bool b) : i(i), b(b) {}

    static void* create(const Arguments& arg_holder)
    {
        // Insert parameter types here.
        auto& args
          = static_cast<Arguments::ArgTuple<int, bool>&>(*(arg_holder.get())).get();

        if (arg_holder)
        {
            return new One(std::get<0>(args), std::get<1>(args));
        }
        else
        {
            // Insert default parameters (if any) here.
            return new One(0, false);
        }
    }

    // Testing function.
    friend std::ostream& operator<<(std::ostream& os, const One& one)
    {
        return os << "One, with "
                  << one.i
                  << " and "
                  << std::boolalpha << one.b << std::noboolalpha
                  << ".\n";
    }
};
std::ostream& operator<<(std::ostream& os, const One& one);


class Two {
    char c;
    double d;

  public:
    Two(char c, double d) : c(c), d(d) {}

    static void* create(const Arguments& arg_holder)
    {
        // Insert parameter types here.
        auto& args
          = static_cast<Arguments::ArgTuple<char, double>&>(*(arg_holder.get())).get();

        if (arg_holder)
        {
            return new Two(std::get<0>(args), std::get<1>(args));
        }
        else
        {
            // Insert default parameters (if any) here.
            return new Two('\0', 0.0);
        }
    }

    // Testing function.
    friend std::ostream& operator<<(std::ostream& os, const Two& two)
    {
        return os << "Two, with "
                  << (two.c == '\0' ? "null" : std::string{ 1, two.c })
                  << " and "
                  << two.d
                  << ".\n";
    }
};
std::ostream& operator<<(std::ostream& os, const Two& two);

// -----

// This is the world interface.
class World
{
    // Actual worker.
    virtual void* create_impl(const std::type_index& ctype, const Arguments& arg_holder) = 0;

    // Type-to-create() map.
    static std::unordered_map<std::type_index, std::function<void*(const Arguments&)>> creators;

  public:
    // Templated front-end.
    template<typename ComponentType>
    ComponentType* createComponent(const Arguments& arg_holder)
    {
        return static_cast<ComponentType*>(create_impl(typeid(ComponentType), arg_holder));
    }

    // Populate type-to-create() map.
    static void populate_creators() {
        creators[typeid(One)] = &One::create;
        creators[typeid(Two)] = &Two::create;
    }
};
std::unordered_map<std::type_index, std::function<void*(const Arguments&)>> World::creators;


template<typename Allocator = std::allocator<World>>
class WorldImpl : public World
{
    void* create_impl(const std::type_index& ctype, const Arguments& arg_holder) override
    {
        return creators[ctype](arg_holder);
    }
};

class Entity
{
    World* world;

  public:
    template<typename ComponentType, typename... Args>
    void assign(Args... args)
    {
        ComponentType* component = world->createComponent<ComponentType>(Arguments(args...));

        std::cout << *component;

        delete component;
    }

    Entity() : world(new WorldImpl<>())
    {
    }

    ~Entity()
    {
        if (world) { delete world; }
    }
};

int main() {
    World::populate_creators();

    Entity e;

    e.assign<One>();
    e.assign<Two>();

    e.assign<One>(118, true);
    e.assign<Two>('?', 8.69);

    e.assign<One>('0', 8);      // Fails; calls something like One(1075929415, true).
    e.assign<One>((int)'0', 8); // Succeeds.
}