/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * 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 <vector>
#include <math.h>

#define GL_GLEXT_PROTOTYPES
#define EGL_EGLEXT_PROTOTYPES
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#undef EGL_EGLEXT_PROTOTYPES
#undef GL_GLEXT_PROTOTYPES

#include "abc3d.h"
#include "debug.h"

namespace android {
namespace hardware {
namespace camera {
namespace provider {
namespace implementation {
namespace abc3d {
namespace {
constexpr char kTag[] = "abc3d";

float dot3(const float a3[], const float b3[]) {
    return a3[0] * b3[0] + a3[1] * b3[1] + a3[2] * b3[2];
}

/*
 * https://registry.khronos.org/OpenGL-Refpages/gl2.1/xhtml/gluLookAt.xml
 * https://registry.khronos.org/OpenGL-Refpages/gl2.1/xhtml/glTranslate.xml
 * This function takes `m44` (where zzz (assumed zero) and ooo (assumed one)
 * are ignored) and multiplies it by a translation matrix.
 *
 *           m44             translate
 *  [  s0  s1  s2 zzz ]   [ 1 0 0 -eyeX ]   [  s0  s1  s2 -dot3(m44[0:2],  eye3) ]
 *  [ up0 up1 up2 zzz ] * [ 0 1 0 -eyeY ] = [ up0 up1 up2 -dot3(m44[4:6],  eye3) ]
 *  [  b0  b1  b2 zzz ]   [ 0 0 1 -eyeZ ]   [  b0  b1  b2 -dot3(m44[8:10], eye3) ]
 *  [ zzz zzz zzz ooo ]   [ 0 0 0     1 ]   [   0   0   0                      1 ]
*/
void lookAtEyeCoordinates(float m44[], const float eye3[]) {
    m44[3]  = -dot3(&m44[0], eye3);
    m44[7]  = -dot3(&m44[4], eye3);
    m44[11] = -dot3(&m44[8], eye3);
    m44[12] = 0;
    m44[13] = 0;
    m44[14] = 0;
    m44[15] = 1;
}
}  // namespace

#define RETURN_CTOR_FAILED(S) \
    ALOGE("%s:%s:%d %s failed", kTag, __func__, __LINE__, S); return;

AutoImageKHR::AutoImageKHR(const EGLDisplay display, const EGLClientBuffer clientBuf)
        : mEglDisplay(display) {
    static const EGLint imageAttrs[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE};
    mEglImage = eglCreateImageKHR(
        display, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, clientBuf, imageAttrs);
    if (mEglImage == EGL_NO_IMAGE_KHR) {
        RETURN_CTOR_FAILED("eglCreateImageKHR");
    }
}

AutoImageKHR::AutoImageKHR(AutoImageKHR&& rhs) noexcept
        : mEglDisplay(rhs.mEglDisplay)
        , mEglImage(std::exchange(rhs.mEglImage, EGL_NO_IMAGE_KHR)) {}

AutoImageKHR& AutoImageKHR::operator=(AutoImageKHR&& rhs) noexcept {
    if (this != &rhs) {
        mEglDisplay = rhs.mEglDisplay;
        mEglImage = std::exchange(rhs.mEglImage, EGL_NO_IMAGE_KHR);
    }
    return *this;
}

AutoImageKHR::~AutoImageKHR() {
    if (mEglImage != EGL_NO_IMAGE_KHR) {
        eglDestroyImageKHR(mEglDisplay, mEglImage);
    }
}

EglCurrentContext::EglCurrentContext(const EGLDisplay display)
        : mEglDisplay(display) {}

EglCurrentContext::EglCurrentContext(EglCurrentContext&& rhs) noexcept
        : mEglDisplay(std::exchange(rhs.mEglDisplay, EGL_NO_DISPLAY)) {}

EglCurrentContext& EglCurrentContext::operator=(EglCurrentContext&& rhs) noexcept {
    if (this != &rhs) {
        mEglDisplay = std::exchange(rhs.mEglDisplay, EGL_NO_DISPLAY);
    }
    return *this;
}

EglCurrentContext::~EglCurrentContext() {
    if (mEglDisplay != EGL_NO_DISPLAY) {
        LOG_ALWAYS_FATAL_IF(!eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE,
                                            EGL_NO_SURFACE, EGL_NO_CONTEXT));
    }
}

EglContext::EglContext(EglContext&& rhs) noexcept
        : mEglDisplay(std::exchange(rhs.mEglDisplay, EGL_NO_DISPLAY))
        , mEglContext(std::exchange(rhs.mEglContext, EGL_NO_CONTEXT))
        , mEglSurface(std::exchange(rhs.mEglSurface, EGL_NO_SURFACE)) {}

EglContext& EglContext::operator=(EglContext&& rhs) noexcept {
    if (this != &rhs) {
        mEglDisplay = std::exchange(rhs.mEglDisplay, EGL_NO_DISPLAY);
        mEglContext = std::exchange(rhs.mEglContext, EGL_NO_CONTEXT);
        mEglSurface = std::exchange(rhs.mEglSurface, EGL_NO_SURFACE);
    }
    return *this;
}

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

void EglContext::clear() {
    if (mEglSurface != EGL_NO_SURFACE) {
        eglDestroySurface(mEglDisplay, mEglSurface);
        mEglSurface = EGL_NO_SURFACE;
    }
    if (mEglContext != EGL_NO_CONTEXT) {
        eglDestroyContext(mEglDisplay, mEglContext);
        mEglContext = EGL_NO_CONTEXT;
    }
    if (mEglDisplay != EGL_NO_DISPLAY) {
        eglTerminate(mEglDisplay);
        mEglDisplay = EGL_NO_DISPLAY;
    }
}

EglCurrentContext EglContext::init() {
    if (mEglContext != EGL_NO_CONTEXT) {
        LOG_ALWAYS_FATAL_IF(!eglMakeCurrent(mEglDisplay, mEglSurface,
                                            mEglSurface, mEglContext));
        return EglCurrentContext(mEglDisplay);
    }

    const EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    if (display == EGL_NO_DISPLAY) {
        return EglCurrentContext(FAILURE(EGL_NO_DISPLAY));
    }

    EGLint major, minor;
    if (!eglInitialize(display, &major, &minor)) {
        return EglCurrentContext(FAILURE(EGL_NO_DISPLAY));
    }
    ALOGD("%s:%d: Initialized EGL, version %d.%d", __func__, __LINE__,
          static_cast<int>(major), static_cast<int>(minor));

    static const EGLint configAttrs[] = {
        EGL_RENDERABLE_TYPE,    EGL_OPENGL_ES2_BIT,
        EGL_CONFIG_CAVEAT,      EGL_NONE,
        EGL_RED_SIZE,           8,
        EGL_GREEN_SIZE,         8,
        EGL_BLUE_SIZE,          8,
        EGL_ALPHA_SIZE,         8,
        EGL_NONE
    };

    EGLint numConfigs = 1;
    EGLConfig config = EGL_NO_CONFIG_KHR;
    if (!eglChooseConfig(display, configAttrs, &config, 1, &numConfigs) ||
            (config == EGL_NO_CONFIG_KHR) || (numConfigs != 1)) {
        eglTerminate(display);
        return EglCurrentContext(FAILURE(EGL_NO_DISPLAY));
    }

    static const EGLint contextAttrs[] = {
        EGL_CONTEXT_CLIENT_VERSION, 2,
        EGL_NONE
    };
    const EGLContext context = eglCreateContext(display, config,
                                                EGL_NO_CONTEXT, contextAttrs);
    if (context == EGL_NO_CONTEXT) {
        eglTerminate(display);
        return EglCurrentContext(FAILURE(EGL_NO_DISPLAY));
    }

    EGLSurface surface = EGL_NO_SURFACE;
    if (!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, context)) {
        // EGL_KHR_surfaceless_context is not supported
        const EGLint surfaceAttrs[] = {
            EGL_WIDTH,  1,
            EGL_HEIGHT, 1,
            EGL_NONE
        };
        surface = eglCreatePbufferSurface(display, config, surfaceAttrs);
        if (surface == EGL_NO_SURFACE) {
            eglDestroyContext(display, context);
            eglTerminate(display);
            return EglCurrentContext(FAILURE(EGL_NO_DISPLAY));
        }

        if (!eglMakeCurrent(display, surface, surface, context)) {
            eglDestroySurface(display, surface);
            eglDestroyContext(display, context);
            eglTerminate(display);
            return EglCurrentContext(FAILURE(EGL_NO_DISPLAY));
        }
    }

    mEglDisplay = display;
    mEglContext = context;
    mEglSurface = surface;

    return EglCurrentContext(display);
}

EglCurrentContext EglContext::getCurrentContext() {
    if (mEglContext == EGL_NO_CONTEXT) {
        return EglCurrentContext(EGL_NO_DISPLAY);
    } else if (eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
        return EglCurrentContext(mEglDisplay);
    } else {
        return EglCurrentContext(FAILURE(EGL_NO_DISPLAY));
    }
}

AutoTexture::AutoTexture(const GLenum target) {
    glGenTextures(1, &mTex);
    if (mTex) {
        glBindTexture(target, mTex);
    } else {
        RETURN_CTOR_FAILED("glGenTextures");
    }
}

AutoTexture::AutoTexture(const GLenum target,
                         const GLint internalformat,
                         const GLsizei width,
                         const GLsizei height,
                         const GLenum format,
                         const GLenum type,
                         const void* data) {
    glGenTextures(1, &mTex);
    if (mTex) {
        glBindTexture(target, mTex);
        glTexImage2D(target, 0, internalformat, width, height, 0, format, type, data);
    } else {
        RETURN_CTOR_FAILED("glGenTextures");
    }
}

AutoTexture::AutoTexture(AutoTexture&& rhs) noexcept
        : mTex(std::exchange(rhs.mTex, 0)) {}

AutoTexture& AutoTexture::operator=(AutoTexture&& rhs) noexcept {
    if (this != &rhs) {
        mTex = std::exchange(rhs.mTex, 0);
    }
    return *this;
}

AutoTexture::~AutoTexture() {
    clear();
}

void AutoTexture::clear() {
    if (mTex) {
        glDeleteTextures(1, &mTex);
        mTex = 0;
    }
}

AutoFrameBuffer::AutoFrameBuffer() {
    glGenFramebuffers(1, &mFBO);
    if (mFBO) {
        glBindFramebuffer(GL_FRAMEBUFFER, mFBO);
    } else {
        RETURN_CTOR_FAILED("glGenFramebuffers");
    }
}

AutoFrameBuffer::~AutoFrameBuffer() {
    if (mFBO) {
        glDeleteFramebuffers(1, &mFBO);
    }
}

AutoShader::AutoShader(AutoShader&& rhs) noexcept
        : mShader(std::exchange(rhs.mShader, 0)) {}

AutoShader& AutoShader::operator=(AutoShader&& rhs) noexcept {
    if (this != &rhs) {
        mShader = std::exchange(rhs.mShader, 0);
    }
    return *this;
}

AutoShader::~AutoShader() {
    if (mShader) {
        glDeleteShader(mShader);
    }
}

GLuint AutoShader::compile(const GLenum type, const char* text) {
    const GLuint shader = glCreateShader(type);
    if (!shader) {
        return FAILURE(0);
    }

    glShaderSource(shader, 1, &text, nullptr);
    glCompileShader(shader);
    GLint compiled = 0;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
    if (!compiled) {
        GLint infoLen = 0;
        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
        if(infoLen > 1) {
            std::vector<char> msg(infoLen + 1);
            glGetShaderInfoLog(shader, infoLen, nullptr, msg.data());
            msg[infoLen] = 0;
            ALOGE("%s:%d: error compiling shader '%s' (type=%d): '%s'",
                  __func__, __LINE__, text, type, msg.data());
        }
        glDeleteShader(shader);
        return FAILURE(0);
    }

    if (mShader) {
        glDeleteShader(mShader);
    }

    mShader = shader;
    return shader;
}

AutoProgram::AutoProgram(AutoProgram&& rhs) noexcept
        : mProgram(std::exchange(rhs.mProgram, 0)) {}

AutoProgram& AutoProgram::operator=(AutoProgram&& rhs) noexcept {
    if (this != &rhs) {
        mProgram = std::exchange(rhs.mProgram, 0);
    }
    return *this;
}

AutoProgram::~AutoProgram() {
    clear();
}

void AutoProgram::clear() {
    if (mProgram) {
        glDeleteProgram(mProgram);
        mProgram = 0;
    }
}

bool AutoProgram::link(const GLuint vertexShader,
                       const GLuint fragmentShader) {
    const GLuint program = glCreateProgram();
    if (!program) {
        return FAILURE(false);
    }

    glAttachShader(program, vertexShader);
    glAttachShader(program, fragmentShader);
    glLinkProgram(program);

    GLint linked = 0;
    glGetProgramiv(program, GL_LINK_STATUS, &linked);
    if (!linked) {
        GLint infoLen = 0;
        glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLen);
        if(infoLen > 1) {
            std::vector<char> msg(infoLen + 1);
            glGetProgramInfoLog(program, infoLen, nullptr, msg.data());
            msg[infoLen] = 0;
            ALOGE("%s:%d: error linking shaders: '%s'",
                  __func__, __LINE__, msg.data());
        }

        glDeleteProgram(program);
        return FAILURE(false);
    }

    if (mProgram) {
        glDeleteProgram(mProgram);
    }

    mProgram = program;
    return true;
}

GLint AutoProgram::getAttribLocation(const char* name) const {
    if (mProgram > 0) {
        const GLint result = glGetAttribLocation(mProgram, name);
        return (result >= 0) ? result : FAILURE(-1);
    } else {
        return FAILURE(-1);
    }
}

GLint AutoProgram::getUniformLocation(const char* name) const {
    if (mProgram > 0) {
        const GLint result = glGetUniformLocation(mProgram, name);
        return (result >= 0) ? result : FAILURE(-1);
    } else {
        return FAILURE(-1);
    }
}

// https://registry.khronos.org/OpenGL-Refpages/gl2.1/xhtml/glFrustum.xml
void frustum(float m44[],
             const double left, const double right,
             const double bottom, const double top,
             const double near, const double far) {
    const double invWidth = 1.0 / (right - left);
    const double invHeight = 1.0 / (top - bottom);
    const double invDepth = 1.0 / (far - near);
    const double near2 = 2 * near;

    m44[0] = near2 * invWidth;
    m44[1] = 0;
    m44[2] = (right + left) * invWidth;
    m44[3] = 0;

    m44[4] = 0;
    m44[5] = near2 * invHeight;
    m44[6] = (top + bottom) * invHeight;
    m44[7] = 0;

    m44[8] = 0;
    m44[9] = 0;
    m44[10] = -(far + near) * invDepth;
    m44[11] = -far * near2 * invDepth;

    m44[12] = 0;
    m44[13] = 0;
    m44[14] = -1;
    m44[15] = 0;
}

/*
 * https://registry.khronos.org/OpenGL-Refpages/gl2.1/xhtml/gluLookAt.xml
 * https://en.wikipedia.org/wiki/Rotation_matrix#Basic_rotations
 *
 * Here we calculate {Side, Up, Backwards} from Euler angles in the XYZ order:
 *
 * [ 1,    0,     0 ]   [  cosY, 0, sinY ]   [ cosZ, -sinZ, 0 ]   [ sx, ux, bx ]
 * [ 0, cosX, -sinX ] * [     0, 1,    0 ] * [ sinZ,  cosZ, 0 ] = [ sy, uy, by ]
 * [ 0, sinX,  cosX ]   [ -sinY, 0, cosY ]   [    0,     0, 1 ]   [ sz, uz, bz ]
 *
 * We calculate `backwards` because the camera looks into the negative Z
 * direction, so instead of calculating camera's forward and negating it twice,
 * let's call it `backwards`.
 *
 * After multiplying the first two:
 * [         cosY,    0,         sinY ]
 * [  sinX * sinY, cosX, -sinX * cosY ]
 * [ -cosX * sinY, sinX,  cosX * cosY ]
 *
 * The final result:
 * [                       cosY * cosZ,                      -cosY * sinZ,         sinY ]
 * [  sinX * sinY * cosZ + cosX * sinZ, -sinX * sinY * sinZ + cosX * cosZ, -sinX * cosY ]
 * [ -cosX * sinY * cosZ + sinX * sinZ,  cosX * sinY * sinZ + sinX * cosZ,  cosX * cosY ]
 *
 * {Side, Up, Backwards} are the columns in the matrix above.
 */
void lookAtXyzRot(float m44[], const float eye3[], const float rot3[]) {
    const double sinX = sin(rot3[0]);
    const double cosX = cos(rot3[0]);
    const double sinY = sin(rot3[1]);
    const double cosY = cos(rot3[1]);
    const double sinZ = sin(rot3[2]);
    const double cosZ = cos(rot3[2]);

    m44[0]  = cosY * cosZ;
    m44[1]  = sinX * sinY * cosZ + cosX * sinZ;
    m44[2]  = -cosX * sinY * cosZ + sinX * sinZ;
    m44[4]  = -cosY * sinZ;
    m44[5]  = -sinX * sinY * sinZ + cosX * cosZ;
    m44[6]  = cosX * sinY * sinZ + sinX * cosZ;
    m44[8]  = sinY;
    m44[9]  = -sinX * cosY;
    m44[10] = cosX * cosY;
    lookAtEyeCoordinates(m44, eye3);
}

void mulM44(float m44[], const float lhs44[], const float rhs44[]) {
    m44[0] = lhs44[0] * rhs44[0] + lhs44[1] * rhs44[4] + lhs44[2] * rhs44[8] + lhs44[3] * rhs44[12];
    m44[1] = lhs44[0] * rhs44[1] + lhs44[1] * rhs44[5] + lhs44[2] * rhs44[9] + lhs44[3] * rhs44[13];
    m44[2] = lhs44[0] * rhs44[2] + lhs44[1] * rhs44[6] + lhs44[2] * rhs44[10] + lhs44[3] * rhs44[14];
    m44[3] = lhs44[0] * rhs44[3] + lhs44[1] * rhs44[7] + lhs44[2] * rhs44[11] + lhs44[3] * rhs44[15];

    m44[4] = lhs44[4] * rhs44[0] + lhs44[5] * rhs44[4] + lhs44[6] * rhs44[8] + lhs44[7] * rhs44[12];
    m44[5] = lhs44[4] * rhs44[1] + lhs44[5] * rhs44[5] + lhs44[6] * rhs44[9] + lhs44[7] * rhs44[13];
    m44[6] = lhs44[4] * rhs44[2] + lhs44[5] * rhs44[6] + lhs44[6] * rhs44[10] + lhs44[7] * rhs44[14];
    m44[7] = lhs44[4] * rhs44[3] + lhs44[5] * rhs44[7] + lhs44[6] * rhs44[11] + lhs44[7] * rhs44[15];

    m44[8] = lhs44[8] * rhs44[0] + lhs44[9] * rhs44[4] + lhs44[10] * rhs44[8] + lhs44[11] * rhs44[12];
    m44[9] = lhs44[8] * rhs44[1] + lhs44[9] * rhs44[5] + lhs44[10] * rhs44[9] + lhs44[11] * rhs44[13];
    m44[10] = lhs44[8] * rhs44[2] + lhs44[9] * rhs44[6] + lhs44[10] * rhs44[10] + lhs44[11] * rhs44[14];
    m44[11] = lhs44[8] * rhs44[3] + lhs44[9] * rhs44[7] + lhs44[10] * rhs44[11] + lhs44[11] * rhs44[15];

    m44[12] = lhs44[12] * rhs44[0] + lhs44[13] * rhs44[4] + lhs44[14] * rhs44[8] + lhs44[15] * rhs44[12];
    m44[13] = lhs44[12] * rhs44[1] + lhs44[13] * rhs44[5] + lhs44[14] * rhs44[9] + lhs44[15] * rhs44[13];
    m44[14] = lhs44[12] * rhs44[2] + lhs44[13] * rhs44[6] + lhs44[14] * rhs44[10] + lhs44[15] * rhs44[14];
    m44[15] = lhs44[12] * rhs44[3] + lhs44[13] * rhs44[7] + lhs44[14] * rhs44[11] + lhs44[15] * rhs44[15];
}

}  // namespace abc3d
}  // namespace implementation
}  // namespace provider
}  // namespace camera
}  // namespace hardware
}  // namespace android