/*
 * Copyright 2013 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.
 */

#define LOG_TAG "ScreenRecord"
//#define LOG_NDEBUG 0
#include <utils/Log.h>

#define EGL_EGLEXT_PROTOTYPES

#include <gui/BufferQueue.h>
#include <gui/GraphicBufferAlloc.h>
#include <gui/Surface.h>

#include "EglWindow.h"

#include <EGL/egl.h>
#include <EGL/eglext.h>

#include <assert.h>

using namespace android;


status_t EglWindow::createWindow(const sp<IGraphicBufferProducer>& surface) {
    if (mEglSurface != EGL_NO_SURFACE) {
        ALOGE("surface already created");
        return UNKNOWN_ERROR;
    }
    status_t err = eglSetupContext(false);
    if (err != NO_ERROR) {
        return err;
    }

    // Cache the current dimensions.  We're not expecting these to change.
    surface->query(NATIVE_WINDOW_WIDTH, &mWidth);
    surface->query(NATIVE_WINDOW_HEIGHT, &mHeight);

    // Output side (EGL surface to draw on).
    sp<ANativeWindow> anw = new Surface(surface);
    mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig, anw.get(),
            NULL);
    if (mEglSurface == EGL_NO_SURFACE) {
        ALOGE("eglCreateWindowSurface error: %#x", eglGetError());
        eglRelease();
        return UNKNOWN_ERROR;
    }

    return NO_ERROR;
}

status_t EglWindow::createPbuffer(int width, int height) {
    if (mEglSurface != EGL_NO_SURFACE) {
        ALOGE("surface already created");
        return UNKNOWN_ERROR;
    }
    status_t err = eglSetupContext(true);
    if (err != NO_ERROR) {
        return err;
    }

    mWidth = width;
    mHeight = height;

    EGLint pbufferAttribs[] = {
            EGL_WIDTH, width,
            EGL_HEIGHT, height,
            EGL_NONE
    };
    mEglSurface = eglCreatePbufferSurface(mEglDisplay, mEglConfig, pbufferAttribs);
    if (mEglSurface == EGL_NO_SURFACE) {
        ALOGE("eglCreatePbufferSurface error: %#x", eglGetError());
        eglRelease();
        return UNKNOWN_ERROR;
    }

    return NO_ERROR;
}

status_t EglWindow::makeCurrent() const {
    if (!eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
        ALOGE("eglMakeCurrent failed: %#x", eglGetError());
        return UNKNOWN_ERROR;
    }
    return NO_ERROR;
}

status_t EglWindow::eglSetupContext(bool forPbuffer) {
    EGLBoolean result;

    mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    if (mEglDisplay == EGL_NO_DISPLAY) {
        ALOGE("eglGetDisplay failed: %#x", eglGetError());
        return UNKNOWN_ERROR;
    }

    EGLint majorVersion, minorVersion;
    result = eglInitialize(mEglDisplay, &majorVersion, &minorVersion);
    if (result != EGL_TRUE) {
        ALOGE("eglInitialize failed: %#x", eglGetError());
        return UNKNOWN_ERROR;
    }
    ALOGV("Initialized EGL v%d.%d", majorVersion, minorVersion);

    EGLint numConfigs = 0;
    EGLint windowConfigAttribs[] = {
            EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
            EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
            EGL_RECORDABLE_ANDROID, 1,
            EGL_RED_SIZE, 8,
            EGL_GREEN_SIZE, 8,
            EGL_BLUE_SIZE, 8,
            // no alpha
            EGL_NONE
    };
    EGLint pbufferConfigAttribs[] = {
            EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
            EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
            EGL_RED_SIZE, 8,
            EGL_GREEN_SIZE, 8,
            EGL_BLUE_SIZE, 8,
            EGL_ALPHA_SIZE, 8,
            EGL_NONE
    };
    result = eglChooseConfig(mEglDisplay,
            forPbuffer ? pbufferConfigAttribs : windowConfigAttribs,
            &mEglConfig, 1, &numConfigs);
    if (result != EGL_TRUE) {
        ALOGE("eglChooseConfig error: %#x", eglGetError());
        return UNKNOWN_ERROR;
    }

    EGLint contextAttribs[] = {
        EGL_CONTEXT_CLIENT_VERSION, 2,
        EGL_NONE
    };
    mEglContext = eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT,
            contextAttribs);
    if (mEglContext == EGL_NO_CONTEXT) {
        ALOGE("eglCreateContext error: %#x", eglGetError());
        return UNKNOWN_ERROR;
    }

    return NO_ERROR;
}

void EglWindow::eglRelease() {
    ALOGV("EglWindow::eglRelease");
    if (mEglDisplay != EGL_NO_DISPLAY) {
        eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
                EGL_NO_CONTEXT);

        if (mEglContext != EGL_NO_CONTEXT) {
            eglDestroyContext(mEglDisplay, mEglContext);
        }

        if (mEglSurface != EGL_NO_SURFACE) {
            eglDestroySurface(mEglDisplay, mEglSurface);
        }
    }

    mEglDisplay = EGL_NO_DISPLAY;
    mEglContext = EGL_NO_CONTEXT;
    mEglSurface = EGL_NO_SURFACE;
    mEglConfig = NULL;

    eglReleaseThread();
}

// Sets the presentation time on the current EGL buffer.
void EglWindow::presentationTime(nsecs_t whenNsec) const {
    eglPresentationTimeANDROID(mEglDisplay, mEglSurface, whenNsec);
}

// Swaps the EGL buffer.
void EglWindow::swapBuffers() const {
    eglSwapBuffers(mEglDisplay, mEglSurface);
}
