#include "../myclass.hpp"
#include <ctti/type_id.hpp>
#include <siplasplas/utility/fusion.hpp>
#include <siplasplas/utility/invoke.hpp>
#include <json4moderncpp/src/json.hpp>
#include <iostream>
#include <sstream>
template<typename T>
std::enable_if_t<std::is_fundamental<T>::value, nlohmann::json>
serialize(const T& object);
template<typename T>
std::enable_if_t<std::is_class<T>::value, nlohmann::json>
serialize(const T& object);
template<typename T>
nlohmann::json serialize(const std::vector<T>& values);
nlohmann::json serialize(const std::string& value);
template<typename... Ts>
nlohmann::json serialize(const std::tuple<Ts...>& value);
template<typename First, typename Second>
nlohmann::json serialize(const std::pair<First, Second>& value);
template<typename Key, typename Value>
nlohmann::json serialize(const std::unordered_map<Key, Value>& value);
template<typename T>
std::enable_if_t<std::is_fundamental<T>::value, nlohmann::json>
serialize(const T& value)
{
    auto json = nlohmann::json::object();
    json["value"] = value;
    return json;
}
template<typename T>
nlohmann::json serialize(const std::vector<T>& values)
{
    auto json  = nlohmann::json::object();
    auto array = nlohmann::json::array();
    for(const T& value : values)
    {
        array.push_back(serialize(value));
    }
    json["value"] = array;
    return json;
}
nlohmann::json serialize(const std::string& value)
{
    auto json = nlohmann::json::object();
    json["value"] = value;
    return json;
}
template<typename... Ts>
nlohmann::json serialize(const std::tuple<Ts...>& value)
{
    auto json  = nlohmann::json::object();
    auto map   = nlohmann::json::object();
    cpp::foreach_type<cpp::meta::make_index_sequence_for<Ts...>>([&](auto type)
    {
        using Index = cpp::meta::type_t<decltype(type)>;
        map[std::to_string(Index::value)] = serialize(std::get<Index::value>(value));
    });
    json["value"] = map;
    return json;
}
template<typename First, typename Second>
nlohmann::json serialize(const std::pair<First, Second>& value)
{
    auto json  = nlohmann::json::object();
    auto array = nlohmann::json::array();
    array.push_back(serialize(value.first));
    array.push_back(serialize(value.second));
    json["value"] = array;
    return json;
}
template<typename Key, typename Value>
nlohmann::json serialize(const std::unordered_map<Key, Value>& value)
{
    auto json   = nlohmann::json::object();
    auto array  = nlohmann::json::array();
    for(const auto& keyValue : value)
    {
        const auto& key   = keyValue.first;
        const auto& value = keyValue.second;
        array.push_back(nlohmann::json::object({
            {"key", serialize(key)},
            {"value", serialize(value)}
        }));
    }
    json["value"] = array;
    return json;
}
template<typename T>
std::enable_if_t<std::is_class<T>::value, nlohmann::json> serialize(const T& object)
{
    auto json = nlohmann::json::object();
    auto fields = nlohmann::json::object();
    
    
    
    cpp::foreach_type<typename cpp::static_reflection::Class<T>::Fields>([&](auto type)
    {
        using FieldInfo = cpp::meta::type_t<decltype(type)>;
        fmt::print("In class {}, field {} (Type: {})\n",
            ctti::type_id<T>().name(),
            FieldInfo::SourceInfo::spelling(),
            ctti::type_id<typename FieldInfo::value_type>().name()
        );
        
        fields[FieldInfo::SourceInfo::spelling()] = serialize(
        );
    });
    json["value"] = fields;
    return json;
}
namespace
{
template<typename T>
struct IsNotSpecializedType : public std::true_type {};
template<typename... Ts>
struct IsNotSpecializedType<
std::tuple<Ts...>> : 
public std::false_type {};
 template<typename Key, typename Value>
struct IsNotSpecializedType<std::unordered_map<Key, Value>> : public std::false_type {};
template<typename T>
struct IsNotSpecializedType<std::vector<T>> : public std::false_type {};
template<typename T, typename = void>
class Deserialize;
template<typename T>
class Deserialize<T, std::enable_if_t<std::is_fundamental<T>::value>>
{
public:
    static T apply(const nlohmann::json& json)
    {
        return json["value"];
    }
};
template<typename T>
class Deserialize<T, std::enable_if_t<std::is_class<T>::value && IsNotSpecializedType<T>::value>>
{
public:
    static T apply(const nlohmann::json& json)
    {
        const auto& fields = json["value"];
        T object;
        cpp::foreach_type<typename cpp::static_reflection::Class<T>::Fields>([&](auto type)
        {
            using FieldInfo = cpp::meta::type_t<decltype(type)>;
            using Type = typename FieldInfo::value_type;
            fmt::print("Deserializing {} (Type: {}, Stored type: {})\n",
                ctti::type_id<T>().name(),
                ctti::type_id<Type>().name(),
                fields[FieldInfo::SourceInfo::spelling()]["type"].template get<std::string>()
            );
            cpp::invoke(FieldInfo::get(), 
object) = Deserialize<Type>::apply(fields[FieldInfo::SourceInfo::spelling()]);
         });
        return object;
    }
};
template<typename T>
class Deserialize<std::vector<T>, void>
{
public:
    static std::vector<T> apply(const nlohmann::json& json)
    {
        std::vector<T> vector;
        vector.reserve(json.size());
        for(const auto& value : json["value"])
        {
            vector.emplace_back(Deserialize<T>::apply(value));
        }
        return vector;
    }
};
template<>
class Deserialize<std::string, void>
{
public:
    static std::string apply(const nlohmann::json& json)
    {
        return json["value"];
    }
};
template<typename Key, typename Value>
class Deserialize<std::unordered_map<Key, Value>, void>
{
public:
    static std::unordered_map<Key, Value> apply(const nlohmann::json& json)
    {
        std::unordered_map<Key, Value> map;
        for(const auto& keyValue : json["value"])
        {
                "keyValue ({}) must be an object node. Is an {} instead", keyValue.dump(), keyValue.type()
            );
            map[Deserialize<Key>::apply(keyValue["key"])] = Deserialize<Value>::apply(keyValue["value"]);
        }
        return map;
    }
};
template<typename... Ts>
class Deserialize<std::tuple<Ts...>, void>
{
public:
    static std::tuple<Ts...> apply(const nlohmann::json& json)
    {
        std::tuple<Ts...> tuple;
        cpp::foreach_type<cpp::meta::make_index_sequence_for<Ts...>>([&](auto type)
        {
            using Index = cpp::meta::type_t<decltype(type)>;
            std::get<Index::value>(tuple) = Deserialize<cpp::meta::pack_get_t<Index::value, Ts...>>::apply(json["value"][std::to_string(Index::value)]);
        });
        return tuple;
    }
};
template<typename First, typename Second>
class Deserialize<std::pair<First, Second>, void>
{
public:
    static std::pair<First, Second> apply(const nlohmann::json& json)
    {
        return std::make_pair(
            Deserialize<First>::apply(json["value"]["first"]),
            Deserialize<Second>::apply(json["value"]["second"])
        );
    }
};
}
template<typename T>
T deserialize(const nlohmann::json& json)
{
    return Deserialize<T>::apply(json);
}
int main()
{
    MyClass myObject;
    auto json = serialize(myObject);
    auto deserializedObject = deserialize<MyClass>(json);
    auto reserializedObject = serialize(deserializedObject);
    std::cout << "Serialized object: " << json.dump(1) << std::endl;
    std::cout << "Re-serialized deserialized object: " << reserializedObject.dump(1) << std::endl;
}