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

#include "RenderDirectView.h"
#include "Utils.h"

#include <hidl/HidlTransportSupport.h>
#include <log/log.h>
#include <utils/SystemClock.h>

namespace android {
namespace automotive {
namespace evs {
namespace support {

using ::android::hardware::configureRpcThreadpool;
using ::android::hardware::joinRpcThreadpool;
using ::android::hardware::automotive::evs::V1_0::DisplayState;
using ::android::hardware::automotive::evs::V1_0::EvsResult;

// TODO(b/130246434): since we don't support multi-display use case, there
// should only be one DisplayUseCase. Add the logic to prevent more than
// one DisplayUseCases running at the same time.
DisplayUseCase::DisplayUseCase(string cameraId, BaseRenderCallback* callback) :
      BaseUseCase(vector<string>(1, cameraId)) {
    mRenderCallback = callback;
}

DisplayUseCase::~DisplayUseCase() {
    if (mCurrentRenderer != nullptr) {
        mCurrentRenderer->deactivate();
        mCurrentRenderer = nullptr;  // It's a smart pointer, so destructs on assignment to null
    }

    mIsReadyToRun = false;
    if (mWorkerThread.joinable()) {
        mWorkerThread.join();
    }
}

bool DisplayUseCase::initialize() {
    // Load our configuration information
    ConfigManager config;
    if (!config.initialize("/system/etc/automotive/evs_support_lib/camera_config.json")) {
        ALOGE("Missing or improper configuration for the EVS application.  Exiting.");
        return false;
    }

    // Set thread pool size to one to avoid concurrent events from the HAL.
    // This pool will handle the EvsCameraStream callbacks.
    // Note:  This _will_ run in parallel with the EvsListener run() loop below which
    // runs the application logic that reacts to the async events.
    configureRpcThreadpool(1, false /* callerWillJoin */);

    mResourceManager = ResourceManager::getInstance();
    if (mResourceManager == nullptr) {
        ALOGE("Failed to get resource manager instance. Initialization failed.");
        return false;
    }

    // Request exclusive access to the EVS display
    ALOGI("Acquiring EVS Display");

    mDisplay = mResourceManager->openDisplay();
    if (mDisplay.get() == nullptr) {
        ALOGE("EVS Display unavailable.  Exiting.");
        return false;
    }

    ALOGD("Requesting camera list");
    for (auto&& info : config.getCameras()) {
        // This use case is currently a single camera use case.
        // Only one element is available in the camera id list.
        string cameraId = mCameraIds[0];
        if (cameraId == info.cameraId) {
            mStreamHandler = mResourceManager->obtainStreamHandler(cameraId);
            if (mStreamHandler.get() == nullptr) {
                ALOGE("Failed to get a valid StreamHandler for %s", cameraId.c_str());
                return false;
            }

            mIsInitialized = true;
            return true;
        }
    }

    ALOGE("Cannot find a match camera. Exiting");
    return false;
}

// TODO(b/130246434): if user accidentally call this function twice, there is
// no logic to handle that and it will causes issues. For example, the
// mWorkerThread will be assigned twice and cause unexpected behavior.
// We need to fix this issue.
bool DisplayUseCase::startVideoStream() {
    // Initialize the use case.
    if (!mIsInitialized && !initialize()) {
        ALOGE("There is an error while initializing the use case. Exiting");
        return false;
    }

    ALOGD("Attach use case to StreamHandler");
    if (mRenderCallback != nullptr) {
        mStreamHandler->attachRenderCallback(mRenderCallback);
    }

    ALOGD("Start video streaming using worker thread");
    mIsReadyToRun = true;
    mWorkerThread = std::thread([this]() {
        // We have a camera assigned to this state for direct view
        mCurrentRenderer = std::make_unique<RenderDirectView>();
        if (!mCurrentRenderer) {
            ALOGE("Failed to construct direct renderer. Exiting.");
            mIsReadyToRun = false;
            return;
        }

        // Now set the display state based on whether we have a video feed to show
        // Start the camera stream
        ALOGD("EvsStartCameraStreamTiming start time: %" PRId64 "ms", android::elapsedRealtime());
        if (!mCurrentRenderer->activate()) {
            ALOGE("New renderer failed to activate. Exiting");
            mIsReadyToRun = false;
            return;
        }

        // Activate the display
        ALOGD("EvsActivateDisplayTiming start time: %" PRId64 "ms", android::elapsedRealtime());
        Return<EvsResult> result = mDisplay->setDisplayState(DisplayState::VISIBLE_ON_NEXT_FRAME);
        if (result != EvsResult::OK) {
            ALOGE("setDisplayState returned an error (%d). Exiting.", (EvsResult)result);
            mIsReadyToRun = false;
            return;
        }

        if (!mStreamHandler->startStream()) {
            ALOGE("failed to start stream handler");
            mIsReadyToRun = false;
            return;
        }

        while (mIsReadyToRun && streamFrame());

        ALOGD("Worker thread stops.");
    });

    return true;
}

void DisplayUseCase::stopVideoStream() {
    ALOGD("Stop video streaming in worker thread.");
    mIsReadyToRun = false;

    if (mStreamHandler == nullptr) {
        ALOGE("Failed to detach render callback since stream handler is null");

        // Something may go wrong. Instead of to return this method right away,
        // we want to finish the remaining logic of this method to try to
        // release other resources.
    } else {
        mStreamHandler->detachRenderCallback();
    }

    if (mResourceManager == nullptr) {
        ALOGE("Failed to release resources since resource manager is null");
    } else {
        mResourceManager->releaseStreamHandler(mCameraIds[0]);
        mStreamHandler = nullptr;

        mResourceManager->closeDisplay(mDisplay);
        mDisplay = nullptr;

        // TODO(b/130246434): with the current logic, the initialize method will
        // be triggered every time when a pair of
        // stopVideoStream/startVideoStream is called. We might want to move
        // some heavy work away from initialize method so increase the
        // performance.

        // Sets mIsInitialzed to false so the initialize method will be
        // triggered when startVideoStream is called again.
        mIsInitialized = false;
    }
    return;
}

bool DisplayUseCase::streamFrame() {
    // Get the output buffer we'll use to display the imagery
    BufferDesc tgtBuffer = {};
    mDisplay->getTargetBuffer([&tgtBuffer](const BufferDesc& buff) { tgtBuffer = buff; });

    // TODO(b/130246434): if there is no new display frame available, shall we
    // still get display buffer? Shall we just skip and keep the display
    // un-refreshed?
    // We should explore this option.

    // If there is no display buffer available, skip it.
    if (tgtBuffer.memHandle == nullptr) {
        ALOGW("Didn't get requested output buffer -- skipping this frame.");

        // Return true since it won't affect next call.
        return true;
    } else {
        // If there is no new display frame available, re-use the old (held)
        // frame for display.
        // Otherwise, return the old (held) frame, fetch the newly available
        // frame from stream handler, and use the new frame for display
        // purposes.
        if (!mStreamHandler->newDisplayFrameAvailable()) {
            ALOGD("No new display frame is available. Re-use the old frame.");
        } else {
            ALOGD("Get new display frame, refreshing");

            // If we already hold a camera image for display purposes, it's
            // time to return it to evs camera driver.
            if (mImageBuffer.memHandle.getNativeHandle() != nullptr) {
                mStreamHandler->doneWithFrame(mImageBuffer);
            }

            // Get the new image we want to use as our display content
            mImageBuffer = mStreamHandler->getNewDisplayFrame();
        }

        // Render the image buffer to the display buffer
        bool result = mCurrentRenderer->drawFrame(tgtBuffer, mImageBuffer);

        // Send the finished display buffer back to display driver
        // Even if the rendering fails, we still want to return the display
        // buffer.
        mDisplay->returnTargetBufferForDisplay(tgtBuffer);

        return result;
    }
}

DisplayUseCase DisplayUseCase::createDefaultUseCase(string cameraId, BaseRenderCallback* callback) {
    return DisplayUseCase(cameraId, callback);
}

}  // namespace support
}  // namespace evs
}  // namespace automotive
}  // namespace android
