#include <type_traits>
#include <functional>
#include <stdexcept>
#include <sstream>
#include <string>
#include <vector>
#include <unordered_map>
#include <set>
#include <featherkit/entity/entitymanager.h>
#include <featherkit/entity/entity.h>
namespace fea
{
using Params = std::vector<std::string>;
using Template = std::unordered_map<std::string, std::string>;
namespace detail
{
Params split(const std::string& in, char delim)
{
Params params;
std::string param;
std::stringstream splitter(in);
while (std::getline(splitter, param, delim))
{
params.push_back(param);
}
return params;
}
} // namespace detail
class EntityFactory
{
public:
EntityFactory(EntityManager& manager)
: m_manager{ &manager }
{}
template<typename Fn>
void addPrimitive(const std::string& primitive, Fn parser)
{
if (m_mapper.find(primitive) != m_mapper.end())
{
throw std::logic_error{ "duplicate primitive [" + primitive + "]" };
}
using Type = typename std::result_of<Fn(const Params&)>::type;
// make parser/registration generator.
m_mapper[primitive] = [this, parser](const std::string& name)->Parser
{
m_manager->registerAttribute<Type>(name);
// make paser.
return [parser, name](const std::string& params)->Setter
{
auto value = parser(detail::split(params, ','));
// make setter.
return [name, value](EntityPtr& entity)
{
entity->setAttribute<Type>(name, value);
};
};
};
}
void map(const std::string& attribute, const std::string& primitive)
{
if (m_parser.find(attribute) != m_parser.end())
{
throw std::logic_error{ "duplicate attribute [" + attribute + "]" };
}
auto it = m_mapper.find(primitive);
if (it == m_mapper.end())
{
throw std::invalid_argument{ "no primitive [" + primitive + "]" };
}
m_parser.emplace(attribute, it->second(attribute));
}
void addTemplate(const std::string& name, const Template& data)
{
if (m_proto.find(name) != m_proto.end())
{
throw std::logic_error{ "duplicate template [" + name + "]" };
}
Proto proto;
for (const auto& elememt : data)
{
const auto& attribute = elememt.first;
const auto& arguments = elememt.second;
proto.attributes.insert(attribute);
if (arguments.size())
{
auto it = m_parser.find(attribute);
if (it == m_parser.end())
{
throw std::invalid_argument{ "attribute [" + attribute + "] not mapped to primitive" };
}
const auto& parser = it->second;
proto.values.insert({ attribute, parser(arguments) });
}
}
m_proto.emplace(name, std::move(proto));
}
WeakEntityPtr instantiate(const std::string& name)
{
const auto it = m_proto.find(name);
if (it == m_proto.end())
{
throw std::invalid_argument{ "no template [" + name + "]" };
}
const auto& attributes = it->second.attributes;
const auto& values = it->second.values;
auto entity = m_manager->createEntity(attributes).lock();
for (const auto& value : values)
{
value.setter(entity);
}
return entity;
}
private:
using Setter = std::function<void(EntityPtr&)>;
using Parser = std::function<Setter(const std::string&)>;
using Mapper = std::function<Parser(const std::string&)>;
private:
struct Proto
{
struct Value
{
bool operator<(const Value& other) const
{
return key < other.key;
}
std::string key;
Setter setter;
};
std::set<std::string> attributes;
std::set<Value> values;
};
private:
std::unordered_map<std::string, Proto> m_proto;
std::unordered_map<std::string, Parser> m_parser;
std::unordered_map<std::string, Mapper> m_mapper;
EntityManager* m_manager;
};
} // namespace fea