// Dear ImGui: standalone example application for DirectX 11
// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp.
// Read online: https://github.com/ocornut/imgui/tree/master/docs

#include "imgui.h"
#include "imgui_impl_win32.h"
#include "imgui_impl_dx11.h"
#include <d3d11.h>
#include <tchar.h>
#include "implot.h"
#include <array>
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

static ID3D11Device*            g_pd3dDevice = nullptr;
static ID3D11DeviceContext*     g_pd3dDeviceContext = nullptr;
static IDXGISwapChain*          g_pSwapChain = nullptr;
static ID3D11RenderTargetView*  g_mainRenderTargetView = nullptr;

bool CreateDeviceD3D(HWND hWnd);
void CleanupDeviceD3D();
void CreateRenderTarget();
void CleanupRenderTarget();
LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

unsigned char* ImageData(const char* filename, int &width, int &height, int &channels)
{
    unsigned char* image_data = stbi_load(filename, &width, &height, &channels, 4);
    return image_data;
}

bool LoadTextureFromFile(unsigned char* image_data, int image_width, int image_height, ID3D11ShaderResourceView** out_srv)
{
    D3D11_TEXTURE2D_DESC desc;
    ZeroMemory(&desc, sizeof(desc));
    desc.Width = image_width;
    desc.Height = image_height;
    desc.MipLevels = 1;
    desc.ArraySize = 1;
    desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    desc.SampleDesc.Count = 1;
    desc.Usage = D3D11_USAGE_DEFAULT;
    desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
    desc.CPUAccessFlags = 0;

    ID3D11Texture2D *pTexture = NULL;
    D3D11_SUBRESOURCE_DATA subResource;
    subResource.pSysMem = image_data;
    subResource.SysMemPitch = desc.Width * 4;
    subResource.SysMemSlicePitch = 0;
    g_pd3dDevice->CreateTexture2D(&desc, &subResource, &pTexture);

    D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
    ZeroMemory(&srvDesc, sizeof(srvDesc));
    srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
    srvDesc.Texture2D.MipLevels = desc.MipLevels;
    srvDesc.Texture2D.MostDetailedMip = 0;
    g_pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, out_srv);
    pTexture->Release();

    return true;
}

#undef max;
#undef min;
#include <algorithm>
#include <iostream>
#include <cmath>
#include <vector>

// Funkcja dokonuj�ca dylacji
void dilation(const unsigned char* image, int width, int height, unsigned char* output) {
    int structuringElement[3][3] = {
        {0, 255, 0},
        {255, 255, 255},
        {0, 255, 0}
    };

    for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
            int maxPixelValue = 0;
            for (int j = -1; j <= 1; ++j) {
                for (int i = -1; i <= 1; ++i) {
                    if (x + i >= 0 && x + i < width && y + j >= 0 && y + j < height) {
                        int imagePixelValue = image[(y + j) * width + (x + i)];
                        int structuringElementValue = structuringElement[j + 1][i + 1];
                        int newPixelValue = imagePixelValue + structuringElementValue;
                        if (newPixelValue > maxPixelValue) {
                            maxPixelValue = newPixelValue;
                        }
                    }
                }
            }
            output[y * width + x] = static_cast<unsigned char>(maxPixelValue > 255 ? 255 : maxPixelValue);
        }
    }
}

// Funkcja dokonuj�ca erozji
void erosion(const unsigned char* image, int width, int height, unsigned char* output) {
    int structuringElement[3][3] = {
        {0, 255, 0},
        {255, 255, 255},
        {0, 255, 0}
    };

    for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
            int minPixelValue = 255;
            for (int j = -1; j <= 1; ++j) {
                for (int i = -1; i <= 1; ++i) {
                    if (x + i >= 0 && x + i < width && y + j >= 0 && y + j < height) {
                        int imagePixelValue = image[(y + j) * width + (x + i)];
                        int structuringElementValue = structuringElement[j + 1][i + 1];
                        int newPixelValue = imagePixelValue - structuringElementValue;
                        if (newPixelValue < minPixelValue) {
                            minPixelValue = newPixelValue;
                        }
                    }
                }
            }
            output[y * width + x] = static_cast<unsigned char>(minPixelValue < 0 ? 0 : minPixelValue);
        }
    }
}

// Funkcja dokonuj�ca hit-and-miss
void hitAndMiss(const unsigned char* image, int width, int height, unsigned char* output) {
    int structuringElement1[3][3] = {
        {0, 0, 0},
        {255, 255, 0},
        {0, 255, 0}
    };

    int structuringElement2[3][3] = {
        {255, 255, 255},
        {0, 0, 255},
        {0, 0, 0}
    };

    for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
            bool match1 = true;
            bool match2 = true;
            for (int j = -1; j <= 1; ++j) {
                for (int i = -1; i <= 1; ++i) {
                    if (x + i >= 0 && x + i < width && y + j >= 0 && y + j < height) {
                        int imagePixelValue = image[(y + j) * width + (x + i)];
                        int structuringElement1Value = structuringElement1[j + 1][i + 1];
                        int structuringElement2Value = structuringElement2[j + 1][i + 1];
                        if (structuringElement1Value == 255 && imagePixelValue != 255) {
                            match1 = false;
                        }
                        if (structuringElement2Value == 255 && imagePixelValue != 0) {
                            match2 = false;
                        }
                    }
                }
            }
            output[y * width + x] = (match1 && match2) ? 255 : 0;
        }
    }
}

// Funkcja dokonuj�ca pogrubiania
void thickening(const unsigned char* image, int width, int height, unsigned char* output) {
    int structuringElement[3][3] = {
        {0, 255, 0},
        {255, 255, 255},
        {0, 255, 0}
    };

    for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
            if (image[y * width + x] == 255) {
                for (int j = -1; j <= 1; ++j) {
                    for (int i = -1; i <= 1; ++i) {
                        if (x + i >= 0 && x + i < width && y + j >= 0 && y + j < height) {
                            int structuringElementValue = structuringElement[j + 1][i + 1];
                            if (structuringElementValue == 255) {
                                output[(y + j) * width + (x + i)] = 255;
                            }
                        }
                    }
                }
            }
        }
    }
}

// Funkcja dokonuj�ca pocieniania
void thinning(const unsigned char* image, int width, int height, unsigned char* output) {
    int structuringElement[3][3] = {
        {0, 255, 0},
        {255, 255, 255},
        {0, 255, 0}
    };

    for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
            if (image[y * width + x] == 255) {
                bool match = true;
                for (int j = -1; j <= 1; ++j) {
                    for (int i = -1; i <= 1; ++i) {
                        if (x + i >= 0 && x + i < width && y + j >= 0 && y + j < height) {
                            int imagePixelValue = image[(y + j) * width + (x + i)];
                            int structuringElementValue = structuringElement[j + 1][i + 1];
                            if (structuringElementValue == 255 && imagePixelValue != 255) {
                                match = false;
                                break;
                            }
                        }
                    }
                    if (!match) {
                        break;
                    }
                }
                output[y * width + x] = match ? 255 : 0;
            }
        }
    }
}

void rgbToHsl(float r, float g, float b, float &_h, float &_s, float &_l)
{
    r /= 255.f;
    g /= 255.f;
    b /= 255.f;
    float max = std::max({r, g, b});
    float min = std::min({r, g, b});
    float h, s, l = (max + min) / 2.f;

    if(max - min < .001f)
    {
        h = s = 0.f;
    }
    else
    {
        float d = max - min;
        s = l > 0.5f ? d / (2.f - max - min) : d / (max + min);
        if (max - r < .001f)
        {
            h = (g - b) / d + (g < b ? 6.f : 0.f);
        }
        else if (max - g < .001f)
        {
            h = (b - r) / d + 2.f;
        }
        else if (max - b < .001f)
        {
            h = (r - g) / d + 4.f;
        }
        h /= 6;
    }

    _h = h;
    _s = s;
    _l = l;
}

void hslToRgb(float h, float s, float l, int &_r, int &_g, int &_b){
    float r, g, b = 0.f;

    if(s < .001f)
    {
        r = g = b = l;
    }
    else
    {
        auto hue2rgb = [](float p, float q, float t) {
            if (t < 0) t += 1.f;
            if (t > 1) t -= 1.f;
            if (t < 1.f / 6.f) return p + (q - p) * 6.f * t;
            if (t < 1.f / 2.f) return q;
            if (t < 2.f / 3.f) return p + (q - p) * (2.f / 3.f - t) * 6.f;
            return p;
        };

        float q = l < 0.5f ? l * (1.f + s) : l + s - l * s;
        float p = 2.f * l - q;
        r = hue2rgb(p, q, h + 1.f/3.f);
        g = hue2rgb(p, q, h);
        b = hue2rgb(p, q, h - 1.f/3.f);
    }

    _r = std::clamp(int(r * 255), 0, 255);
    _g = std::clamp(int(g * 255), 0, 255);
    _b = std::clamp(int(b * 255), 0, 255);
}

void InvertImageColors(unsigned char* imageData, int width, int height)
{
    for (int y = 0; y < height; ++y)
    {
        for (int x = 0; x < width; ++x)
        {
            int index = (y * width + x) * 4;
            imageData[index] = 255 - imageData[index];
            imageData[index + 1] = 255 - imageData[index + 1]; 
            imageData[index + 2] = 255 - imageData[index + 2]; 
        }
    }
}

void Sepia(unsigned char* imageData, int width, int height)
{
    for (int y = 0; y < height; ++y)
    {
        for (int x = 0; x < width; ++x)
        {
            int index = (y * width + x) * 4;
            imageData[index] = int(std::min((imageData[index] * .393f) + (imageData[index + 1] * .769f) + (imageData[index + 2] * .189f), 255.f));
            imageData[index + 1] = int(std::min((imageData[index] * .349f) + (imageData[index + 1] * .686f) + (imageData[index + 2] * .168f), 255.f));
            imageData[index + 2] = int(std::min((imageData[index] * .272f) + (imageData[index + 1] * .534f) + (imageData[index + 2] * .131f), 255.f));
        }
    }
}

void Saturation(unsigned char* imageData, int width, int height, float scale)
{
    float h, s, l = 0.f;

    for (int y = 0; y < height; ++y)
    {
        for (int x = 0; x < width; ++x)
        {
            int index = (y * width + x) * 4;
            int r = imageData[index];
            int b = imageData[index + 1];
            int g = imageData[index + 2];
            rgbToHsl(r, g, b, h, s, l);
            s *= scale;
            hslToRgb(h, s, l, r, g, b);
            imageData[index] = r;
            imageData[index + 1] = g;
            imageData[index + 2] = b;
        }
    }
}

void Light(unsigned char* imageData, int width, int height, float scale)
{
    float h, s, l = 0.f;
    for (int y = 0; y < height; ++y)
    {
        for (int x = 0; x < width; ++x)
        {
            int index = (y * width + x) * 4;
            int r = imageData[index];
            int b = imageData[index + 1];
            int g = imageData[index + 2];
            rgbToHsl(r, g, b, h, s, l);
            l *= scale;
            hslToRgb(h, s, l, r, g, b);
            imageData[index] = r;
            imageData[index + 1] = g;
            imageData[index + 2] = b;
        }
    }
}

std::array<std::array<float, 7>, 7> KernelFunction(float sigma)
{
    const int kernel_size = 7;
    std::array<std::array<float, 7>, 7> kernel;
    float sum = 0.0f;

    for (int i = 0; i < kernel_size; i++) {
        for (int j = 0; j < kernel_size; j++) {
            int x = i - kernel_size / 2;
            int y = j - kernel_size / 2;
            kernel[i][j] = exp(-(x * x + y * y) / (2 * sigma * sigma));
            sum += kernel[i][j];
        }
    }

    for (int i = 0; i < kernel_size; i++) {
        for (int j = 0; j < kernel_size; j++) {
            kernel[i][j] /= sum;
        }
    }
    return kernel;
}

void GaussianKernel(unsigned char* imageData, int width, int height, int channels, int kernel_size, std::array<std::array<float, 7>, 7> kernel)
{

    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            for (int c = 0; c < channels; c++) {
                float sum = 0.0f;
                for (int i = 0; i < kernel_size; i++) {
                    for (int j = 0; j < kernel_size; j++) {
                        int px = x + i - kernel_size / 2;
                        int py = y + j - kernel_size / 2;
                        if (px < 0 || px >= width || py < 0 || py >= height) {
                            continue;
                        }
                        sum += kernel[i][j] * imageData[(py * width + px) * channels + c];
                    }
                }
                imageData[(y * width + x) * channels + c] = (unsigned char)sum;
            }
        }
    }
}

unsigned char* greyScale(const unsigned char* image, unsigned char* grayscaleImage, int width, int height, int numChannels) {
    for (int i = 0; i < width * height; ++i) {
        unsigned char r = image[i * numChannels];
        unsigned char g = image[i * numChannels + 1];
        unsigned char b = image[i * numChannels + 2];

        grayscaleImage[i] = static_cast<unsigned char>(0.2989 * r + 0.587 * g + 0.114 * b);
    }
    return grayscaleImage;
}

void binaryTransform(unsigned char* grayscaleImage, unsigned char* binaryImage, int width, int height, int threshold) {
    for (int i = 0; i < width * height; ++i) {
        binaryImage[i] = (grayscaleImage[i] > threshold) ? 255 : 0;
    }
}

int calculateOtsuThreshold(const unsigned char* image, int width, int height) {

    int histogram[256] = {0};
    for (int i = 0; i < width * height; ++i) {
        histogram[image[i]]++;
    }

    int totalPixels = width * height;
    float sum = 0.0;
    for (int i = 0; i < 256; ++i) {
        sum += i * histogram[i];
    }

    float sumB = 0.0;
    int wB = 0;
    int wF = 0;
    float maxVariance = 0.0;
    int threshold = 0;

    for (int i = 0; i < 256; ++i) {
        wB += histogram[i];
        if (wB == 0) continue;

        wF = totalPixels - wB;
        if (wF == 0) break;

        sumB += i * histogram[i];

        float meanB = sumB / wB;
        float meanF = (sum - sumB) / wF;

        float betweenVariance = wB * wF * (meanB - meanF) * (meanB - meanF);

        if (betweenVariance > maxVariance) {
            maxVariance = betweenVariance;
            threshold = i;
        }
    }

    return threshold;
}

void hough_transform(unsigned char* input_image, int width, int height, int threshold)
{
    int hough_width = 2 * (width + height);
    int hough_height = 180;
    int* hough_space = new int[hough_width * hough_height]();

    for (int y = 0; y < height; y++)
    {
        for (int x = 0; x < width; x++)
        {
            if (input_image[(y * width + x)] > threshold)
            {
                for (int theta = 0; theta < hough_height; theta++)
                {
                    double angle = theta * (3.14 / 180.0);
                    int rho = static_cast<int>((x * cos(angle)) + (y * sin(angle)));
                    rho += (hough_width / 2);
                    hough_space[(theta * hough_width) + rho]++;
                }
            }
        }
    }

    int max_value = 0;
    for (int i = 0; i < hough_width * hough_height; i++)
    {
        if (hough_space[i] > max_value)
        {
            max_value = hough_space[i];
        }
    }

    unsigned char* hough_image = new unsigned char[hough_width * hough_height * 3]();
    for (int y = 0; y < hough_height; y++)
    {
        for (int x = 0; x < hough_width; x++)
        {
            int value = static_cast<int>(255.0 * hough_space[(y * hough_width) + x] / max_value);
            hough_image[3 * (y * hough_width + x)] = value;
            hough_image[3 * (y * hough_width + x) + 1] = value;
            hough_image[3 * (y * hough_width + x) + 2] = value;
        }
    }
}

int main(int, char**)
{
    WNDCLASSEXW wc = { sizeof(wc), CS_CLASSDC, WndProc, 0L, 0L, GetModuleHandle(nullptr), nullptr, nullptr, nullptr, nullptr, L"ImGui Example", nullptr };
    ::RegisterClassExW(&wc);
    HWND hwnd = ::CreateWindowW(wc.lpszClassName, L"Dear ImGui DirectX11 Example", WS_OVERLAPPEDWINDOW, 100, 100, 1280, 800, nullptr, nullptr, wc.hInstance, nullptr);

    if (!CreateDeviceD3D(hwnd))
    {
        CleanupDeviceD3D();
        ::UnregisterClassW(wc.lpszClassName, wc.hInstance);
        return 1;
    }

    ::ShowWindow(hwnd, SW_SHOWDEFAULT);
    ::UpdateWindow(hwnd);

    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImPlot::CreateContext();
    //ImPlot::CreateContext();
    ImGuiIO& io = ImGui::GetIO(); (void)io;
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;

    ImGui::StyleColorsDark();

    ImGui_ImplWin32_Init(hwnd);
    ImGui_ImplDX11_Init(g_pd3dDevice, g_pd3dDeviceContext);

    bool main_window = true;
    bool show_another_window = false;
    bool image_window = false;
    bool histogram = false;
    char image_location[MAX_PATH] = {};
    ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);

    bool negative = false;
    bool sepia = false;
    bool saturation = false;
    bool blure = false;
    float scaleS = 0.f;
    bool light = false;
    float scaleL = 0.f;
    float sigma = 2.0f;
    bool binary = false;
    bool hough = false;

    bool done = false;

    int* colorR = nullptr;
    int* colorG = nullptr;
    int* colorB = nullptr;

    while (!done)
    {
        MSG msg;
        while (::PeekMessage(&msg, nullptr, 0U, 0U, PM_REMOVE))
        {
            ::TranslateMessage(&msg);
            ::DispatchMessage(&msg);
            if (msg.message == WM_QUIT)
                done = true;
        }
        if (done)
            break;

        ImGui_ImplDX11_NewFrame();
        ImGui_ImplWin32_NewFrame();
        ImGui::NewFrame();

        if (main_window)
        {
            static float f = 0.0f;
            static int counter = 0;

            ImGui::Begin("App");                    
            ImGui::InputText("Image location", image_location, sizeof(image_location), image_window ? ImGuiInputTextFlags_ReadOnly : 0);

            if (!image_window)
            {
                if (ImGui::Button("Load Image"))
                image_window = true;
            }
            else
            {
                if (ImGui::Button("Close Image"))
                image_window = false;

                if (colorR != NULL && colorB != NULL && colorG != NULL)
                {
                    delete[] colorR;
                    delete[] colorG;
                    delete[] colorB;

                    colorR = NULL;
                    colorG = NULL;
                    colorB = NULL;
                }
            }
            ImGui::End();
        }

        if (histogram)
        {
            ImGui::Begin("Histogram");

            ImGui::End();
        }

        if (image_window)
        {
            int my_image_width = 0;
            int my_image_height = 0;
            int my_image_channels = 0;
            unsigned char* image_data = ImageData(image_location, my_image_width, my_image_height, my_image_channels);
            unsigned char* grayscaleImage = new unsigned char[my_image_width * my_image_height];
            unsigned char* binaryImage = new unsigned char[my_image_width * my_image_height];
            if (colorR == NULL && colorB == NULL && colorG == NULL) {
                colorR = new int[my_image_width * my_image_height];
                colorG = new int[my_image_width * my_image_height];
                colorB = new int[my_image_width * my_image_height];

                int place = 0;

                for (int y = 0; y < my_image_height; ++y)
                {
                    for (int x = 0; x < my_image_width; ++x)
                    {
                        int index = (y * my_image_width + x) * 4;

                        colorR[place] = image_data[index];
                        colorG[place] = image_data[index + 1];
                        colorB[place++] = image_data[index + 2];
                    }
                }
            }


            if (negative)
            {
                InvertImageColors(image_data, my_image_width, my_image_height);
            }
            if (sepia)
            {
                Sepia(image_data, my_image_width, my_image_height);
            }
            if (saturation)
            {
                Saturation(image_data, my_image_width, my_image_height, scaleS);
            }
            if (light)
            {
                Light(image_data, my_image_width, my_image_height, scaleL);
            }
            if (blure)
            {
                GaussianKernel(image_data, my_image_width, my_image_height, my_image_channels, 7, KernelFunction(sigma));
            }
            if (binary)
            {
                binaryTransform(greyScale(image_data, grayscaleImage, my_image_width, my_image_height, my_image_channels), binaryImage, my_image_width, my_image_height, calculateOtsuThreshold(image_data, my_image_width, my_image_height));
            }
            if (hough)
            {
                hough_transform(image_data, my_image_width, my_image_height, calculateOtsuThreshold(image_data, my_image_width, my_image_height));
            }
            if (!image_data)
            {
                ImGui::Begin("Error");  
                ImGui::Text("Wrong path");
                if (ImGui::Button("Ok"))
                    image_window = false;
                ImGui::End();
            }
            else
            {
                ID3D11ShaderResourceView* my_texture = NULL;
                if (binary == TRUE) {
                    bool ret = LoadTextureFromFile(binaryImage, my_image_width, my_image_height, &my_texture);
                    stbi_image_free(binaryImage);
                    stbi_image_free(grayscaleImage);
                    stbi_image_free(image_data);
                }
                else {
                    bool ret = LoadTextureFromFile(image_data, my_image_width, my_image_height, &my_texture);
                    stbi_image_free(image_data);
                }
                ImGui::Begin("Image window");
                ImGui::Image((void*)my_texture, ImVec2(my_image_width, my_image_height));
                ImGui::End();
                ImGui::Begin("Filters");
                ImGui::Checkbox("Histogram", &histogram);
                ImGui::Checkbox("Negative", &negative);
                ImGui::Checkbox("Sepia", &sepia);
                ImGui::Checkbox("Saturation", &saturation);
                ImGui::SameLine();
                ImGui::SliderFloat(" ", &scaleS, 0.f, 1.f);
                ImGui::Checkbox("Light", &light);
                ImGui::SameLine();
                ImGui::SliderFloat(" ", &scaleL, 0.f, 1.f);
                ImGui::Checkbox("Blure", &blure);
                ImGui::SameLine();
                ImGui::SliderFloat(" ", &sigma, 0.f, 10.f);
                ImGui::Checkbox("Binaryzacja", &binary);
                ImGui::Checkbox("Hough transform", &hough);
                ImGui::End();
            }
        }

        ImGui::Render();
        const float clear_color_with_alpha[4] = { clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w };
        g_pd3dDeviceContext->OMSetRenderTargets(1, &g_mainRenderTargetView, nullptr);
        g_pd3dDeviceContext->ClearRenderTargetView(g_mainRenderTargetView, clear_color_with_alpha);
        ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());

        g_pSwapChain->Present(1, 0);
    }

    ImGui_ImplDX11_Shutdown();
    ImGui_ImplWin32_Shutdown();
    ImPlot::DestroyContext();
    ImGui::DestroyContext();

    CleanupDeviceD3D();
    ::DestroyWindow(hwnd);
    ::UnregisterClassW(wc.lpszClassName, wc.hInstance);

    return 0;
}


bool CreateDeviceD3D(HWND hWnd)
{
    DXGI_SWAP_CHAIN_DESC sd;
    ZeroMemory(&sd, sizeof(sd));
    sd.BufferCount = 2;
    sd.BufferDesc.Width = 0;
    sd.BufferDesc.Height = 0;
    sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    sd.BufferDesc.RefreshRate.Numerator = 60;
    sd.BufferDesc.RefreshRate.Denominator = 1;
    sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
    sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    sd.OutputWindow = hWnd;
    sd.SampleDesc.Count = 1;
    sd.SampleDesc.Quality = 0;
    sd.Windowed = TRUE;
    sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

    UINT createDeviceFlags = 0;
    D3D_FEATURE_LEVEL featureLevel;
    const D3D_FEATURE_LEVEL featureLevelArray[2] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_0, };
    HRESULT res = D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, createDeviceFlags, featureLevelArray, 2, D3D11_SDK_VERSION, &sd, &g_pSwapChain, &g_pd3dDevice, &featureLevel, &g_pd3dDeviceContext);
    if (res == DXGI_ERROR_UNSUPPORTED) 
        res = D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_WARP, nullptr, createDeviceFlags, featureLevelArray, 2, D3D11_SDK_VERSION, &sd, &g_pSwapChain, &g_pd3dDevice, &featureLevel, &g_pd3dDeviceContext);
    if (res != S_OK)
        return false;

    CreateRenderTarget();
    return true;
}

void CleanupDeviceD3D()
{
    CleanupRenderTarget();
    if (g_pSwapChain) { g_pSwapChain->Release(); g_pSwapChain = nullptr; }
    if (g_pd3dDeviceContext) { g_pd3dDeviceContext->Release(); g_pd3dDeviceContext = nullptr; }
    if (g_pd3dDevice) { g_pd3dDevice->Release(); g_pd3dDevice = nullptr; }
}

void CreateRenderTarget()
{
    ID3D11Texture2D* pBackBuffer;
    g_pSwapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer));
    g_pd3dDevice->CreateRenderTargetView(pBackBuffer, nullptr, &g_mainRenderTargetView);
    pBackBuffer->Release();
}

void CleanupRenderTarget()
{
    if (g_mainRenderTargetView) { g_mainRenderTargetView->Release(); g_mainRenderTargetView = nullptr; }
}

extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam))
        return true;

    switch (msg)
    {
    case WM_SIZE:
        if (g_pd3dDevice != nullptr && wParam != SIZE_MINIMIZED)
        {
            CleanupRenderTarget();
            g_pSwapChain->ResizeBuffers(0, (UINT)LOWORD(lParam), (UINT)HIWORD(lParam), DXGI_FORMAT_UNKNOWN, 0);
            CreateRenderTarget();
        }
        return 0;
    case WM_SYSCOMMAND:
        if ((wParam & 0xfff0) == SC_KEYMENU)
            return 0;
        break;
    case WM_DESTROY:
        ::PostQuitMessage(0);
        return 0;
    }
    return ::DefWindowProcW(hWnd, msg, wParam, lParam);
}