#include "application_icon_cache.h"

#include <GLFW/glfw3.h>
#include <algorithm>
#include <array>

#ifdef _WIN32
#include <windows.h>
#include <shellapi.h>
#endif

ApplicationIconCache::~ApplicationIconCache()
{
    shutdown();
}

unsigned int ApplicationIconCache::textureFor(const std::filesystem::path &executable)
{
    if (executable.empty())
        return 0;
    const std::string key = executable.string();
    const auto found = textures_.find(key);
    if (found != textures_.end())
        return found->second;
    const unsigned int texture = loadTexture(executable);
    textures_.emplace(key, texture);
    return texture;
}

unsigned int ApplicationIconCache::loadTexture(const std::filesystem::path &executable) const
{
#ifdef _WIN32
    HICON large_icon = nullptr;
    HICON small_icon = nullptr;
    if (ExtractIconExW(executable.wstring().c_str(), 0, &large_icon, &small_icon, 1) == 0)
        return 0;
    HICON icon = large_icon ? large_icon : small_icon;
    if (!icon)
        return 0;

    constexpr int size = 32;
    BITMAPINFO bitmap_info{};
    bitmap_info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bitmap_info.bmiHeader.biWidth = size;
    bitmap_info.bmiHeader.biHeight = -size;
    bitmap_info.bmiHeader.biPlanes = 1;
    bitmap_info.bmiHeader.biBitCount = 32;
    bitmap_info.bmiHeader.biCompression = BI_RGB;

    void *pixels = nullptr;
    HDC device = CreateCompatibleDC(nullptr);
    HBITMAP bitmap = CreateDIBSection(device, &bitmap_info, DIB_RGB_COLORS, &pixels, nullptr, 0);
    unsigned int texture = 0;
    if (bitmap && pixels)
    {
        const HGDIOBJ previous = SelectObject(device, bitmap);
        std::fill_n(static_cast<unsigned char *>(pixels), size * size * 4, 0);
        if (DrawIconEx(device, 0, 0, icon, size, size, 0, nullptr, DI_NORMAL))
        {
            auto *rgba = static_cast<unsigned char *>(pixels);
            for (int pixel = 0; pixel < size * size; ++pixel)
                std::swap(rgba[pixel * 4], rgba[pixel * 4 + 2]);
            glGenTextures(1, &texture);
            glBindTexture(GL_TEXTURE_2D, texture);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size, size, 0,
                         GL_RGBA, GL_UNSIGNED_BYTE, pixels);
            glBindTexture(GL_TEXTURE_2D, 0);
        }
        SelectObject(device, previous);
    }
    if (bitmap)
        DeleteObject(bitmap);
    if (device)
        DeleteDC(device);
    if (large_icon)
        DestroyIcon(large_icon);
    if (small_icon)
        DestroyIcon(small_icon);
    return texture;
#else
    (void)executable;
    return 0;
#endif
}

void ApplicationIconCache::shutdown()
{
    for (auto &[path, texture] : textures_)
    {
        (void)path;
        if (texture)
            glDeleteTextures(1, &texture);
    }
    textures_.clear();
}
