#include "egl_context.h"

#include <android/log.h>
#include <android/native_window.h>
#include <unistd.h>

#define TAG "LckEglContext"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)

#ifndef EGL_NATIVE_BUFFER_ANDROID
#define EGL_NATIVE_BUFFER_ANDROID 0x3140
#endif

#ifndef EGL_SYNC_NATIVE_FENCE_ANDROID
#define EGL_SYNC_NATIVE_FENCE_ANDROID 0x3144
#endif

#ifndef EGL_SYNC_NATIVE_FENCE_FD_ANDROID
#define EGL_SYNC_NATIVE_FENCE_FD_ANDROID 0x3145
#endif

#ifndef EGL_RECORDABLE_ANDROID
#define EGL_RECORDABLE_ANDROID 0x3142
#endif

EglContext::EglContext() {}

EglContext::~EglContext() {
    Release();
}

bool EglContext::LoadExtensions() {
    eglCreateSyncKHR = (PFNEGLCREATESYNCKHRPROC)eglGetProcAddress("eglCreateSyncKHR");
    eglWaitSyncKHR = (PFNEGLWAITSYNCKHRPROC)eglGetProcAddress("eglWaitSyncKHR");
    eglDestroySyncKHR = (PFNEGLDESTROYSYNCKHRPROC)eglGetProcAddress("eglDestroySyncKHR");
    eglGetNativeClientBufferANDROID = (PFNEGLGETNATIVECLIENTBUFFERANDROIDPROC)eglGetProcAddress("eglGetNativeClientBufferANDROID");
    eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR");
    eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR");
    glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES");
    eglPresentationTimeANDROID = (PFNEGLPRESENTATIONTIMEANDROIDPROC)eglGetProcAddress("eglPresentationTimeANDROID");

    if (!eglGetNativeClientBufferANDROID || !eglCreateImageKHR ||
        !eglDestroyImageKHR || !glEGLImageTargetTexture2DOES) {
        LOGE("Missing required EGL extensions for HardwareBuffer import");
        return false;
    }

    return true;
}

bool EglContext::Init() {
    display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    if (display == EGL_NO_DISPLAY) {
        LOGE("eglGetDisplay failed");
        return false;
    }

    EGLint major, minor;
    if (!eglInitialize(display, &major, &minor)) {
        LOGE("eglInitialize failed");
        return false;
    }
    LOGI("EGL initialized: %d.%d", major, minor);

    // EGL config: RGBA8, ES3, recordable for MediaCodec
    EGLint configAttribs[] = {
        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,
        EGL_RED_SIZE, 8,
        EGL_GREEN_SIZE, 8,
        EGL_BLUE_SIZE, 8,
        EGL_ALPHA_SIZE, 8,
        EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
        EGL_RECORDABLE_ANDROID, EGL_TRUE,
        EGL_NONE
    };

    EGLint numConfigs;
    if (!eglChooseConfig(display, configAttribs, &config, 1, &numConfigs) || numConfigs == 0) {
        LOGE("eglChooseConfig failed");
        return false;
    }

    EGLint contextAttribs[] = {
        EGL_CONTEXT_CLIENT_VERSION, 3,
        EGL_NONE
    };

    context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs);
    if (context == EGL_NO_CONTEXT) {
        LOGE("eglCreateContext failed");
        return false;
    }

    if (!LoadExtensions()) {
        return false;
    }

    LOGI("EGL context created successfully");
    return true;
}

bool EglContext::CreateWindowSurface(ANativeWindow* window) {
    if (surface != EGL_NO_SURFACE) {
        eglDestroySurface(display, surface);
    }

    surface = eglCreateWindowSurface(display, config, window, nullptr);
    if (surface == EGL_NO_SURFACE) {
        LOGE("eglCreateWindowSurface failed: 0x%x", eglGetError());
        return false;
    }

    eglQuerySurface(display, surface, EGL_WIDTH, &surfaceWidth);
    eglQuerySurface(display, surface, EGL_HEIGHT, &surfaceHeight);
    LOGI("EGL window surface created: %dx%d", surfaceWidth, surfaceHeight);
    return true;
}

GLuint EglContext::ImportHardwareBuffer(AHardwareBuffer* buffer) {
    if (!eglGetNativeClientBufferANDROID || !eglCreateImageKHR || !glEGLImageTargetTexture2DOES) {
        LOGE("Missing EGL extensions for HardwareBuffer import");
        return 0;
    }

    EGLClientBuffer clientBuffer = eglGetNativeClientBufferANDROID(buffer);
    if (!clientBuffer) {
        LOGE("eglGetNativeClientBufferANDROID failed");
        return 0;
    }

    EGLint imageAttribs[] = {
        EGL_IMAGE_PRESERVED_KHR, EGL_TRUE,
        EGL_NONE
    };

    EGLImageKHR image = eglCreateImageKHR(display, EGL_NO_CONTEXT,
                                           EGL_NATIVE_BUFFER_ANDROID,
                                           clientBuffer, imageAttribs);
    if (image == EGL_NO_IMAGE_KHR) {
        LOGE("eglCreateImageKHR failed: 0x%x", eglGetError());
        return 0;
    }

    GLuint textureId;
    glGenTextures(1, &textureId);
    glBindTexture(GL_TEXTURE_EXTERNAL_OES, textureId);
    glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, image);

    // We need to keep the image alive — store it associated with the texture
    // The caller must call ReleaseImportedTexture to clean up
    // For now, we destroy the image immediately since the texture retains the content
    eglDestroyImageKHR(display, image);

    return textureId;
}

void EglContext::ReleaseImportedTexture(GLuint textureId, EGLImageKHR image) {
    if (textureId) {
        glDeleteTextures(1, &textureId);
    }
    if (image != EGL_NO_IMAGE_KHR && eglDestroyImageKHR) {
        eglDestroyImageKHR(display, image);
    }
}

void EglContext::WaitFence(int fenceFd) {
    if (fenceFd < 0) return;

    if (eglCreateSyncKHR && eglWaitSyncKHR && eglDestroySyncKHR) {
        EGLint attribs[] = {
            EGL_SYNC_NATIVE_FENCE_FD_ANDROID, fenceFd,
            EGL_NONE
        };

        EGLSyncKHR sync = eglCreateSyncKHR(display, EGL_SYNC_NATIVE_FENCE_ANDROID, attribs);
        if (sync != EGL_NO_SYNC_KHR) {
            // GPU-side wait — doesn't block CPU
            eglWaitSyncKHR(display, sync, 0);
            eglDestroySyncKHR(display, sync);
            // eglCreateSyncKHR takes ownership of fenceFd
            return;
        }
    }

    // Fallback: CPU-side wait
    close(fenceFd);
}

void EglContext::SetPresentationTime(int64_t timestampNs) {
    if (eglPresentationTimeANDROID && surface != EGL_NO_SURFACE) {
        eglPresentationTimeANDROID(display, surface, timestampNs);
    }
}

bool EglContext::MakeCurrent() {
    return eglMakeCurrent(display, surface, surface, context) == EGL_TRUE;
}

bool EglContext::SwapBuffers() {
    return eglSwapBuffers(display, surface) == EGL_TRUE;
}

bool EglContext::CreatePreviewSurface(ANativeWindow* window) {
    if (!window || display == EGL_NO_DISPLAY) return false;

    DestroyPreviewSurface();

    previewSurface = eglCreateWindowSurface(display, config, window, nullptr);
    if (previewSurface == EGL_NO_SURFACE) {
        LOGE("eglCreateWindowSurface (preview) failed: 0x%x", eglGetError());
        return false;
    }

    previewWindow = window;
    eglQuerySurface(display, previewSurface, EGL_WIDTH, &previewWidth);
    eglQuerySurface(display, previewSurface, EGL_HEIGHT, &previewHeight);
    LOGI("Preview surface created: %dx%d", previewWidth, previewHeight);
    return true;
}

void EglContext::DestroyPreviewSurface() {
    if (previewSurface != EGL_NO_SURFACE && display != EGL_NO_DISPLAY) {
        // Make sure preview isn't current before destroying
        eglMakeCurrent(display, surface, surface, context);
        eglDestroySurface(display, previewSurface);
        previewSurface = EGL_NO_SURFACE;
        LOGI("Preview surface destroyed");
    }
    if (previewWindow) {
        ANativeWindow_release(previewWindow);
        previewWindow = nullptr;
    }
    previewWidth = 0;
    previewHeight = 0;
}

bool EglContext::MakePreviewCurrent() {
    if (previewSurface == EGL_NO_SURFACE) return false;
    return eglMakeCurrent(display, previewSurface, previewSurface, context) == EGL_TRUE;
}

bool EglContext::MakeEncoderCurrent() {
    return eglMakeCurrent(display, surface, surface, context) == EGL_TRUE;
}

bool EglContext::SwapPreviewBuffers() {
    if (previewSurface == EGL_NO_SURFACE) return false;
    return eglSwapBuffers(display, previewSurface) == EGL_TRUE;
}

void EglContext::Release() {
    if (display != EGL_NO_DISPLAY) {
        eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
        DestroyPreviewSurface();
        if (surface != EGL_NO_SURFACE) {
            eglDestroySurface(display, surface);
            surface = EGL_NO_SURFACE;
        }
        if (context != EGL_NO_CONTEXT) {
            eglDestroyContext(display, context);
            context = EGL_NO_CONTEXT;
        }
        eglTerminate(display);
        display = EGL_NO_DISPLAY;
    }
    LOGI("EGL resources released");
}
