#include "adapters/unity/chams/ChamsService.h"

#include <string>

#include "adapters/il2cpp/Il2CppExports.h"
#include "adapters/il2cpp/Il2CppResolver.h"
#include "adapters/il2cpp/Il2CppObjectAccess.h"
#include "adapters/il2cpp/Il2CppString.h"
#include "adapters/eft/common/PlayerWorldReader.h"
#include "adapters/unity/bindings/UnityTypes.h"
#include "adapters/il2cpp/Il2CppCollections.h"
#include "adapters/unity/bindings/UnityApi.h"
#include "adapters/unity/chams/resources/ChamsBundleRuntime.h"
#include "adapters/unity/chams/resources/ChamsShaderRepository.h"
#include "adapters/unity/chams/ChamsSupport.h"

namespace ta::adapters::unity::chams
{
void ChamsService::update(const Il2CppClass* game_world_class,
                          ta::adapters::il2cpp::Il2CppObjectInstance game_world_instance,
                          ta::adapters::il2cpp::Il2CppObjectInstance main_player,
                          const ChamsConfigSnapshot& config)
{
    if (!config.weapon_chams && !config.npc_chams && !config.gear_chams)
    {
        weapon_chams_.reset();
        npc_chams_.reset();
        return;
    }

    if (!unity_image_)
        unity_image_ = ta::adapters::il2cpp::resolve_image("UnityEngine.CoreModule.dll");

    // weapon chams
    if (config.weapon_chams)
        weapon_chams_.update(unity_image_, main_player, config);
    else
        weapon_chams_.reset();

    // chams
    if (config.npc_chams || config.gear_chams)
    {
        const std::string main_nickname = ta::adapters::eft::common::get_player_nickname(main_player);
        npc_chams_.update(game_world_class, unity_image_, game_world_instance, main_nickname, config);
    }
    else
    {
        npc_chams_.reset();
    }
}


void ChamsService::reset()
{
    unity_image_ = nullptr;
    npc_chams_.reset();
    weapon_chams_.reset();
    ta::adapters::unity::chams::shader_repository().reset();
}

void ChamsService::discard_session() noexcept
{
    // Scene teardown invalidates Unity Material pointers before the delayed
    // raid-state transition is observed. Session cleanup must only forget
    // cached pointers; restoring materials here would dereference destroyed
    // objects.
    unity_image_ = nullptr;
    npc_chams_.discard_session();
    weapon_chams_.discard_session();
    ta::adapters::unity::chams::shader_repository().reset();
}


bool ChamsService::capture_material_state(Il2CppObject* material, ChamsMaterialState& state)
{
    if (!material)
        return false;

    ta::adapters::il2cpp::Il2CppObjectInstance material_instance(material);
    const auto get_shader = material_instance.get_method<Il2CppObject*(*)(Il2CppObject*)>("get_shader", 0);
    const auto get_main_texture = material_instance.get_method<Il2CppObject*(*)(Il2CppObject*)>("get_mainTexture", 0);
    if (!get_shader || !get_main_texture)
        return false;

    state.material = material;
    state.shader = get_shader(material);
    state.main_texture = get_main_texture(material);
    return true;
}

bool ChamsService::restore_material_state(const ChamsMaterialState& state)
{
    if (!state.material)
        return false;

    ta::adapters::il2cpp::Il2CppObjectInstance material_instance(state.material);
    const auto set_shader = material_instance.get_method<void(*)(Il2CppObject*, Il2CppObject*)>("set_shader", 1);
    const auto set_main_texture = material_instance.get_method<void(*)(Il2CppObject*, Il2CppObject*)>("set_mainTexture", 1);
    if (!set_shader || !set_main_texture)
        return false;

    if (state.shader)
        set_shader(state.material, state.shader);
    set_main_texture(state.material, state.main_texture);
    return true;
}

bool ChamsService::apply_material_chams(const Il2CppImage* unity_image,
                                        ta::adapters::il2cpp::Il2CppObjectInstance material_instance,
                                        const char* shader_name,
                                        const ta::foundation::RgbaColor& visible_color,
                                        const ta::foundation::RgbaColor& occluded_color)
{
    if (!unity_image || !material_instance.get_instance() ||
        !ta::adapters::unity::chams::ChamsBundleRuntime::instance().current_bundle())
    {
        return false;
    }

    Il2CppObject* chams_shader = ta::adapters::unity::chams::shader_repository().resolve(unity_image, shader_name);
    if (!chams_shader)
        return false;

    const auto set_shader = material_instance.get_method<void(*)(Il2CppObject*, Il2CppObject*)>(
        "set_shader", 1);
    const auto set_color = material_instance.get_method<void(*)(
        Il2CppObject*, Il2CppString*, ta::adapters::unity::color)>("SetColor", 2);
    const auto set_main_texture = material_instance.get_method<void(*)(Il2CppObject*, Il2CppObject*)>(
        "set_mainTexture", 1);
    if (!set_shader || !set_color || !set_main_texture || !ta::adapters::il2cpp::exports::api().il2cpp_string_new)
        return false;

    Il2CppString* color_visible = ta::adapters::il2cpp::il2cpp_string_new("_Color_Visible");
    Il2CppString* color_occluded = ta::adapters::il2cpp::il2cpp_string_new("_Color_Occluded");
    if (!color_visible || !color_occluded)
        return false;

    set_shader(material_instance.get_instance(), chams_shader);
    set_color(material_instance.get_instance(), color_visible,
              ta::adapters::unity::color{visible_color.r, visible_color.g, visible_color.b, visible_color.a});
    set_color(material_instance.get_instance(), color_occluded,
              ta::adapters::unity::color{occluded_color.r, occluded_color.g, occluded_color.b, occluded_color.a});
    set_main_texture(material_instance.get_instance(), nullptr);
    return true;
}
}
