/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "VRPuppetCommandBuffer.h"
#include "prthread.h"
#include "mozilla/ClearOnShutdown.h"

namespace mozilla::gfx {

static StaticRefPtr<VRPuppetCommandBuffer> sVRPuppetCommandBufferSingleton;

/* static */
VRPuppetCommandBuffer& VRPuppetCommandBuffer::Get() {
  if (sVRPuppetCommandBufferSingleton == nullptr) {
    sVRPuppetCommandBufferSingleton = new VRPuppetCommandBuffer();
    ClearOnShutdown(&sVRPuppetCommandBufferSingleton);
  }
  return *sVRPuppetCommandBufferSingleton;
}

/* static */
bool VRPuppetCommandBuffer::IsCreated() {
  return sVRPuppetCommandBufferSingleton != nullptr;
}

VRPuppetCommandBuffer::VRPuppetCommandBuffer()
    : mMutex("VRPuppetCommandBuffer::mMutex") {
  MOZ_COUNT_CTOR(VRPuppetCommandBuffer);
  MOZ_ASSERT(sVRPuppetCommandBufferSingleton == nullptr);
  Reset();
}

VRPuppetCommandBuffer::~VRPuppetCommandBuffer() {
  MOZ_COUNT_DTOR(VRPuppetCommandBuffer);
}

void VRPuppetCommandBuffer::Submit(const nsTArray<uint64_t>& aBuffer) {
  MutexAutoLock lock(mMutex);
  mBuffer.AppendElements(aBuffer);
  mEnded = false;
  mEndedWithTimeout = false;
}

bool VRPuppetCommandBuffer::HasEnded() {
  MutexAutoLock lock(mMutex);
  return mEnded;
}

void VRPuppetCommandBuffer::Reset() {
  MutexAutoLock lock(mMutex);
  memset(&mPendingState, 0, sizeof(VRSystemState));
  memset(&mCommittedState, 0, sizeof(VRSystemState));
  for (int iControllerIdx = 0; iControllerIdx < kVRControllerMaxCount;
       iControllerIdx++) {
    for (int iHaptic = 0; iHaptic < kNumPuppetHaptics; iHaptic++) {
      mHapticPulseRemaining[iControllerIdx][iHaptic] = 0.0f;
      mHapticPulseIntensity[iControllerIdx][iHaptic] = 0.0f;
    }
  }
  mDataOffset = 0;
  mPresentationRequested = false;
  mFrameSubmitted = false;
  mFrameAccepted = false;
  mTimeoutDuration = 10.0f;
  mWaitRemaining = 0.0f;
  mBlockedTime = 0.0f;
  mTimerElapsed = 0.0f;
  mEnded = true;
  mEndedWithTimeout = false;
  mLastRunTimestamp = TimeStamp();
  mTimerSamples.Clear();
  mBuffer.Clear();
}

bool VRPuppetCommandBuffer::RunCommand(uint64_t aCommand, double aDeltaTime) {
  /**
   * Run a single command.  If the command is blocking on a state change and
   * can't be executed, return false.
   *
   * VRPuppetCommandBuffer::RunCommand is only called by
   *VRPuppetCommandBuffer::Run(), which is already holding the mutex.
   *
   * Note that VRPuppetCommandBuffer::RunCommand may potentially be called >1000
   *times per frame. It might not hurt to add an assert here, but we should
   *avoid adding code which may potentially malloc (eg string handling) even for
   *debug builds here.  This function will need to be reasonably fast, even in
   *debug builds which will be using it during tests.
   **/
  switch ((VRPuppet_Command)(aCommand & 0xff00000000000000)) {
    case VRPuppet_Command::VRPuppet_End:
      CompleteTest(false);
      break;
    case VRPuppet_Command::VRPuppet_ClearAll:
      memset(&mPendingState, 0, sizeof(VRSystemState));
      memset(&mCommittedState, 0, sizeof(VRSystemState));
      mPresentationRequested = false;
      mFrameSubmitted = false;
      mFrameAccepted = false;
      break;
    case VRPuppet_Command::VRPuppet_ClearController: {
      uint8_t controllerIdx = aCommand & 0x00000000000000ff;
      if (controllerIdx < kVRControllerMaxCount) {
        mPendingState.controllerState[controllerIdx].Clear();
      }
    } break;
    case VRPuppet_Command::VRPuppet_Timeout:
      mTimeoutDuration = (double)(aCommand & 0x00000000ffffffff) / 1000.0f;
      break;
    case VRPuppet_Command::VRPuppet_Wait:
      if (mWaitRemaining == 0.0f) {
        mWaitRemaining = (double)(aCommand & 0x00000000ffffffff) / 1000.0f;
        // Wait timer started, block
        return false;
      }
      mWaitRemaining -= aDeltaTime;
      if (mWaitRemaining > 0.0f) {
        // Wait timer still running, block
        return false;
      }
      // Wait timer has elapsed, unblock
      mWaitRemaining = 0.0f;
      break;
    case VRPuppet_Command::VRPuppet_WaitSubmit:
      if (!mFrameSubmitted) {
        return false;
      }
      break;
    case VRPuppet_Command::VRPuppet_CaptureFrame:
      // TODO - Capture the frame and record the output (Bug 1555180)
      break;
    case VRPuppet_Command::VRPuppet_AcknowledgeFrame:
      mFrameSubmitted = false;
      mFrameAccepted = true;
      break;
    case VRPuppet_Command::VRPuppet_RejectFrame:
      mFrameSubmitted = false;
      mFrameAccepted = false;
      break;
    case VRPuppet_Command::VRPuppet_WaitPresentationStart:
      if (!mPresentationRequested) {
        return false;
      }
      break;
    case VRPuppet_Command::VRPuppet_WaitPresentationEnd:
      if (mPresentationRequested) {
        return false;
      }
      break;
    case VRPuppet_Command::VRPuppet_WaitHapticIntensity: {
      // 0x0800cchhvvvvvvvv - VRPuppet_WaitHapticIntensity(c, h, v)
      uint8_t iControllerIdx = (aCommand & 0x0000ff0000000000) >> 40;
      if (iControllerIdx >= kVRControllerMaxCount) {
        // Puppet test is broken, ensure it fails
        return false;
      }
      uint8_t iHapticIdx = (aCommand & 0x000000ff00000000) >> 32;
      if (iHapticIdx >= kNumPuppetHaptics) {
        // Puppet test is broken, ensure it fails
        return false;
      }
      uint32_t iHapticIntensity =
          aCommand & 0x00000000ffffffff;  // interpreted as 16.16 fixed point

      SimulateHaptics(aDeltaTime);
      uint64_t iCurrentIntensity =
          round(mHapticPulseIntensity[iControllerIdx][iHapticIdx] *
                (1 << 16));  // convert to 16.16 fixed point
      if (iCurrentIntensity > 0xffffffff) {
        iCurrentIntensity = 0xffffffff;
      }
      if (iCurrentIntensity != iHapticIntensity) {
        return false;
      }
    } break;

    case VRPuppet_Command::VRPuppet_StartTimer:
      mTimerElapsed = 0.0f;
      break;
    case VRPuppet_Command::VRPuppet_StopTimer:
      mTimerSamples.AppendElements(mTimerElapsed);
      // TODO - Return the timer samples to Javascript once the command buffer
      // is complete (Bug 1555182)
      break;

    case VRPuppet_Command::VRPuppet_UpdateDisplay:
      mDataOffset = (uint8_t*)&mPendingState.displayState -
                    (uint8_t*)&mPendingState + (aCommand & 0x00000000ffffffff);
      break;
    case VRPuppet_Command::VRPuppet_UpdateSensor:
      mDataOffset = (uint8_t*)&mPendingState.sensorState -
                    (uint8_t*)&mPendingState + (aCommand & 0x00000000ffffffff);
      break;
    case VRPuppet_Command::VRPuppet_UpdateControllers:
      mDataOffset = (uint8_t*)&mPendingState.controllerState -
                    (uint8_t*)&mPendingState + (aCommand & 0x00000000ffffffff);
      break;
    case VRPuppet_Command::VRPuppet_Commit:
      memcpy(&mCommittedState, &mPendingState, sizeof(VRSystemState));
      break;

    case VRPuppet_Command::VRPuppet_Data7:
      WriteData((aCommand & 0x00ff000000000000) >> 48);
      [[fallthrough]];
      // Purposefully, no break
    case VRPuppet_Command::VRPuppet_Data6:
      WriteData((aCommand & 0x0000ff0000000000) >> 40);
      [[fallthrough]];
      // Purposefully, no break
    case VRPuppet_Command::VRPuppet_Data5:
      WriteData((aCommand & 0x000000ff00000000) >> 32);
      [[fallthrough]];
      // Purposefully, no break
    case VRPuppet_Command::VRPuppet_Data4:
      WriteData((aCommand & 0x00000000ff000000) >> 24);
      [[fallthrough]];
      // Purposefully, no break
    case VRPuppet_Command::VRPuppet_Data3:
      WriteData((aCommand & 0x0000000000ff0000) >> 16);
      [[fallthrough]];
      // Purposefully, no break
    case VRPuppet_Command::VRPuppet_Data2:
      WriteData((aCommand & 0x000000000000ff00) >> 8);
      [[fallthrough]];
      // Purposefully, no break
    case VRPuppet_Command::VRPuppet_Data1:
      WriteData(aCommand & 0x00000000000000ff);
      break;
  }
  return true;
}

void VRPuppetCommandBuffer::WriteData(uint8_t aData) {
  if (mDataOffset && mDataOffset < sizeof(VRSystemState)) {
    ((uint8_t*)&mPendingState)[mDataOffset++] = aData;
  }
}

void VRPuppetCommandBuffer::Run() {
  MutexAutoLock lock(mMutex);
  TimeStamp now = TimeStamp::Now();
  double deltaTime = 0.0f;
  if (!mLastRunTimestamp.IsNull()) {
    deltaTime = (now - mLastRunTimestamp).ToSeconds();
  }
  mLastRunTimestamp = now;
  mTimerElapsed += deltaTime;
  size_t transactionLength = 0;
  while (transactionLength < mBuffer.Length() && !mEnded) {
    if (RunCommand(mBuffer[transactionLength], deltaTime)) {
      mBlockedTime = 0.0f;
      transactionLength++;
    } else {
      mBlockedTime += deltaTime;
      if (mBlockedTime > mTimeoutDuration) {
        CompleteTest(true);
      }
      // If a command is blocked, we don't increment transactionLength,
      // allowing the command to be retried on the next cycle
      break;
    }
  }
  mBuffer.RemoveElementsAt(0, transactionLength);
}

void VRPuppetCommandBuffer::Run(VRSystemState& aState) {
  Run();
  // We don't want to stomp over some members
  bool bEnumerationCompleted = aState.enumerationCompleted;
  bool bShutdown = aState.displayState.shutdown;
  uint32_t minRestartInterval = aState.displayState.minRestartInterval;

  // Overwrite it all
  memcpy(&aState, &mCommittedState, sizeof(VRSystemState));

  // Restore the members
  aState.enumerationCompleted = bEnumerationCompleted;
  aState.displayState.shutdown = bShutdown;
  aState.displayState.minRestartInterval = minRestartInterval;
}

void VRPuppetCommandBuffer::StartPresentation() {
  mPresentationRequested = true;
  Run();
}

void VRPuppetCommandBuffer::StopPresentation() {
  mPresentationRequested = false;
  Run();
}

bool VRPuppetCommandBuffer::SubmitFrame() {
  // Emulate blocking behavior of various XR API's as
  // described by puppet script
  mFrameSubmitted = true;
  mFrameAccepted = false;
  while (true) {
    Run();
    if (!mFrameSubmitted || mEnded) {
      break;
    }
    PR_Sleep(PR_INTERVAL_NO_WAIT);  // Yield
  }

  return mFrameAccepted;
}

void VRPuppetCommandBuffer::VibrateHaptic(uint32_t aControllerIdx,
                                          uint32_t aHapticIndex,
                                          float aIntensity, float aDuration) {
  if (aHapticIndex >= kNumPuppetHaptics ||
      aControllerIdx >= kVRControllerMaxCount) {
    return;
  }

  // We must Run() before and after updating haptic state to avoid script
  // deadlocks
  // The deadlocks would be caused by scripts that include two
  // VRPuppet_WaitHapticIntensity commands.  If
  // VRPuppetCommandBuffer::VibrateHaptic() is called twice without advancing
  // through the command buffer with VRPuppetCommandBuffer::Run() in between,
  // the first VRPuppet_WaitHapticInensity may not see the transient value that
  // it is waiting for, thus blocking forever and deadlocking the script.
  Run();
  mHapticPulseRemaining[aControllerIdx][aHapticIndex] = aDuration;
  mHapticPulseIntensity[aControllerIdx][aHapticIndex] = aIntensity;
  Run();
}

void VRPuppetCommandBuffer::StopVibrateHaptic(uint32_t aControllerIdx) {
  if (aControllerIdx >= kVRControllerMaxCount) {
    return;
  }
  // We must Run() before and after updating haptic state to avoid script
  // deadlocks
  Run();
  for (int iHaptic = 0; iHaptic < kNumPuppetHaptics; iHaptic++) {
    mHapticPulseRemaining[aControllerIdx][iHaptic] = 0.0f;
    mHapticPulseIntensity[aControllerIdx][iHaptic] = 0.0f;
  }
  Run();
}

void VRPuppetCommandBuffer::StopAllHaptics() {
  // We must Run() before and after updating haptic state to avoid script
  // deadlocks
  Run();
  for (int iControllerIdx = 0; iControllerIdx < kVRControllerMaxCount;
       iControllerIdx++) {
    for (int iHaptic = 0; iHaptic < kNumPuppetHaptics; iHaptic++) {
      mHapticPulseRemaining[iControllerIdx][iHaptic] = 0.0f;
      mHapticPulseIntensity[iControllerIdx][iHaptic] = 0.0f;
    }
  }
  Run();
}

void VRPuppetCommandBuffer::SimulateHaptics(double aDeltaTime) {
  for (int iControllerIdx = 0; iControllerIdx < kVRControllerMaxCount;
       iControllerIdx++) {
    for (int iHaptic = 0; iHaptic < kNumPuppetHaptics; iHaptic++) {
      if (mHapticPulseIntensity[iControllerIdx][iHaptic] > 0.0f) {
        mHapticPulseRemaining[iControllerIdx][iHaptic] -= aDeltaTime;
        if (mHapticPulseRemaining[iControllerIdx][iHaptic] <= 0.0f) {
          mHapticPulseRemaining[iControllerIdx][iHaptic] = 0.0f;
          mHapticPulseIntensity[iControllerIdx][iHaptic] = 0.0f;
        }
      }
    }
  }
}

void VRPuppetCommandBuffer::CompleteTest(bool aTimedOut) {
  mEndedWithTimeout = aTimedOut;
  mEnded = true;
}

/**
 *  Generates a sequence of VRPuppet_Data# commands, as described
 *  in VRPuppetCommandBuffer.h, to encode the changes to be made to
 *  a "destination" structure to match the "source" structure.
 *  As the commands are encoded, the destination structure is updated
 *  to match the source.
 *
 * @param aBuffer
 *     The buffer in which the commands will be appended.
 * @param aSrcStart
 *     Byte pointer to the start of the structure that
 *     will be copied from.
 * @param aDstStart
 *     Byte pointer to the start of the structure that
 *     will be copied to.
 * @param aLength
 *     Length of the structure that will be copied.
 * @param aUpdateCommand
 *     VRPuppet_... command indicating which structure is being
 *     copied:
 *     VRPuppet_Command::VRPuppet_UpdateDisplay:
 *         A single VRDisplayState struct
 *     VRPuppet_Command::VRPuppet_UpdateSensor:
 *         A single VRHMDSensorState struct
 *     VRPuppet_Command::VRPuppet_UpdateControllers:
 *         An array of VRControllerState structs with a
 *         count of kVRControllerMaxCount
 */
void VRPuppetCommandBuffer::EncodeStruct(nsTArray<uint64_t>& aBuffer,
                                         uint8_t* aSrcStart, uint8_t* aDstStart,
                                         size_t aLength,
                                         VRPuppet_Command aUpdateCommand) {
  // Naive implementation, but will not be executed in realtime, so will not
  // affect test timer results. Could be improved to avoid unaligned reads and
  // to use SSE.

  // Pointer to source byte being compared+copied
  uint8_t* src = aSrcStart;

  // Pointer to destination byte being compared+copied
  uint8_t* dst = aDstStart;

  // Number of bytes packed into bufData
  uint8_t bufLen = 0;

  // 64-bits to be interpreted as up to 7 separate bytes
  // This will form the lower 56 bits of the command
  uint64_t bufData = 0;

  // purgebuffer takes the bytes stored in bufData and generates a VRPuppet
  // command representing those bytes as "VRPuppet Data".
  // VRPUppet_Data1 encodes 1 byte
  // VRPuppet_Data2 encodes 2 bytes
  // and so on, until..
  // VRPuppet_Data7 encodes 7 bytes
  // This command is appended to aBuffer, then bufLen and bufData are reset
  auto purgeBuffer = [&]() {
    // Only emit a command if there are data bytes in bufData
    if (bufLen > 0) {
      MOZ_ASSERT(bufLen <= 7);
      uint64_t command = (uint64_t)VRPuppet_Command::VRPuppet_Data1;
      command += ((uint64_t)VRPuppet_Command::VRPuppet_Data2 -
                  (uint64_t)VRPuppet_Command::VRPuppet_Data1) *
                 (bufLen - 1);
      command |= bufData;
      aBuffer.AppendElement(command);
      bufLen = 0;
      bufData = 0;
    }
  };

  // Loop through the bytes of the structs.
  // While copying the struct at aSrcStart to aDstStart,
  // the differences are encoded as VRPuppet commands and
  // appended to aBuffer.
  for (size_t i = 0; i < aLength; i++) {
    if (*src != *dst) {
      // This byte is different

      // Copy the byte to the destination
      *dst = *src;

      if (bufLen == 0) {
        // This is the start of a new span of changed bytes

        // Output a command to specify the offset of the
        // span.
        aBuffer.AppendElement((uint64_t)aUpdateCommand + i);

        // Store this first byte in bufData.
        // We will batch up to 7 bytes in one VRPuppet_DataXX
        // command, so we won't emit it yet.
        bufLen = 1;
        bufData = *src;
      } else if (bufLen <= 6) {
        // This is the continuation of a span of changed bytes.
        // There is room to add more bytes to bufData.

        // Store the next byte in bufData.
        // We will batch up to 7 bytes in one VRPuppet_DataXX
        // command, so we won't emit it yet.
        bufData = (bufData << 8) | *src;
        bufLen++;
      } else {
        MOZ_ASSERT(bufLen == 7);
        // This is the continuation of a span of changed bytes.
        // There are already 7 bytes in bufData, so we must emit
        // the VRPuppet_Data7 command for the prior bytes before
        // starting a new command.
        aBuffer.AppendElement((uint64_t)VRPuppet_Command::VRPuppet_Data7 +
                              bufData);

        // Store this byte to be included in the next VRPuppet_DataXX
        // command.
        bufLen = 1;
        bufData = *src;
      }
    } else {
      // This byte is the same.
      // If there are bytes in bufData, the span has now ended and we must
      // emit a VRPuppet_DataXX command for the accumulated bytes.
      // purgeBuffer will not emit any commands if there are no bytes
      // accumulated.
      purgeBuffer();
    }
    // Advance to the next source and destination byte.
    ++src;
    ++dst;
  }
  // In the event that the very last byte of the structs differ, we must
  // ensure that the accumulated bytes are emitted as a VRPuppet_DataXX
  // command.
  purgeBuffer();
}

}  // namespace mozilla::gfx