/*
 * Copyright (C) 2024 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_NDEBUG 0
#define LOG_TAG "VirtualCameraTestInstance"

#include "VirtualCameraTestInstance.h"

#include <atomic>
#include <chrono>
#include <memory>
#include <mutex>
#include <ratio>
#include <thread>

#include "GLES/gl.h"
#include "android/binder_auto_utils.h"
#include "android/native_window.h"
#include "log/log.h"
#include "util/EglDisplayContext.h"
#include "util/EglProgram.h"

namespace android {
namespace companion {
namespace virtualcamera {

using ::aidl::android::companion::virtualcamera::Format;
using ::aidl::android::view::Surface;
using ::ndk::ScopedAStatus;

namespace {

std::shared_ptr<ANativeWindow> nativeWindowFromSurface(const Surface& surface) {
  ANativeWindow* nativeWindow = surface.get();
  if (nativeWindow != nullptr) {
    ANativeWindow_acquire(nativeWindow);
  }
  return std::shared_ptr<ANativeWindow>(nativeWindow, ANativeWindow_release);
}

std::chrono::nanoseconds getCurrentTimestamp() {
  return std::chrono::duration_cast<std::chrono::nanoseconds>(
      std::chrono::steady_clock::now().time_since_epoch());
}

}  // namespace

TestPatternRenderer::TestPatternRenderer(
    std::shared_ptr<ANativeWindow> nativeWindow, int fps)
    : mFps(fps), mNativeWindow(nativeWindow) {
}

void TestPatternRenderer::start() {
  std::lock_guard<std::mutex> lock(mLock);
  if (mRunning.exchange(true, std::memory_order_relaxed)) {
    ALOGW("Render thread already started.");
    return;
  }
  mThread =
      std::thread(&TestPatternRenderer::renderThreadLoop, this, mNativeWindow);
}

void TestPatternRenderer::stop() {
  std::lock_guard<std::mutex> lock(mLock);
  if (!mRunning.exchange(false, std::memory_order_relaxed)) {
    ALOGW("Render thread already stopped.");
    return;
  }
  mThread.detach();
  mRunning = false;
}

void TestPatternRenderer::renderThreadLoop(
    std::shared_ptr<ANativeWindow> nativeWindow) {
  // Prevent destruction of this instance until the thread terminates.
  std::shared_ptr<TestPatternRenderer> thiz = shared_from_this();

  ALOGV("Starting test client render loop");

  EglDisplayContext eglDisplayContext(nativeWindow);
  EglTestPatternProgram testPatternProgram;

  const std::chrono::nanoseconds frameDuration(
      static_cast<uint64_t>(1e9 / mFps));

  std::chrono::nanoseconds lastFrameTs(0);
  int frameNumber = 0;
  while (mRunning) {
    // Wait for appropriate amount of time to meet configured FPS.
    std::chrono::nanoseconds ts = getCurrentTimestamp();
    std::chrono::nanoseconds currentDuration = ts - lastFrameTs;
    if (currentDuration < frameDuration) {
      std::this_thread::sleep_for(frameDuration - currentDuration);
    }

    // Render the test pattern and update timestamp.
    testPatternProgram.draw(ts);
    eglDisplayContext.swapBuffers();
    lastFrameTs = getCurrentTimestamp();
  }

  ALOGV("Terminating test client render loop");
}

VirtualCameraTestInstance::VirtualCameraTestInstance(const int fps)
    : mFps(fps) {
}

ScopedAStatus VirtualCameraTestInstance::onStreamConfigured(
    const int32_t streamId, const Surface& surface, const int32_t width,
    const int32_t height, const Format pixelFormat) {
  ALOGV("%s: streamId %d, %dx%d pixFmt=%s", __func__, streamId, width, height,
        toString(pixelFormat).c_str());

  auto renderer = std::make_shared<TestPatternRenderer>(
      nativeWindowFromSurface(surface), mFps);

  std::lock_guard<std::mutex> lock(mLock);
  if (mInputRenderers.try_emplace(streamId, renderer).second) {
    renderer->start();
  } else {
    ALOGE(
        "%s: Input stream with id %d is already active, ignoring "
        "onStreamConfigured call",
        __func__, streamId);
  }

  return ScopedAStatus::ok();
}

ScopedAStatus VirtualCameraTestInstance::onProcessCaptureRequest(
    const int32_t /*in_streamId*/, const int32_t /*in_frameId*/) {
  return ScopedAStatus::ok();
}

ScopedAStatus VirtualCameraTestInstance::onStreamClosed(const int32_t streamId) {
  ALOGV("%s: streamId %d", __func__, streamId);

  std::shared_ptr<TestPatternRenderer> renderer;
  {
    std::lock_guard<std::mutex> lock(mLock);
    auto it = mInputRenderers.find(streamId);
    if (it != mInputRenderers.end()) {
      renderer = std::move(it->second);
      mInputRenderers.erase(it);
    }
  }
  if (renderer != nullptr) {
    renderer->stop();
  }
  return ScopedAStatus::ok();
}

}  // namespace virtualcamera
}  // namespace companion
}  // namespace android
