/*
 * Copyright (C) 2022 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 "CarDisplayProxy.h"

#include <aidlcommonsupport/NativeHandle.h>
#include <android-base/logging.h>
#include <android-base/scopeguard.h>
#include <gui/ISurfaceComposer.h>
#include <gui/SurfaceComposerClient.h>
#include <gui/bufferqueue/2.0/B2HGraphicBufferProducer.h>
#include <gui/view/Surface.h>
#include <ui/Rotation.h>

#include <cinttypes>

namespace {

using ::aidl::android::frameworks::automotive::display::DisplayDesc;
using ::aidl::android::frameworks::automotive::display::Rotation;
using ::aidl::android::hardware::common::NativeHandle;
using ::android::SurfaceComposerClient;
using ::ndk::ScopedAStatus;

// We're using the highest Z-order.
constexpr int32_t kSurfaceZOrder = 0x7FFFFFFF;

Rotation convert(::android::ui::Rotation uiRotation) {
    switch (uiRotation) {
        case ::android::ui::ROTATION_0:
            return Rotation::ROTATION_0;
        case ::android::ui::ROTATION_90:
            return Rotation::ROTATION_90;
        case ::android::ui::ROTATION_180:
            return Rotation::ROTATION_180;
        case ::android::ui::ROTATION_270:
            return Rotation::ROTATION_270;
    }
}

constexpr size_t kMaxWindowSize = 256;

native_handle_t* convertHalTokenToNativeHandle(const ::android::HalToken& halToken) {
    // Attempts to store halToken in the ints of the native_handle_t after its
    // size.
    auto nhDataByteSize = halToken.size();
    if (nhDataByteSize > kMaxWindowSize) {
        return nullptr;
    }

    auto numInts = ceil(nhDataByteSize / sizeof(int)) + 1;
    native_handle_t* nh = native_handle_create(/* numFds = */ 0, numInts);
    if (nh == nullptr) {
        return nullptr;
    }

    // Stores the size of the token in the first int
    nh->data[0] = nhDataByteSize;
    memcpy(&(nh->data[1]), halToken.data(), nhDataByteSize);
    return nh;
}

}  // namespace

namespace aidl::android::frameworks::automotive::display::implementation {

ScopedAStatus CarDisplayProxy::getDisplayIdList(std::vector<int64_t>* _aidl_return) {
    std::vector<::android::PhysicalDisplayId> displayIds =
            SurfaceComposerClient::getPhysicalDisplayIds();
    std::for_each(displayIds.begin(), displayIds.end(),
                  [_aidl_return](const ::android::PhysicalDisplayId& id) {
                      _aidl_return->push_back(id.value);
                  });

    return ScopedAStatus::ok();
}

ScopedAStatus CarDisplayProxy::getDisplayInfo(int64_t id, DisplayDesc* _aidl_return) {
    ::android::ui::DisplayMode displayMode;
    ::android::ui::DisplayState displayState;
    if (!getDisplayInfoFromSurfaceComposerClient(id, &displayMode, &displayState)) {
        LOG(ERROR) << "Invalid display id = " << id;
        return ScopedAStatus::fromStatus(STATUS_BAD_VALUE);
    }

    DisplayDesc desc = {
            .width = displayMode.resolution.width,
            .height = displayMode.resolution.height,
            .layer = displayState.layerStack.id,
            .orientation = convert(displayState.orientation),
    };
    *_aidl_return = std::move(desc);
    return ScopedAStatus::ok();
}

::android::sp<::android::IBinder> CarDisplayProxy::getDisplayInfoFromSurfaceComposerClient(
        int64_t id, ::android::ui::DisplayMode* displayMode,
        ::android::ui::DisplayState* displayState) {
    ::android::sp<::android::IBinder> displayToken;
    std::optional<::android::PhysicalDisplayId> displayId =
            ::android::DisplayId::fromValue<::android::PhysicalDisplayId>(id);
    if (!displayId) {
        LOG(ERROR) << "Failed to get a valid display name";
        return nullptr;
    }

    displayToken = SurfaceComposerClient::getPhysicalDisplayToken(*displayId);
    if (!displayToken) {
        LOG(ERROR) << "Failed to get a valid display token";
        return nullptr;
    }

    ::android::status_t status =
            SurfaceComposerClient::getActiveDisplayMode(displayToken, displayMode);
    if (status != ::android::NO_ERROR) {
        LOG(WARNING) << "Failed to read current mode of the display " << id;
    }

    status = SurfaceComposerClient::getDisplayState(displayToken, displayState);
    if (status != ::android::NO_ERROR) {
        LOG(WARNING) << "Failed to read current state of the display " << id;
    }

    return displayToken;
}

ScopedAStatus CarDisplayProxy::getHGraphicBufferProducer(int64_t id, NativeHandle* _aidl_return) {
    ::android::sp<::android::IBinder> displayToken;
    ::android::sp<::android::SurfaceControl> surfaceControl;

    if (auto status = getDisplayRecord(id, &displayToken, &surfaceControl); !status.isOk()) {
        return status;
    }

    // SurfaceControl::getSurface() is guaranteed to be non-null.
    auto targetSurface = surfaceControl->getSurface();
    auto igbp = targetSurface->getIGraphicBufferProducer();
    auto hgbp =
            new ::android::hardware::graphics::bufferqueue::V2_0::utils::B2HGraphicBufferProducer(
                    igbp);

    ::android::HalToken halToken;
    if (!::android::createHalToken(hgbp, &halToken)) {
        LOG(ERROR) << "Failed to create a hal token";
        return ScopedAStatus::fromStatus(STATUS_BAD_VALUE);
    }

    native_handle_t* handle = convertHalTokenToNativeHandle(halToken);
    auto scope_guard = ::android::base::make_scope_guard([handle]() {
        native_handle_close(handle);
        native_handle_delete(handle);
    });
    if (handle == nullptr) {
        LOG(ERROR) << "Failed to create a handle, errno = " << strerror(errno);
        return ScopedAStatus::fromStatus(STATUS_BAD_VALUE);
    }

    *_aidl_return = ::android::dupToAidl(handle);
    return ScopedAStatus::ok();
}

ScopedAStatus CarDisplayProxy::getSurface(int64_t id, android::view::Surface* _aidl_return) {
    if (_aidl_return == nullptr) {
        LOG(ERROR) << __FUNCTION__
                   << " is called with an invalid aidl::android::view::Surface object.";
        return ScopedAStatus::fromStatus(STATUS_BAD_VALUE);
    }

    auto it = mSurfaceList.find(id);
    if (it != mSurfaceList.end() && ::android::Surface::isValid(mSurfaceList[id])) {
        LOG(DEBUG) << __FUNCTION__ << " reuses an existing surface for a display " << id;
        _aidl_return->reset(mSurfaceList[id].get());
        return ScopedAStatus::ok();
    }

    ::android::sp<::android::IBinder> displayToken;
    ::android::sp<::android::SurfaceControl> surfaceControl;
    if (auto status = getDisplayRecord(id, &displayToken, &surfaceControl); !status.isOk()) {
        return status;
    }

    // Retrieve a Surface associated with a target display.
    ::android::sp<::android::Surface> targetSurface = surfaceControl->getSurface();
    if (!targetSurface || !::android::Surface::isValid(targetSurface)) {
        LOG(ERROR) << "Created Surface is invalid, " << targetSurface.get();
        return ScopedAStatus::fromStatus(STATUS_FAILED_TRANSACTION);
    }

    // Bookkeep a retrieved Surface object and pass it to the client as a format
    // of ::aidl::android::view::Surface, which is alias to
    // ::aidl::android::hardware::NativeWindow.
    mSurfaceList.insert_or_assign(id, targetSurface);
    _aidl_return->reset(targetSurface.get());
    return ScopedAStatus::ok();
}

ScopedAStatus CarDisplayProxy::hideWindow(int64_t id) {
    auto it = mDisplays.find(id);
    if (it == mDisplays.end()) {
        LOG(DEBUG) << __FUNCTION__ << ": Invalid display id, " << id;
        return ScopedAStatus::ok();
    }

    auto status = SurfaceComposerClient::Transaction{}.hide(it->second.surfaceControl).apply();
    if (status != ::android::NO_ERROR) {
        LOG(DEBUG) << __FUNCTION__
                   << ": Failed to hide a surface, status = " << ::android::statusToString(status);
    }

    return ScopedAStatus::ok();
}

ScopedAStatus CarDisplayProxy::showWindow(int64_t id) {
    auto it = mDisplays.find(id);
    if (it == mDisplays.end()) {
        LOG(ERROR) << __FUNCTION__ << ": Invalid display id, " << id;
        return ScopedAStatus::fromStatus(STATUS_BAD_VALUE);
    }

    const auto& displayToken = it->second.token;
    const auto& surfaceControl = it->second.surfaceControl;
    ::android::ui::DisplayState displayState;
    ::android::status_t status =
            SurfaceComposerClient::getDisplayState(displayToken, &displayState);
    if (status != ::android::NO_ERROR) {
        LOG(ERROR) << "Failed to read current state of the display " << id
                   << ", status = " << ::android::statusToString(status);
        return ScopedAStatus::fromStatus(status);
    }

    SurfaceComposerClient::Transaction t;
    t.setDisplayLayerStack(displayToken, displayState.layerStack);
    t.setLayerStack(surfaceControl, displayState.layerStack);

    status = t.setLayer(surfaceControl, kSurfaceZOrder).show(surfaceControl).apply();
    if (status != ::android::NO_ERROR) {
        LOG(ERROR) << "Failed to set a layer";
        return ScopedAStatus::fromStatus(status);
    }

    return ScopedAStatus::ok();
}

ScopedAStatus CarDisplayProxy::getDisplayRecord(
        int64_t id, ::android::sp<::android::IBinder>* outDisplayToken,
        ::android::sp<::android::SurfaceControl>* outSurfaceControl) {
    auto it = mDisplays.find(id);
    if (it != mDisplays.end()) {
        *outDisplayToken = mDisplays[id].token;
        *outSurfaceControl = mDisplays[id].surfaceControl;
        return ScopedAStatus::ok();
    }

    ::android::sp<::android::IBinder> displayToken;
    ::android::sp<::android::SurfaceControl> surfaceControl;
    ::android::ui::DisplayMode displayMode;
    ::android::ui::DisplayState displayState;
    displayToken = getDisplayInfoFromSurfaceComposerClient(id, &displayMode, &displayState);
    if (!displayToken) {
        LOG(ERROR) << "Failed to read display information from SurfaceComposerClient.";
        return ScopedAStatus::fromStatus(STATUS_FAILED_TRANSACTION);
    }

    auto displayWidth = displayMode.resolution.getWidth();
    auto displayHeight = displayMode.resolution.getHeight();
    if ((displayState.orientation != ::android::ui::ROTATION_0) &&
        (displayState.orientation != ::android::ui::ROTATION_180)) {
        std::swap(displayWidth, displayHeight);
    }

    ::android::sp<SurfaceComposerClient> client = new SurfaceComposerClient();
    ::android::status_t status = client->initCheck();
    if (status != ::android::NO_ERROR) {
        LOG(ERROR) << "SurfaceComposerClient::initCheck() fails, error = "
                   << ::android::statusToString(status);
        return ScopedAStatus::fromStatus(status);
    }

    surfaceControl =
            client->createSurface(::android::String8::format("CarDisplayProxy::%" PRIx64, id),
                                  displayWidth, displayHeight, ::android::PIXEL_FORMAT_RGBX_8888,
                                  ::android::ISurfaceComposerClient::eOpaque);
    if (!surfaceControl || !surfaceControl->isValid()) {
        LOG(ERROR) << "Failed to create a SurfaceControl";
        return ScopedAStatus::fromStatus(STATUS_FAILED_TRANSACTION);
    }

    *outDisplayToken = displayToken;
    *outSurfaceControl = surfaceControl;

    DisplayRecord rec = {displayToken, surfaceControl};
    mDisplays.insert_or_assign(id, std::move(rec));

    return ScopedAStatus::ok();
}

}  // namespace aidl::android::frameworks::automotive::display::implementation
