/*
 * 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 "PostWorkerGl.h"

#include "FrameBuffer.h"
#include "gl/DisplayGl.h"
#include "gl/DisplaySurfaceGl.h"
#include "host-common/GfxstreamFatalError.h"
#include "host-common/logging.h"
#include "host-common/misc.h"

namespace gfxstream {

namespace {

using emugl::ABORT_REASON_OTHER;
using emugl::FatalError;
using gl::DisplayGl;
using gl::DisplaySurfaceGl;
using gl::EmulationGl;
using gl::s_egl;

hwc_transform_t getTransformFromRotation(int rotation) {
    switch (static_cast<int>(rotation / 90)) {
        case 1:
            return HWC_TRANSFORM_ROT_270;
        case 2:
            return HWC_TRANSFORM_ROT_180;
        case 3:
            return HWC_TRANSFORM_ROT_90;
        default:
            return HWC_TRANSFORM_NONE;
    }
}

}  // namespace

PostWorkerGl::PostWorkerGl(bool mainThreadPostingOnly, FrameBuffer* fb, Compositor* compositor,
                           DisplayGl* displayGl, gl::EmulationGl* emulationGl)
    : PostWorker(mainThreadPostingOnly, fb, compositor),
      m_displayGl(displayGl),
      mEmulationGl(emulationGl) {
    if (!m_displayGl) {
        GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "PostWorker missing DisplayGl.";
    }
}

void PostWorkerGl::screenshot(ColorBuffer* cb, int screenwidth, int screenheight, GLenum format,
                              GLenum type, int skinRotation, void* pixels, Rect rect) {
    // See b/292237104.
    mFb->lock();
    cb->readToBytesScaled(screenwidth, screenheight, format, type, skinRotation, rect, pixels);
    mFb->unlock();
}

std::shared_future<void> PostWorkerGl::postImpl(ColorBuffer* cb) {
    if (!mContextBound || m_mainThreadPostingOnly) {
        // This might happen on headless mode
        // Also if posting on main thread, the context binding can get polluted easily, which
        // requires frequent rebinds.
        setupContext();
    }
    std::shared_future<void> completedFuture = std::async(std::launch::deferred, [] {}).share();
    completedFuture.wait();

    DisplayGl::Post post = {};

    ComposeLayer postLayerOptions = {
        .composeMode = HWC2_COMPOSITION_DEVICE,
        .blendMode = HWC2_BLEND_MODE_NONE,
        .alpha = 1.0f,
        .transform = HWC_TRANSFORM_NONE,
    };

    const auto& multiDisplay = emugl::get_emugl_multi_display_operations();
    const bool not_pixel_fold = !(multiDisplay.isPixelFold());
    if (not_pixel_fold && multiDisplay.isMultiDisplayEnabled()) {
        if (multiDisplay.isMultiDisplayWindow()) {
            int32_t previousDisplayId = -1;
            uint32_t currentDisplayId;
            uint32_t currentDisplayColorBufferHandle;
            while (multiDisplay.getNextMultiDisplay(previousDisplayId, &currentDisplayId,
                                                    /*x=*/nullptr,
                                                    /*y=*/nullptr,
                                                    /*w=*/nullptr,
                                                    /*h=*/nullptr,
                                                    /*dpi=*/nullptr,
                                                    /*flags=*/nullptr,
                                                    &currentDisplayColorBufferHandle)) {
                previousDisplayId = currentDisplayId;

                if (currentDisplayColorBufferHandle == 0) {
                    continue;
                }
                emugl::get_emugl_window_operations().paintMultiDisplayWindow(
                    currentDisplayId, currentDisplayColorBufferHandle);
            }
            post.layers.push_back(postWithOverlay(cb));
        } else {
            uint32_t combinedDisplayW = 0;
            uint32_t combinedDisplayH = 0;
            multiDisplay.getCombinedDisplaySize(&combinedDisplayW, &combinedDisplayH);

            post.frameWidth = combinedDisplayW;
            post.frameHeight = combinedDisplayH;

            int32_t previousDisplayId = -1;
            uint32_t currentDisplayId;
            int32_t currentDisplayOffsetX;
            int32_t currentDisplayOffsetY;
            uint32_t currentDisplayW;
            uint32_t currentDisplayH;
            uint32_t currentDisplayColorBufferHandle;
            while (multiDisplay.getNextMultiDisplay(
                previousDisplayId, &currentDisplayId, &currentDisplayOffsetX,
                &currentDisplayOffsetY, &currentDisplayW, &currentDisplayH,
                /*dpi=*/nullptr,
                /*flags=*/nullptr, &currentDisplayColorBufferHandle)) {
                previousDisplayId = currentDisplayId;

                if (currentDisplayW == 0 || currentDisplayH == 0 ||
                    (currentDisplayId != 0 && currentDisplayColorBufferHandle == 0)) {
                    continue;
                }

                ColorBuffer* currentCb =
                    currentDisplayId == 0
                        ? cb
                        : mFb->findColorBuffer(currentDisplayColorBufferHandle).get();
                if (!currentCb) {
                    continue;
                }

                const auto transform = getTransformFromRotation(mFb->getZrot());
                postLayerOptions.transform = transform;
                if ( transform == HWC_TRANSFORM_ROT_90 || transform == HWC_TRANSFORM_ROT_270) {
                    std::swap(currentDisplayW, currentDisplayH);
                }
                postLayerOptions.displayFrame = {
                    .left = static_cast<int>(currentDisplayOffsetX),
                    .top = static_cast<int>(currentDisplayOffsetY),
                    .right = static_cast<int>(currentDisplayOffsetX + currentDisplayW),
                    .bottom = static_cast<int>(currentDisplayOffsetY + currentDisplayH),
                };
                postLayerOptions.crop = {
                    .left = 0.0f,
                    .top = static_cast<float>(currentCb->getHeight()),
                    .right = static_cast<float>(currentCb->getWidth()),
                    .bottom = 0.0f,
                };

                post.layers.push_back(DisplayGl::PostLayer{
                    .colorBuffer = currentCb,
                    .layerOptions = postLayerOptions,
                });
            }
        }
    } else if (emugl::get_emugl_window_operations().isFolded()) {
        const float dpr = mFb->getDpr();

        post.frameWidth = m_viewportWidth / dpr;
        post.frameHeight = m_viewportHeight / dpr;

        int displayOffsetX;
        int displayOffsetY;
        int displayW;
        int displayH;
        emugl::get_emugl_window_operations().getFoldedArea(&displayOffsetX, &displayOffsetY,
                                                           &displayW, &displayH);

        postLayerOptions.displayFrame = {
            .left = 0,
            .top = 0,
            .right = mFb->windowWidth(),
            .bottom = mFb->windowHeight(),
        };
        postLayerOptions.crop = {
            .left = static_cast<float>(displayOffsetX),
            .top = static_cast<float>(displayOffsetY + displayH),
            .right = static_cast<float>(displayOffsetX + displayW),
            .bottom = static_cast<float>(displayOffsetY),
        };
        postLayerOptions.transform = getTransformFromRotation(mFb->getZrot());

        post.layers.push_back(DisplayGl::PostLayer{
            .colorBuffer = cb,
            .layerOptions = postLayerOptions,
        });
    } else {
        post.layers.push_back(postWithOverlay(cb));
    }
    return m_displayGl->post(post);
}

DisplayGl::PostLayer PostWorkerGl::postWithOverlay(ColorBuffer* cb) {
    float dpr = mFb->getDpr();
    int windowWidth = mFb->windowWidth();
    int windowHeight = mFb->windowHeight();
    float px = mFb->getPx();
    float py = mFb->getPy();
    int zRot = mFb->getZrot();
    hwc_transform_t rotation = (hwc_transform_t)0;

    // Find the x and y values at the origin when "fully scrolled."
    // Multiply by 2 because the texture goes from -1 to 1, not 0 to 1.
    // Multiply the windowing coordinates by DPR because they ignore
    // DPR, but the viewport includes DPR.
    float fx = 2.f * (m_viewportWidth - windowWidth * dpr) / (float)m_viewportWidth;
    float fy = 2.f * (m_viewportHeight - windowHeight * dpr) / (float)m_viewportHeight;

    // finally, compute translation values
    float dx = px * fx;
    float dy = py * fy;

    return DisplayGl::PostLayer{
        .colorBuffer = cb,
        .overlayOptions =
            DisplayGl::PostLayer::OverlayOptions{
                .rotation = static_cast<float>(zRot),
                .dx = dx,
                .dy = dy,
            },
    };
}

// Called whenever the subwindow needs a refresh (FrameBuffer::setupSubWindow).
// This rebinds the subwindow context (to account for
// when the refresh is a display change, for instance)
// and resets the posting viewport.
void PostWorkerGl::viewportImpl(int width, int height) {
    setupContext();
    const float dpr = mFb->getDpr();
    m_viewportWidth = width * dpr;
    m_viewportHeight = height * dpr;

    if (!m_displayGl) {
        GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "PostWorker missing DisplayGl.";
    }
    m_displayGl->viewport(m_viewportWidth, m_viewportHeight);
}

// Called when the subwindow refreshes, but there is no
// last posted color buffer to show to the user. Instead of
// displaying whatever happens to be in the back buffer,
// clear() is useful for outputting consistent colors.
void PostWorkerGl::clearImpl() {
    if (!mContextBound || m_mainThreadPostingOnly) {
        // This might happen on headless mode
        setupContext();
    }
    m_displayGl->clear();
}

std::shared_future<void> PostWorkerGl::composeImpl(const FlatComposeRequest& composeRequest) {
    if (!mContextBound || m_mainThreadPostingOnly) {
        // This might happen on headless mode
        setupContext();
    }
    return PostWorker::composeImpl(composeRequest);
}

void PostWorkerGl::setupContext() {
    android::base::AutoLock lock(mMutex);
    const auto* surface = getBoundSurface();
    const DisplaySurfaceGl* surfaceGl = nullptr;
    if (surface) {
        surfaceGl = static_cast<const DisplaySurfaceGl*>(surface->getImpl());
    } else {
        // Create a fake context.
        // This could happen in AEMU with -qt-hide-window. Also due to an Intel bug
        // this needs to happen on post thread.
        // b/274313125
        if (!mFakeWindowSurface) {
            mFakeWindowSurface = mEmulationGl->createFakeWindowSurface();
        }
        if (!mFakeWindowSurface) {
            ERR("Post worker does not have a window surface.");
            return;
        }
        surfaceGl = static_cast<const DisplaySurfaceGl*>(mFakeWindowSurface->getImpl());
    }

    // It will be no-op if it rebinds to the same context.
    // We need to retry just in case the surface is changed.

    // Also we could not use the scope context helper here,
    // because (1) binds and unbinds happen in very different places;
    // (2) they both need to happen in post thread, but the d'tor
    // of PostWorker can happen in a different thread.
    if (!surfaceGl->bindContext()) {
        ERR("Failed to bind to post worker context.");
        return;
    }
    mContextBound = true;
}

void PostWorkerGl::exitImpl() {
    if (!mContextBound) {
        return;
    }
    s_egl.eglMakeCurrent(s_egl.eglGetDisplay(EGL_DEFAULT_DISPLAY), nullptr, nullptr, nullptr);
    mContextBound = false;
}

}  // namespace gfxstream
