#include "renderer.h"

#include <spdlog/spdlog.h>

#include "renderer_texture.h"
#include "core/window/renderer_window.h"
#define STB_IMAGE_IMPLEMENTATION
#define STBI_FAILURE_USERMSG
#include "renderer_text.h"
#include "renderer_texture_array.h"
#include "stb_image.h"

#if DX_BACKEND
#include "backend/dx/dx_renderer.h"
#endif

using time_type = decltype(std::chrono::high_resolution_clock::now());
std::chrono::duration<double> delta_time = {};
time_type begin_time = {};
time_type last_time = {};
renderer* s_renderer = nullptr;

void renderer::tick() {
}

void renderer::destroy_texture(renderer_texture* texture) { delete texture; }

void renderer::destroy_texture_array(renderer_texture_array* texture_array) { delete texture_array; }

renderer_texture* renderer::load_image(const std::string& file_path, texture_format in_format) {
    int width, height, channels;
    const auto result = stbi_load(file_path.c_str(), &width, &height, &channels, 0);
    if (!result) {
        const char* reason = stbi_failure_reason();
        spdlog::critical("加载图片 {0} 失败: {1}", file_path, reason);
        return nullptr;
    }
    int target_channel_count = get_format_channel_count(in_format);
    unsigned int row_pitch = 0;

    auto texture = create_texture(width, height, in_format, device_memory_type::gpu);
    auto data = texture->lock(&row_pitch);
    for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
            unsigned char* src = result + (y * width + x) * channels;
            unsigned char* dst = static_cast<unsigned char*>(data) + y * row_pitch + x * target_channel_count;
            switch (channels) {
            case 1:
                dst[0] = src[0];
                dst[1] = 0;
                dst[2] = 0;
                dst[3] = 255;
                break;
            case 2:
                dst[0] = src[0];
                dst[1] = src[1];
                dst[2] = 0;
                dst[3] = 255;
                break;
            case 3:
                dst[0] = src[0];
                dst[1] = src[1];
                dst[2] = src[2];
                dst[3] = 255;
                break;
            case 4:
                dst[0] = src[0];
                dst[1] = src[1];
                dst[2] = src[2];
                dst[3] = src[3];
                break;
            default:
                break;
            }
        }
    }
    texture->unlock();
    stbi_image_free(result);

    return texture;
}

void renderer::destroy_window(renderer_window* window) {
    delete window;
}

renderer* aorii::get_renderer_raw() {
    return s_renderer;
}

bool aorii::create_renderer(renderer_api api) {
    if (!aorii_text::init_freetype())
        return false;
    begin_time = std::chrono::high_resolution_clock::now();
    if (s_renderer) return true;
    switch (api) {
#if DX_BACKEND
        case renderer_api::dx11: s_renderer = new dx_renderer();
            break;
#endif
#if GL_BACKEND
    case renderer_api::opengl:
        break;
#endif
#if VK_BACKEND
    case renderer_api::vulkan:
        break;
#endif
        default: spdlog::critical("Failed to create renderer!");
            assert(false);
    }

    if (!s_renderer->init()) {
        delete s_renderer;
        s_renderer = nullptr;
    }
    last_time = std::chrono::high_resolution_clock::now();
    return s_renderer != nullptr;
}

void aorii::destroy_renderer() {
    if (!s_renderer) return;

    s_renderer->destroy();
    delete s_renderer;
    s_renderer = nullptr;

    aorii_text::destroy_freetype();
}

void aorii::update() {
    const auto& current_time = std::chrono::high_resolution_clock::now();
    delta_time = current_time - last_time;
    last_time = current_time;

    s_renderer->tick();
    s_renderer->render();

    std::this_thread::yield();
}

const std::chrono::duration<double>& aorii::get_delta_time() {
    return delta_time;
}

std::chrono::duration<double> aorii::get_total_time() {
    return std::chrono::high_resolution_clock::now() - begin_time;
}
