/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "render_context/render_context.h"
#include "render_context/new_render_context/render_context_gl.h"
#include "render_context/new_render_context/render_context_vk.h"

#include <MacTypes.h>
#include <__config>
#include <sstream>
#include <chrono>
#include <UIKit/UIKit.h>
#include <CoreFoundation/CoreFoundation.h>
#include "OpenGLES/EAGL.h"
#include "OpenGLES/ES2/gl.h"
#include "OpenGLES/ES2/glext.h"
#include "QuartzCore/CAEAGLLayer.h"
#include "rs_trace.h"
#include "memory/rs_tag_tracker.h"
#include "platform/common/rs_log.h"
#ifdef RS_ENABLE_GL
#include "platform/common/rs_system_properties.h"
#endif

namespace OHOS {
namespace Rosen {
std::mutex RenderContextGL::resourceContextMutex;
EGLContext RenderContextGL::resourceContext = nullptr;
constexpr int32_t EGL_CONTEXT_CLIENT_VERSION_NUM = 2;
constexpr char CHARACTER_WHITESPACE = ' ';
constexpr const char* CHARACTER_STRING_WHITESPACE = " ";
constexpr const char* EGL_KHR_SURFACELESS_CONTEXT = "EGL_KHR_surfaceless_context";

constexpr size_t DEFAULT_SKIA_CACHE_SIZE        = 96 * (1 << 20);
constexpr int DEFAULT_SKIA_CACHE_COUNT          = 2 * (1 << 12);

std::shared_ptr<RenderContext> RenderContext::Create()
{
#ifdef RS_ENABLE_VK
    if (RSSystemProperties::IsUseVulkan()) {
        return std::make_shared<RenderContextVK>();
    }
#endif
    return std::make_shared<RenderContextGL>();
}

RenderContextGL::RenderContextGL()
{
    surface_ = nullptr;
    drGPUContext_ = nullptr;
    nativeWindow_ = nullptr;
    eglDisplay_ = EGL_NO_DISPLAY;
    eglContext_ = EGL_NO_CONTEXT;
    eglSurface_ = EGL_NO_SURFACE;
    config_ = nullptr;
    mHandler_ = nullptr;
}

RenderContextGL::~RenderContextGL()
{
    EAGLContext* context = EAGLContext.currentContext;
    [EAGLContext setCurrentContext:static_cast<EAGLContext*>(eglContext_)];

    glDeleteFramebuffers(1, &framebuffer_);
    glDeleteRenderbuffers(1, &colorbuffer_);

    [EAGLContext setCurrentContext:context];
}

void RenderContextGL::CreatePbufferSurface()
{
}

const EGLContext RenderContextGL::GetResourceContext()
{
    std::lock_guard<std::mutex> lock(RenderContextGL::resourceContextMutex);
    if (!RenderContextGL::resourceContext) {
        RenderContextGL::resourceContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
    }
    if (!RenderContextGL::resourceContext) {
        RenderContextGL::resourceContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    }
    return RenderContextGL::resourceContext;
}

bool RenderContextGL::Init()
{   
    if (IsEglContextReady()) {
         return true;
    }
    eglContext_ = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3
            sharegroup:static_cast<EAGLContext*>(GetResourceContext()).sharegroup];
    if (eglContext_ == nullptr) {
        eglContext_ = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2
            sharegroup:static_cast<EAGLContext*>(GetResourceContext()).sharegroup];
    }

    if (eglContext_ == nullptr) {
        ROSEN_LOGE("eglContext_ is null");
        return false;
    }
    color_space_ = Drawing::ColorSpace::CreateSRGB();
    if (@available(iOS 10, *)) {
        UIDisplayGamut displayGamut = [UIScreen mainScreen].traitCollection.displayGamut;
        switch (displayGamut) {
            case UIDisplayGamutP3:
            color_space_ = Drawing::ColorSpace::CreateRGB(Drawing::CMSTransferFuncType::SRGB,
                Drawing::CMSMatrixType::DCIP3);
                break;
            default:
                break;
        }
    }

    if (valid_) {
        return false;
    }
    
    bool isContentCurrent = [EAGLContext setCurrentContext:static_cast<EAGLContext*>(eglContext_)];
    if (!isContentCurrent){
        ROSEN_LOGE("Failed to set current OpenGL context");
        return false;
    }

    glGenFramebuffers(1, &framebuffer_);
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer_);
    glGenRenderbuffers(1, &colorbuffer_);
    glBindRenderbuffer(GL_RENDERBUFFER, colorbuffer_);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorbuffer_);
    valid_ = true;
    return true;
}

void RenderContextGL::MakeCurrent(EGLSurface surface, EGLContext context)
{
    [EAGLContext setCurrentContext:static_cast<EAGLContext*>(eglContext_)];
    CAEAGLLayer* layer = static_cast<CAEAGLLayer*>(surface);
    const CGSize layer_size = [layer bounds].size;
    const CGFloat contents_scale = layer.contentsScale;
    const GLint size_width = layer_size.width * contents_scale;
    const GLint size_height = layer_size.height * contents_scale;

    if (layer == layer_ && size_width == storage_width_ && size_height == storage_height_) {
        // Nothing to since the stoage size is already consistent with the layer.
        return;
    }
    ROSEN_LOGD("renderbufferStorage");
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer_);
    glBindRenderbuffer(GL_RENDERBUFFER, colorbuffer_);
    __block bool res = false;
    if (@available(iOS 18, *)) {
        dispatch_sync(dispatch_get_main_queue(), ^{
            res = [eglContext_ renderbufferStorage:GL_RENDERBUFFER fromDrawable:static_cast<CAEAGLLayer *>(layer)];
        });
    } else {
       res = [eglContext_ renderbufferStorage:GL_RENDERBUFFER fromDrawable:static_cast<CAEAGLLayer *>(layer)];
    }
    if (!res) {
        ROSEN_LOGE("eglContext_ renderbufferStorage:GL_RENDERBUFFER Failed");
        storage_width_ = 0;
        storage_height_ = 0;
        [static_cast<CAEAGLLayer *>(layer_) release];
        layer_ = nullptr;
        return;
    }
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &storage_width_);
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &storage_height_);
    if (layer_ != layer) {
        [static_cast<CAEAGLLayer *>(layer_) release];
        layer_ = [layer retain];
    }
}

bool RenderContextGL::UpdateStorageSizeIfNecessary()
{

    return true;
}

bool RenderContextGL::ResourceMakeCurrent()
{
    return [EAGLContext setCurrentContext:static_cast<EAGLContext*>(resourceContext)];
}

void RenderContextGL::CreateShareContext()
{
    return;
}

bool RenderContextGL::SwapBuffers(EGLSurface surface) const
{
    const GLenum discards[] = {
        GL_DEPTH_ATTACHMENT,
        GL_STENCIL_ATTACHMENT,
    };

    glDiscardFramebufferEXT(GL_FRAMEBUFFER, sizeof(discards) / sizeof(GLenum), discards);
    glBindRenderbuffer(GL_RENDERBUFFER, colorbuffer_);
    [[EAGLContext currentContext] presentRenderbuffer:GL_RENDERBUFFER];
    [CATransaction flush];
    return true;
}

void RenderContextGL::AddSurface()
{
    int32_t count = surface_count_.fetch_add(1, std::memory_order_relaxed) + 1;
    ROSEN_LOGD("AddSurface: surface_count_ = %{public}d", count);
}

void RenderContextGL::DeleteSurface()
{
    int32_t count = surface_count_.fetch_sub(1, std::memory_order_acq_rel) - 1;
    ROSEN_LOGI("DeleteSurface: surface_count_ = %{public}d", count);

    if (cleanUpHelper_ != nullptr && count <= 0) {
        ROSEN_LOGI("DeleteSurface: All surfaces destroyed, calling cleanUpHelper_");
        cleanUpHelper_();
    }
}

void RenderContextGL::SetCleanUpHelper(std::function<void()> func)
{
    cleanUpHelper_ = func;
}

void RenderContextGL::DestroyEGLSurface(EGLSurface surface)
{
    if (layer_ != nullptr) {
        [static_cast<CAEAGLLayer*>(layer_) release];
        layer_ = nullptr;
    }
    ROSEN_LOGD("DestroyEGLSurface");
    storage_width_ = 0;
    storage_height_ = 0;
    DeleteSurface();
}

/* 
 *  Destroy the shared source when all rssurface are destroyed, this must be called on the render thread, 
 *  otherwise it will cause memory leak,iosurface will not be released.
 */
void RenderContextGL::DestroySharedSource()
{
    int32_t count = surface_count_.load(std::memory_order_acquire);
    ROSEN_LOGI("DestroySharedSource surface_count_ %{public}d", count);

    if (count > 0) {
        return;
    }

    if (drGPUContext_ != nullptr) {
        ROSEN_LOGD("DestroySharedSource: Cleaning GPUContext resources to release ioaccelerator");
        drGPUContext_->PurgeUnlockedResources(false);
        drGPUContext_->FreeGpuResources();
        drGPUContext_->PerformDeferredCleanup(std::chrono::milliseconds(0));
        drGPUContext_ = nullptr;
    }
    surface_ = nullptr;

    if (eglContext_ != nullptr) {
        ROSEN_LOGD("DestroySharedSource eglContext_ is not nullptr");
        [EAGLContext setCurrentContext:static_cast<EAGLContext*>(eglContext_)];

        if (colorbuffer_ != 0) {
            glDeleteRenderbuffers(1, &colorbuffer_);
            colorbuffer_ = 0;
        }
        if (framebuffer_ != 0) {
            glDeleteFramebuffers(1, &framebuffer_);
            framebuffer_ = 0;
        }
        [EAGLContext setCurrentContext:nil];
        glFinish();
        [static_cast<EAGLContext*>(eglContext_) release];
        eglContext_ = nullptr;
    } else {
        ROSEN_LOGD("DestroySharedSource eglContext_ is nullptr");
    }
    
    storage_width_ = 0;
    storage_height_ = 0;
    valid_ = false;
}

EGLSurface RenderContextGL::CreateEGLSurface(EGLNativeWindowType eglNativeWindow)
{
    CAEAGLLayer* layer = static_cast<CAEAGLLayer*>(eglNativeWindow);
    const CGSize layer_size = [layer bounds].size;
    const CGFloat contents_scale = layer.contentsScale;
    const GLint size_width = layer_size.width * contents_scale;
    const GLint size_height = layer_size.height * contents_scale;

    if (size_width == 0 || size_height == 0) {
        return nullptr;
    }
    return eglNativeWindow;
}

bool RenderContextGL::SetUpGpuContext(std::shared_ptr<Drawing::GPUContext> drawingContext)
{
    if (drGPUContext_ != nullptr) {
        ROSEN_LOGD("Drawing GPUContext has already created");
        return true;
    }
    Drawing::GPUContextOptions options;
    auto drGPUContext = std::make_shared<Drawing::GPUContext>();
    if (!drGPUContext->BuildFromGL(options)) {
        ROSEN_LOGE("SetUpGpuContext drGPUContext is null");
        return false;
    }
    int maxResources = 0;
    size_t maxResourcesSize = 0;
    drGPUContext->GetResourceCacheLimits(&maxResources, &maxResourcesSize);
    if (maxResourcesSize > 0) {
        int cacheLimitsTimes = 2;
        drGPUContext->SetResourceCacheLimits(cacheLimitsTimes * maxResources, cacheLimitsTimes *
            std::fmin(maxResourcesSize, DEFAULT_SKIA_CACHE_SIZE));
    } else {
        drGPUContext->SetResourceCacheLimits(DEFAULT_SKIA_CACHE_COUNT, DEFAULT_SKIA_CACHE_SIZE);
    }
    drGPUContext_ = std::move(drGPUContext);
    return true;
}

std::shared_ptr<Drawing::Surface> RenderContextGL::AcquireSurface(int width, int height)
{
    if (!SetUpGpuContext()) {
        ROSEN_LOGE("GPUContext is not ready!!!");
        return nullptr;
    }
    std::shared_ptr<Drawing::ColorSpace> colorSpace = nullptr;
    struct Drawing::FrameBuffer bufferInfo;
    bufferInfo.width = width;
    bufferInfo.height = height;
    bufferInfo.FBOID = framebuffer_;
    bufferInfo.Format = 0x8058;
    bufferInfo.gpuContext = drGPUContext_;
    bufferInfo.colorType = Drawing::COLORTYPE_RGBA_8888;
    switch (colorSpace_) {
        // [planning] in order to stay consistant with the colorspace used before, we disabled
        // COLOR_GAMUT_SRGB to let the branch to default, then skColorSpace is set to nullptr
        case GRAPHIC_COLOR_GAMUT_DISPLAY_P3:
        case GRAPHIC_COLOR_GAMUT_DCI_P3:
            colorSpace = Drawing::ColorSpace::CreateRGB(Drawing::CMSTransferFuncType::SRGB,
                Drawing::CMSMatrixType::DCIP3);
            bufferInfo.Format = 0x881A;
            bufferInfo.colorType = Drawing::COLORTYPE_RGBA_F16;
            break;
        default:
            break;
    }
    bufferInfo.colorSpace = colorSpace;
    surface_ = std::make_shared<Drawing::Surface>();
    if (!surface_->Bind(bufferInfo)) {
        ROSEN_LOGE("Drawing::Surface surface_ is nullptr");
        surface_ = nullptr;
        return nullptr;
    }
    return surface_;
}

void RenderContextGL::RenderFrame()
{
    RS_TRACE_FUNC();
    // flush commands
    if (surface_ != nullptr && surface_->GetCanvas() != nullptr) {
        surface_->GetCanvas()->Flush();
    } else {
        ROSEN_LOGE("canvas is nullptr!!!");
    }
}

int32_t RenderContextGL::QueryEglBufferAge()
{
    return static_cast<int32_t>(EGL_UNKNOWN);
}

void RenderContextGL::DamageFrame(const std::vector<RectI> &rects)
{
}

void RenderContextGL::ClearRedundantResources()
{
}

std::string RenderContextGL::GetShaderCacheSize() const
{
    return "";
}

std::string RenderContextGL::CleanAllShaderCache() const
{
    return "";
}

bool RenderContextGL::AbandonContext()
{
    return true;
}

void RenderContextGL::DestroyShareContext()
{
}

bool RenderContextGL::QueryMaxGpuBufferSize(uint32_t& maxWidth, uint32_t& maxHeight)
{
    ROSEN_LOGI("RenderContextGL::QueryMaxGpuBufferSize: using OpenGL backend");
    GLint maxTextureSize = 0;
    GLint maxRenderBufferSize = 0;

    glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
    glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &maxRenderBufferSize);

    if (maxTextureSize <= 0 || maxRenderBufferSize <= 0) {
        ROSEN_LOGE("RenderContextGL::QueryMaxGpuBufferSize: failed to get GPU buffer size");
        return false;
    }

    maxWidth = static_cast<uint32_t>(std::min(maxTextureSize, maxRenderBufferSize));
    maxHeight = static_cast<uint32_t>(std::min(maxTextureSize, maxRenderBufferSize));

    ROSEN_LOGI("RenderContextGL::QueryMaxGpuBufferSize: GL_MAX_TEXTURE_SIZE = %{public}d,"
        "GL_MAX_RENDERBUFFER_SIZE = %{public}d, result = %{public}u",
        maxTextureSize, maxRenderBufferSize, maxWidth);
    return true;
}
} // namespace Rosen
} // namespace OHOS