#include <ivrsystem.h>
#include <openvr-util.h>

#include <array>
#include <node.h>
#include <openvr.h>

#include <defines.h>

using namespace v8;

using TrackedDevicePoseArray = std::array<vr::TrackedDevicePose_t, vr::k_unMaxTrackedDeviceCount>;
using TrackedDeviceIndexArray = std::array<vr::TrackedDeviceIndex_t, vr::k_unMaxTrackedDeviceCount>;

//=============================================================================
void IVRSystem::Init(Nan::Persistent<v8::Function> &constructor) {
  // Create a function template that is called in JS to create this wrapper.
  Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);

  // Declare human-readable name for this wrapper.
  tpl->SetClassName(Nan::New("IVRSystem").ToLocalChecked());

  // Declare the stored number of fields (just the wrapped C++ object).
  tpl->InstanceTemplate()->SetInternalFieldCount(1);

  // Assign all the wrapped methods of this object.
  Nan::SetPrototypeMethod(tpl, "GetRecommendedRenderTargetSize", GetRecommendedRenderTargetSize);
  Nan::SetPrototypeMethod(tpl, "GetProjectionMatrix", GetProjectionMatrix);
  Nan::SetPrototypeMethod(tpl, "GetProjectionRaw", GetProjectionRaw);
  Nan::SetPrototypeMethod(tpl, "ComputeDistortion", ComputeDistortion);
  Nan::SetPrototypeMethod(tpl, "GetEyeToHeadTransform", GetEyeToHeadTransform);
  Nan::SetPrototypeMethod(tpl, "GetTimeSinceLastVsync", GetTimeSinceLastVsync);
  Nan::SetPrototypeMethod(tpl, "GetD3D9AdapterIndex", GetD3D9AdapterIndex);
  Nan::SetPrototypeMethod(tpl, "GetDXGIOutputInfo", GetDXGIOutputInfo);
  Nan::SetPrototypeMethod(tpl, "IsDisplayOnDesktop", IsDisplayOnDesktop);
  Nan::SetPrototypeMethod(tpl, "SetDisplayVisibility", SetDisplayVisibility);
  Nan::SetPrototypeMethod(tpl, "GetDeviceToAbsoluteTrackingPose", GetDeviceToAbsoluteTrackingPose);
  Nan::SetPrototypeMethod(tpl, "ResetSeatedZeroPose", ResetSeatedZeroPose);
  Nan::SetPrototypeMethod(tpl, "GetSeatedZeroPoseToStandingAbsoluteTrackingPose", GetSeatedZeroPoseToStandingAbsoluteTrackingPose);
  Nan::SetPrototypeMethod(tpl, "GetRawZeroPoseToStandingAbsoluteTrackingPose", GetRawZeroPoseToStandingAbsoluteTrackingPose);
  Nan::SetPrototypeMethod(tpl, "GetSortedTrackedDeviceIndicesOfClass", GetSortedTrackedDeviceIndicesOfClass);
  Nan::SetPrototypeMethod(tpl, "GetTrackedDeviceActivityLevel", GetTrackedDeviceActivityLevel);
  Nan::SetPrototypeMethod(tpl, "ApplyTransform", ApplyTransform);

  /// virtual vr::TrackedDeviceIndex_t GetTrackedDeviceIndexForControllerRole( vr::ETrackedControllerRole unDeviceType ) = 0;
  Nan::SetPrototypeMethod(tpl, "GetTrackedDeviceIndexForControllerRole", GetTrackedDeviceIndexForControllerRole);
  
  /// virtual vr::ETrackedControllerRole GetControllerRoleForTrackedDeviceIndex( vr::TrackedDeviceIndex_t unDeviceIndex ) = 0;

  Nan::SetPrototypeMethod(tpl, "GetTrackedDeviceClass", GetTrackedDeviceClass);
  Nan::SetPrototypeMethod(tpl, "GetControllerState", GetControllerState);

  /// virtual bool IsTrackedDeviceConnected( vr::TrackedDeviceIndex_t unDeviceIndex ) = 0;
  /// virtual bool GetBoolTrackedDeviceProperty( vr::TrackedDeviceIndex_t unDeviceIndex, ETrackedDeviceProperty prop, ETrackedPropertyError *pError = 0L ) = 0;
  /// virtual float GetFloatTrackedDeviceProperty( vr::TrackedDeviceIndex_t unDeviceIndex, ETrackedDeviceProperty prop, ETrackedPropertyError *pError = 0L ) = 0;
  /// virtual int32_t GetInt32TrackedDeviceProperty( vr::TrackedDeviceIndex_t unDeviceIndex, ETrackedDeviceProperty prop, ETrackedPropertyError *pError = 0L ) = 0;
  /// virtual uint64_t GetUint64TrackedDeviceProperty( vr::TrackedDeviceIndex_t unDeviceIndex, ETrackedDeviceProperty prop, ETrackedPropertyError *pError = 0L ) = 0;
  /// virtual HmdMatrix34_t GetMatrix34TrackedDeviceProperty( vr::TrackedDeviceIndex_t unDeviceIndex, ETrackedDeviceProperty prop, ETrackedPropertyError *pError = 0L ) = 0;
  /// virtual uint32_t GetStringTrackedDeviceProperty( vr::TrackedDeviceIndex_t unDeviceIndex, ETrackedDeviceProperty prop, VR_OUT_STRING() char *pchValue, uint32_t unBufferSize, ETrackedPropertyError *pError = 0L ) = 0;
  /// virtual const char *GetPropErrorNameFromEnum( ETrackedPropertyError error ) = 0;
  /// virtual bool PollNextEvent( VREvent_t *pEvent, uint32_t uncbVREvent ) = 0;
  /// virtual bool PollNextEventWithPose( ETrackingUniverseOrigin eOrigin, VREvent_t *pEvent, uint32_t uncbVREvent, vr::TrackedDevicePose_t *pTrackedDevicePose ) = 0;
  /// virtual const char *GetEventTypeNameFromEnum( EVREventType eType ) = 0;
  /// virtual HiddenAreaMesh_t GetHiddenAreaMesh( EVREye eEye ) = 0;
  /// virtual bool GetControllerState( vr::TrackedDeviceIndex_t unControllerDeviceIndex, vr::VRControllerState_t *pControllerState ) = 0;
  /// virtual bool GetControllerStateWithPose( ETrackingUniverseOrigin eOrigin, vr::TrackedDeviceIndex_t unControllerDeviceIndex, vr::VRControllerState_t *pControllerState, TrackedDevicePose_t *pTrackedDevicePose ) = 0;
  // virtual void TriggerHapticPulse( vr::TrackedDeviceIndex_t unControllerDeviceIndex, uint32_t unAxisId, unsigned short usDurationMicroSec ) = 0;
  Nan::SetPrototypeMethod(tpl, "TriggerHapticPulse", TriggerHapticPulse);
  /// virtual const char *GetButtonIdNameFromEnum( EVRButtonId eButtonId ) = 0;
  /// virtual const char *GetControllerAxisTypeNameFromEnum( EVRControllerAxisType eAxisType ) = 0;

  /* Nan::SetPrototypeMethod(tpl, "CaptureInputFocus", CaptureInputFocus);
  Nan::SetPrototypeMethod(tpl, "ReleaseInputFocus", ReleaseInputFocus);
  Nan::SetPrototypeMethod(tpl, "IsInputFocusCapturedByAnotherProcess", IsInputFocusCapturedByAnotherProcess); */

  /// virtual uint32_t DriverDebugRequest( vr::TrackedDeviceIndex_t unDeviceIndex, const char *pchRequest, char *pchResponseBuffer, uint32_t unResponseBufferSize ) = 0;
  /// virtual vr::EVRFirmwareError PerformFirmwareUpdate( vr::TrackedDeviceIndex_t unDeviceIndex ) = 0;

  Nan::SetPrototypeMethod(tpl, "AcknowledgeQuit_Exiting", AcknowledgeQuit_Exiting);
  Nan::SetPrototypeMethod(tpl, "AcknowledgeQuit_UserPrompt", AcknowledgeQuit_UserPrompt);

  Nan::SetPrototypeMethod(tpl, "GetModelName", GetModelName);

  // Set a static constructor function to reference the `New` function template.
  constructor.Reset(Nan::GetFunction(tpl).ToLocalChecked());
}

//=============================================================================
Local<Object> IVRSystem::NewInstance(vr::IVRSystem *system)
{
  Nan::EscapableHandleScope scope;
  Local<Function> cons = Nan::New(constructor());
  Local<Value> argv[1] = { Nan::New<External>(system) };
  return scope.Escape(Nan::NewInstance(cons, 1, argv).ToLocalChecked());
}

//=============================================================================
IVRSystem::IVRSystem(vr::IVRSystem *self)
: self_(self)
{
  {
    vr::EVRInputError error = vr::VRInput()->GetInputSourceHandle("/user/hand/left", &handInputSourceHandles[0]);
    if (error != vr::EVRInputError::VRInputError_None) {
      Nan::ThrowError("Failed to get left hand input source handle");
    }
  }
  {
    vr::EVRInputError error = vr::VRInput()->GetInputSourceHandle("/user/hand/right", &handInputSourceHandles[1]);
    if (error != vr::EVRInputError::VRInputError_None) {
      Nan::ThrowError("Failed to get right hand input source handle");
    }
  }
  {
    vr::EVRInputError error = vr::VRInput()->GetActionHandle("/actions/default/in/Pose", &poseActionHandle);
    if (error != vr::EVRInputError::VRInputError_None) {
      Nan::ThrowError("Failed to get left hand action handle");
    }
  }
  {
    vr::EVRInputError error = vr::VRInput()->GetActionHandle("/actions/default/in/SkeletonLeftHand", &handAnimActionHandles[0]);
    if (error != vr::EVRInputError::VRInputError_None) {
      Nan::ThrowError("Failed to get left hand anim action handle");
    }
  }
  {
    vr::EVRInputError error = vr::VRInput()->GetActionHandle("/actions/default/in/SkeletonRightHand", &handAnimActionHandles[1]);
    if (error != vr::EVRInputError::VRInputError_None) {
      Nan::ThrowError("Failed to get right hand anim action handle");
    }
  }
}

//=============================================================================
NAN_METHOD(IVRSystem::New)
{
  if (!info.IsConstructCall())
  {
    Nan::ThrowError("Use the `new` keyword when creating a new instance.");
    return;
  }

  if (info.Length() != 1 || !info[0]->IsExternal())
  {
    Nan::ThrowTypeError("Argument[0] must be an `IVRSystem*`.");
    return;
  }

  auto wrapped_instance = static_cast<vr::IVRSystem*>(
    Local<External>::Cast(info[0])->Value());
  IVRSystem *obj = new IVRSystem(wrapped_instance);
  obj->Wrap(info.This());
  info.GetReturnValue().Set(info.This());
}

//=============================================================================
// virtual void GetRecommendedRenderTargetSize( uint32_t *pnWidth, uint32_t *pnHeight ) = 0;
NAN_METHOD(IVRSystem::GetRecommendedRenderTargetSize)
{
  IVRSystem* obj = ObjectWrap::Unwrap<IVRSystem>(info.Holder());

  if (info.Length() != 0)
  {
    Nan::ThrowError("Wrong number of arguments.");
    return;
  }

  uint32_t nWidth, nHeight;
  obj->self_->GetRecommendedRenderTargetSize(&nWidth, &nHeight);

  Local<Object> result = Nan::New<Object>();
  {
    Local<String> width_prop = Nan::New<String>("width").ToLocalChecked();
    Nan::Set(result, width_prop, Nan::New<Number>(nWidth));

    Local<String> height_prop = Nan::New<String>("height").ToLocalChecked();
    Nan::Set(result, height_prop, Nan::New<Number>(nHeight));
  }
  info.GetReturnValue().Set(result);
}

//=============================================================================
/// virtual HmdMatrix44_t GetProjectionMatrix( EVREye eEye, float fNearZ, float fFarZ ) = 0;
NAN_METHOD(IVRSystem::GetProjectionMatrix)
{
  IVRSystem* obj = ObjectWrap::Unwrap<IVRSystem>(info.Holder());

  if (info.Length() != 4)
  {
    Nan::ThrowError("Wrong number of arguments.");
    return;
  }

  if (!info[0]->IsNumber())
  {
    Nan::ThrowTypeError("Argument[0] must be a number (EVREye).");
    return;
  }

  uint32_t nEye = TO_UINT32(info[0]);
  if (nEye >= 2)
  {
    Nan::ThrowTypeError("Argument[0] was out of enum range (EVREye).");
    return;
  }

  if (!info[1]->IsNumber())
  {
    Nan::ThrowTypeError("Argument[1] must be a number.");
    return;
  }

  if (!info[2]->IsNumber())
  {
    Nan::ThrowTypeError("Argument[2] must be a number.");
    return;
  }

  if (!info[3]->IsFloat32Array())
  {
    Nan::ThrowTypeError("Argument[3] must be a Float32Array.");
    return;
  }

  vr::EVREye eEye = static_cast<vr::EVREye>(nEye);
  float fNearZ = TO_FLOAT(info[1]);
  float fFarZ = TO_FLOAT(info[2]);
  vr::HmdMatrix44_t matrix = obj->self_->GetProjectionMatrix(eEye, fNearZ, fFarZ);

  Local<Float32Array> float32Array = Local<Float32Array>::Cast(info[3]);
  for (unsigned int v = 0; v < 4; v++) {
    for (unsigned int u = 0; u < 4; u++) {
      float32Array->Set(v * 4 + u, Number::New(Isolate::GetCurrent(), matrix.m[u][v]));
    }
  }
}

//=============================================================================
/// virtual void GetProjectionRaw( EVREye eEye, float *pfLeft, float *pfRight, float *pfTop, float *pfBottom ) = 0;
NAN_METHOD(IVRSystem::GetProjectionRaw)
{
  IVRSystem* obj = ObjectWrap::Unwrap<IVRSystem>(info.Holder());

  if (info.Length() != 2)
  {
    Nan::ThrowError("Wrong number of arguments.");
    return;
  }

  if (!info[0]->IsNumber())
  {
    Nan::ThrowTypeError("Argument[0] must be a number (EVREye).");
    return;
  }

  uint32_t nEye = TO_UINT32(info[0]);
  if (nEye >= 2)
  {
    Nan::ThrowTypeError("Argument[0] was out of enum range (EVREye).");
    return;
  }

  vr::EVREye eEye = static_cast<vr::EVREye>(nEye);
  float fLeft, fRight, fTop, fBottom;
  obj->self_->GetProjectionRaw(eEye, &fLeft, &fRight, &fTop, &fBottom);

  Local<Float32Array> float32Array = Local<Float32Array>::Cast(info[1]);
  float32Array->Set(0, Nan::New<Number>(fLeft));
  float32Array->Set(1, Nan::New<Number>(fRight));
  float32Array->Set(2, Nan::New<Number>(fTop));
  float32Array->Set(3, Nan::New<Number>(fBottom));
}

//=============================================================================
/// virtual bool ComputeDistortion( EVREye eEye, float fU, float fV, DistortionCoordinates_t *pDistortionCoordinates ) = 0;
NAN_METHOD(IVRSystem::ComputeDistortion)
{
  IVRSystem* obj = ObjectWrap::Unwrap<IVRSystem>(info.Holder());

  if (info.Length() != 3)
  {
    Nan::ThrowError("Wrong number of arguments.");
    return;
  }

  if (!info[0]->IsNumber())
  {
    Nan::ThrowTypeError("Argument[0] must be a number (EVREye).");
    return;
  }

  uint32_t nEye = TO_UINT32(info[0]);
  if (nEye >= 2)
  {
    Nan::ThrowTypeError("Argument[0] was out of enum range (EVREye).");
    return;
  }

  if (!info[1]->IsNumber())
  {
    Nan::ThrowTypeError("Argument[1] must be a number.");
    return;
  }

  if (!info[2]->IsNumber())
  {
    Nan::ThrowTypeError("Argument[2] must be a number.");
    return;
  }

  vr::EVREye eEye = static_cast<vr::EVREye>(nEye);
  float fU = TO_FLOAT(info[1]);
  float fV = TO_FLOAT(info[2]);
  vr::DistortionCoordinates_t distortionCoordinates;
  bool success = obj->self_->ComputeDistortion(eEye, fU, fV, &distortionCoordinates);

  if (!success)
  {
    Nan::ThrowError("Distortion coordinates not suitable.");
    return;
  }
  info.GetReturnValue().Set(encode(distortionCoordinates));
}

//=============================================================================
/// virtual HmdMatrix34_t GetEyeToHeadTransform( EVREye eEye ) = 0;
NAN_METHOD(IVRSystem::GetEyeToHeadTransform)
{
  IVRSystem* obj = ObjectWrap::Unwrap<IVRSystem>(info.Holder());

  if (info.Length() != 2)
  {
    Nan::ThrowError("Wrong number of arguments.");
    return;
  }

  if (!info[0]->IsNumber())
  {
    Nan::ThrowTypeError("Argument[0] must be a number (EVREye).");
    return;
  }

  if (!info[1]->IsFloat32Array())
  {
    Nan::ThrowTypeError("Argument[1] must be a Float32Array.");
    return;
  }

  uint32_t nEye = TO_UINT32(info[0]);
  if (nEye >= 2)
  {
    Nan::ThrowTypeError("Argument[0] was out of enum range (EVREye).");
    return;
  }

  vr::EVREye eEye = static_cast<vr::EVREye>(nEye);
  vr::HmdMatrix34_t matrix = obj->self_->GetEyeToHeadTransform(eEye);

  Local<Float32Array> float32Array = Local<Float32Array>::Cast(info[1]);
  for (unsigned int v = 0; v < 4; v++) {
    for (unsigned int u = 0; u < 3; u++) {
      float32Array->Set(v * 4 + u, Number::New(Isolate::GetCurrent(), matrix.m[u][v]));
    }
  }
  float32Array->Set(0 * 4 + 3, Number::New(Isolate::GetCurrent(), 0));
  float32Array->Set(1 * 4 + 3, Number::New(Isolate::GetCurrent(), 0));
  float32Array->Set(2 * 4 + 3, Number::New(Isolate::GetCurrent(), 0));
  float32Array->Set(3 * 4 + 3, Number::New(Isolate::GetCurrent(), 1));
}

//=============================================================================
/// virtual bool GetTimeSinceLastVsync( float *pfSecondsSinceLastVsync, uint64_t *pulFrameCounter ) = 0;
NAN_METHOD(IVRSystem::GetTimeSinceLastVsync)
{
  IVRSystem* obj = ObjectWrap::Unwrap<IVRSystem>(info.Holder());

  if (info.Length() != 0)
  {
    Nan::ThrowError("Wrong number of arguments.");
    return;
  }

  float fSecondsSinceLastVsync;
  uint64_t ulFrameCounter;
  obj->self_->GetTimeSinceLastVsync(&fSecondsSinceLastVsync, &ulFrameCounter);

  Local<Object> result = Nan::New<Object>();
  {
    Local<String> seconds_prop = Nan::New<String>("seconds").ToLocalChecked();
    Nan::Set(result, seconds_prop, Nan::New<Number>(fSecondsSinceLastVsync));

    // We can't return a 64-bit int, so we just have to return a 32-bit
    // truncation and hope that clients can deal with eventual overflow.
    Local<String> frame_prop = Nan::New<String>("frame").ToLocalChecked();
    Nan::Set(result, frame_prop, Nan::New<Number>(static_cast<uint32_t>(ulFrameCounter)));
  }
  info.GetReturnValue().Set(result);
}

//=============================================================================
/// virtual int32_t GetD3D9AdapterIndex() = 0;
NAN_METHOD(IVRSystem::GetD3D9AdapterIndex)
{
  IVRSystem* obj = ObjectWrap::Unwrap<IVRSystem>(info.Holder());

  if (info.Length() != 0)
  {
    Nan::ThrowError("Wrong number of arguments.");
    return;
  }

  uint32_t uIndex = obj->self_->GetD3D9AdapterIndex();
  info.GetReturnValue().Set(Nan::New<Number>(uIndex));
}

//=============================================================================
/// virtual void GetDXGIOutputInfo( int32_t *pnAdapterIndex ) = 0;
NAN_METHOD(IVRSystem::GetDXGIOutputInfo)
{
  IVRSystem* obj = ObjectWrap::Unwrap<IVRSystem>(info.Holder());

  if (info.Length() != 0)
  {
    Nan::ThrowError("Wrong number of arguments.");
    return;
  }

  int32_t nAdapterIndex;
  obj->self_->GetDXGIOutputInfo(&nAdapterIndex);
  info.GetReturnValue().Set(Nan::New<Number>(nAdapterIndex));
}

//=============================================================================
/// virtual bool IsDisplayOnDesktop() = 0;
NAN_METHOD(IVRSystem::IsDisplayOnDesktop)
{
  IVRSystem* obj = ObjectWrap::Unwrap<IVRSystem>(info.Holder());

  if (info.Length() != 0)
  {
    Nan::ThrowError("Wrong number of arguments.");
    return;
  }

  bool bIsVisibleOnDesktop = obj->self_->IsDisplayOnDesktop();
  info.GetReturnValue().Set(Nan::New<Boolean>(bIsVisibleOnDesktop));
}

//=============================================================================
/// virtual bool SetDisplayVisibility( bool bIsVisibleOnDesktop ) = 0;
NAN_METHOD(IVRSystem::SetDisplayVisibility)
{
  IVRSystem* obj = ObjectWrap::Unwrap<IVRSystem>(info.Holder());

  if (info.Length() != 1)
  {
    Nan::ThrowError("Wrong number of arguments.");
    return;
  }

  if (!info[0]->IsBoolean())
  {
    Nan::ThrowTypeError("Argument[0] must be a boolean.");
    return;
  }

  bool bIsVisibleOnDesktop = TO_BOOL(info[0]);
  bool bSuccess = obj->self_->SetDisplayVisibility(bIsVisibleOnDesktop);
  info.GetReturnValue().Set(Nan::New<Boolean>(bSuccess));
}

//=============================================================================
/// virtual void GetDeviceToAbsoluteTrackingPose( ETrackingUniverseOrigin eOrigin, float fPredictedSecondsToPhotonsFromNow, VR_ARRAY_COUNT(unTrackedDevicePoseArrayCount) TrackedDevicePose_t *pTrackedDevicePoseArray, uint32_t unTrackedDevicePoseArrayCount ) = 0;
NAN_METHOD(IVRSystem::GetDeviceToAbsoluteTrackingPose)
{
  IVRSystem* obj = ObjectWrap::Unwrap<IVRSystem>(info.Holder());

  if (info.Length() != 4)
  {
    Nan::ThrowError("Wrong number of arguments.");
    return;
  }

  if (!info[0]->IsNumber())
  {
    Nan::ThrowTypeError("Argument[0] must be a number (ETrackingUniverseOrigin).");
    return;
  }

  uint32_t nOrigin = TO_UINT32(info[0]);
  if (nOrigin >= 3)
  {
    Nan::ThrowTypeError("Argument[0] was out of enum range (ETrackingUniverseOrigin).");
    return;
  }

  if (!info[1]->IsFloat32Array() || !info[2]->IsFloat32Array() || !info[3]->IsFloat32Array())
  {
    Nan::ThrowTypeError("Arguments[1-3] must be Float32Array.");
    return;
  }

  vr::ETrackingUniverseOrigin eOrigin = static_cast<vr::ETrackingUniverseOrigin>(nOrigin);

  float fSecondsSinceLastVsync;
  obj->self_->GetTimeSinceLastVsync( &fSecondsSinceLastVsync, NULL );
  const float fDisplayFrequency = obj->self_->GetFloatTrackedDeviceProperty( vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_DisplayFrequency_Float );
  const float fFrameDuration = 1.f / fDisplayFrequency;
  const float fVsyncToPhotons = obj->self_->GetFloatTrackedDeviceProperty( vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_SecondsFromVsyncToPhotons_Float );
  const float fPredictedSecondsToPhotonsFromNow = fFrameDuration - fSecondsSinceLastVsync + fVsyncToPhotons;

  TrackedDevicePoseArray trackedDevicePoseArray;
  obj->self_->GetDeviceToAbsoluteTrackingPose(
    eOrigin, fPredictedSecondsToPhotonsFromNow, trackedDevicePoseArray.data(),
    static_cast<uint32_t>(trackedDevicePoseArray.size())
  );

  Local<Float32Array> hmdFloat32Array = Local<Float32Array>::Cast(info[1]);
  Local<Float32Array> leftControllerFloat32Array = Local<Float32Array>::Cast(info[2]);
  Local<Float32Array> rightControllerFloat32Array = Local<Float32Array>::Cast(info[3]);
  hmdFloat32Array->Set(0, Number::New(Isolate::GetCurrent(), std::numeric_limits<float>::quiet_NaN()));
  leftControllerFloat32Array->Set(0, Number::New(Isolate::GetCurrent(), std::numeric_limits<float>::quiet_NaN()));
  rightControllerFloat32Array->Set(0, Number::New(Isolate::GetCurrent(), std::numeric_limits<float>::quiet_NaN()));

  for (unsigned int i = 0; i < trackedDevicePoseArray.size(); i++) {
    const vr::TrackedDevicePose_t &trackedDevicePose = trackedDevicePoseArray[i];
    if (trackedDevicePose.bPoseIsValid) {
      const vr::ETrackedDeviceClass deviceClass = obj->self_->GetTrackedDeviceClass(i);
      if (deviceClass == vr::TrackedDeviceClass_HMD) {
        const vr::HmdMatrix34_t &matrix = trackedDevicePose.mDeviceToAbsoluteTracking;

        for (unsigned int v = 0; v < 4; v++) {
          for (unsigned int u = 0; u < 3; u++) {
            hmdFloat32Array->Set(v * 4 + u, Number::New(Isolate::GetCurrent(), matrix.m[u][v]));
          }
        }
        hmdFloat32Array->Set(0 * 4 + 3, Number::New(Isolate::GetCurrent(), 0));
        hmdFloat32Array->Set(1 * 4 + 3, Number::New(Isolate::GetCurrent(), 0));
        hmdFloat32Array->Set(2 * 4 + 3, Number::New(Isolate::GetCurrent(), 0));
        hmdFloat32Array->Set(3 * 4 + 3, Number::New(Isolate::GetCurrent(), 1));
      } else if (deviceClass == vr::TrackedDeviceClass_Controller) {
        const vr::ETrackedControllerRole controllerRole = obj->self_->GetControllerRoleForTrackedDeviceIndex(i);
        if (controllerRole == vr::TrackedControllerRole_LeftHand) {
          const vr::HmdMatrix34_t &matrix = trackedDevicePose.mDeviceToAbsoluteTracking;

          for (unsigned int v = 0; v < 4; v++) {
            for (unsigned int u = 0; u < 3; u++) {
              leftControllerFloat32Array->Set(v * 4 + u, Number::New(Isolate::GetCurrent(), matrix.m[u][v]));
            }
          }
          leftControllerFloat32Array->Set(0 * 4 + 3, Number::New(Isolate::GetCurrent(), 0));
          leftControllerFloat32Array->Set(1 * 4 + 3, Number::New(Isolate::GetCurrent(), 0));
          leftControllerFloat32Array->Set(2 * 4 + 3, Number::New(Isolate::GetCurrent(), 0));
          leftControllerFloat32Array->Set(3 * 4 + 3, Number::New(Isolate::GetCurrent(), 1));
        } else if (controllerRole == vr::TrackedControllerRole_LeftHand) {
          const vr::HmdMatrix34_t &matrix = trackedDevicePose.mDeviceToAbsoluteTracking;

          for (unsigned int v = 0; v < 4; v++) {
            for (unsigned int u = 0; u < 3; u++) {
              rightControllerFloat32Array->Set(v * 4 + u, Number::New(Isolate::GetCurrent(), matrix.m[u][v]));
            }
          }
          rightControllerFloat32Array->Set(0 * 4 + 3, Number::New(Isolate::GetCurrent(), 0));
          rightControllerFloat32Array->Set(1 * 4 + 3, Number::New(Isolate::GetCurrent(), 0));
          rightControllerFloat32Array->Set(2 * 4 + 3, Number::New(Isolate::GetCurrent(), 0));
          rightControllerFloat32Array->Set(3 * 4 + 3, Number::New(Isolate::GetCurrent(), 1));
        }
      }
    }
  }
}

//=============================================================================
/// virtual void ResetSeatedZeroPose() = 0;
NAN_METHOD(IVRSystem::ResetSeatedZeroPose)
{
  IVRSystem* obj = ObjectWrap::Unwrap<IVRSystem>(info.Holder());

  if (info.Length() != 0)
  {
    Nan::ThrowError("Wrong number of arguments.");
    return;
  }

  obj->self_->ResetSeatedZeroPose();
}

//=============================================================================
/// virtual HmdMatrix34_t GetSeatedZeroPoseToStandingAbsoluteTrackingPose() = 0;
NAN_METHOD(IVRSystem::GetSeatedZeroPoseToStandingAbsoluteTrackingPose)
{
  IVRSystem* obj = ObjectWrap::Unwrap<IVRSystem>(info.Holder());

  if (info.Length() != 1)
  {
    Nan::ThrowError("Wrong number of arguments.");
    return;
  }

  if (!info[0]->IsFloat32Array())
  {
    Nan::ThrowTypeError("Argument[0] must be a Float32Array.");
    return;
  }

  const vr::HmdMatrix34_t &matrix = obj->self_->GetSeatedZeroPoseToStandingAbsoluteTrackingPose();
  Local<Float32Array> float32Array = Local<Float32Array>::Cast(info[0]);
  for (unsigned int v = 0; v < 4; v++) {
    for (unsigned int u = 0; u < 3; u++) {
      float32Array->Set(v * 4 + u, Number::New(Isolate::GetCurrent(), matrix.m[u][v]));
    }
  }
  float32Array->Set(0 * 4 + 3, Number::New(Isolate::GetCurrent(), 0));
  float32Array->Set(1 * 4 + 3, Number::New(Isolate::GetCurrent(), 0));
  float32Array->Set(2 * 4 + 3, Number::New(Isolate::GetCurrent(), 0));
  float32Array->Set(3 * 4 + 3, Number::New(Isolate::GetCurrent(), 1));
}

//=============================================================================
/// virtual HmdMatrix34_t GetRawZeroPoseToStandingAbsoluteTrackingPose() = 0;
NAN_METHOD(IVRSystem::GetRawZeroPoseToStandingAbsoluteTrackingPose)
{
  IVRSystem* obj = ObjectWrap::Unwrap<IVRSystem>(info.Holder());

  if (info.Length() != 0)
  {
    Nan::ThrowError("Wrong number of arguments.");
    return;
  }

  vr::HmdMatrix34_t matrix = obj->self_->GetRawZeroPoseToStandingAbsoluteTrackingPose();
  info.GetReturnValue().Set(encode(matrix));
}

//=============================================================================
/// virtual uint32_t GetSortedTrackedDeviceIndicesOfClass( ETrackedDeviceClass eTrackedDeviceClass, VR_ARRAY_COUNT(unTrackedDeviceIndexArrayCount) vr::TrackedDeviceIndex_t *punTrackedDeviceIndexArray, uint32_t unTrackedDeviceIndexArrayCount, vr::TrackedDeviceIndex_t unRelativeToTrackedDeviceIndex = k_unTrackedDeviceIndex_Hmd ) = 0;
NAN_METHOD(IVRSystem::GetSortedTrackedDeviceIndicesOfClass)
{
  IVRSystem* obj = ObjectWrap::Unwrap<IVRSystem>(info.Holder());

  if (info.Length() < 1 || info.Length() > 2)
  {
    Nan::ThrowError("Wrong number of arguments.");
    return;
  }

  if (!info[0]->IsNumber())
  {
    Nan::ThrowTypeError("Argument[0] must be a number (ETrackedDeviceClass).");
    return;
  }

  uint32_t nTrackedDeviceClass = TO_UINT32(info[0]);
  if (nTrackedDeviceClass >= 6)
  {
    Nan::ThrowTypeError("Argument[0] was out of enum range (ETrackedDeviceClass).");
    return;
  }

  vr::TrackedDeviceIndex_t unRelativeToTrackedDeviceIndex = 0;
  if (!info[1]->IsUndefined())
  {
    if (!info[1]->IsNumber())
    {
      Nan::ThrowTypeError("Argument[1] must be a number.");
      return;
    }
    else
    {
      unRelativeToTrackedDeviceIndex = TO_UINT32(info[1]);
    }
  }

  vr::ETrackedDeviceClass eTrackedDeviceClass =
    static_cast<vr::ETrackedDeviceClass>(nTrackedDeviceClass);
  TrackedDeviceIndexArray trackedDeviceIndexArray;
  uint32_t nDeviceIndices = obj->self_->GetSortedTrackedDeviceIndicesOfClass(
    eTrackedDeviceClass, trackedDeviceIndexArray.data(),
    static_cast<uint32_t>(trackedDeviceIndexArray.size()),
    unRelativeToTrackedDeviceIndex
  );

  info.GetReturnValue().Set(encode<(uint32_t)vr::k_unMaxTrackedDeviceCount>(trackedDeviceIndexArray, nDeviceIndices));
}

//=============================================================================
/// virtual EDeviceActivityLevel GetTrackedDeviceActivityLevel( vr::TrackedDeviceIndex_t unDeviceId ) = 0;
NAN_METHOD(IVRSystem::GetTrackedDeviceActivityLevel)
{
  IVRSystem* obj = ObjectWrap::Unwrap<IVRSystem>(info.Holder());

  if (info.Length() != 1)
  {
    Nan::ThrowError("Wrong number of arguments.");
    return;
  }

  if (!info[0]->IsNumber())
  {
    Nan::ThrowTypeError("Argument[0] must be a number.");
    return;
  }

  uint32_t unDeviceId = TO_UINT32(info[0]);
  vr::EDeviceActivityLevel deviceActivityLevel =
    obj->self_->GetTrackedDeviceActivityLevel(unDeviceId);
  info.GetReturnValue().Set(Nan::New<Number>(
    static_cast<uint32_t>(deviceActivityLevel)));
}

//=============================================================================
/// virtual void ApplyTransform( TrackedDevicePose_t *pOutputPose, const TrackedDevicePose_t *pTrackedDevicePose, const HmdMatrix34_t *pTransform ) = 0;
NAN_METHOD(IVRSystem::ApplyTransform)
{
  IVRSystem* obj = ObjectWrap::Unwrap<IVRSystem>(info.Holder());

  if (info.Length() != 2)
  {
    Nan::ThrowError("Wrong number of arguments.");
    return;
  }

  if (!info[0]->IsObject())
  {
    Nan::ThrowTypeError("Argument[0] must be a tracked device pose.");
    return;
  }

  if (!info[1]->IsArray())
  {
    Nan::ThrowTypeError("Argument[1] must be a 3x4 matrix.");
    return;
  }

  const auto trackedDevicePose = decode<vr::TrackedDevicePose_t>(info[0]);
  const auto transform = decode<vr::HmdMatrix34_t>(info[1]);
  vr::TrackedDevicePose_t outputPose;
  obj->self_->ApplyTransform(&outputPose, &trackedDevicePose, &transform);
  info.GetReturnValue().Set(encode(outputPose));
}

NAN_METHOD(IVRSystem::GetTrackedDeviceIndexForControllerRole)
{
  IVRSystem* obj = ObjectWrap::Unwrap<IVRSystem>(info.Holder());

  if (info.Length() != 1)
  {
    Nan::ThrowError("Wrong number of arguments.");
    return;
  }
  if (!info[0]->IsNumber())
  {
    Nan::ThrowTypeError("Argument[0] must be a number.");
    return;
  }

  vr::ETrackedControllerRole role = static_cast<vr::ETrackedControllerRole>(TO_UINT32(info[0]));
  vr::TrackedDeviceIndex_t deviceClass = obj->self_->GetTrackedDeviceIndexForControllerRole(role);
  info.GetReturnValue().Set(Nan::New<Number>(
    static_cast<uint32_t>(deviceClass)));
}

//=============================================================================
/// virtual ETrackedDeviceClass GetTrackedDeviceClass( vr::TrackedDeviceIndex_t unDeviceIndex ) = 0;
NAN_METHOD(IVRSystem::GetTrackedDeviceClass)
{
  IVRSystem* obj = ObjectWrap::Unwrap<IVRSystem>(info.Holder());

  if (info.Length() != 1)
  {
    Nan::ThrowError("Wrong number of arguments.");
    return;
  }

  if (!info[0]->IsNumber())
  {
    Nan::ThrowTypeError("Argument[0] must be a number.");
    return;
  }

  uint32_t unDeviceIndex = TO_UINT32(info[0]);
  vr::ETrackedDeviceClass trackedDeviceClass =
    obj->self_->GetTrackedDeviceClass(unDeviceIndex);
  info.GetReturnValue().Set(Nan::New<Number>(
    static_cast<uint32_t>(trackedDeviceClass)));
}

NAN_METHOD(IVRSystem::GetControllerState)
{
  IVRSystem* obj = ObjectWrap::Unwrap<IVRSystem>(info.Holder());

  if (info.Length() != 2)
  {
    Nan::ThrowError("Wrong number of arguments.");
    return;
  }

  if (!info[0]->IsNumber())
  {
    Nan::ThrowTypeError("Argument[0] must be a number.");
    return;
  }

  if (!info[1]->IsFloat32Array())
  {
    Nan::ThrowTypeError("Argument[1] must be a Float32Array.");
    return;
  }

  uint32_t side = TO_UINT32(info[0]);
  
  for (unsigned int i = 0; i < vr::k_unMaxTrackedDeviceCount; i++) {
    vr::ETrackedDeviceClass deviceClass = obj->self_->GetTrackedDeviceClass(i);
    if (deviceClass == vr::TrackedDeviceClass_Controller) {
      const vr::ETrackedControllerRole controllerRole = obj->self_->GetControllerRoleForTrackedDeviceIndex(i);
      if ((side == 0 && controllerRole == vr::TrackedControllerRole_LeftHand) || (side == 1 && controllerRole == vr::TrackedControllerRole_RightHand)) {
        vr::VRControllerState_t controllerState;
        if (obj->self_->GetControllerState(i, &controllerState, sizeof(controllerState))) {
          Local<Float32Array> buttons = Local<Float32Array>::Cast(info[1]);
          float *buttonsData = (float *)((char *)buttons->Buffer()->GetContents().Data() + buttons->ByteOffset());

          buttonsData[0] = 1;

          buttonsData[1] = (controllerState.ulButtonPressed & vr::ButtonMaskFromId(vr::k_EButton_System)) ? 1 : 0;
          buttonsData[2] = (controllerState.ulButtonPressed & vr::ButtonMaskFromId(vr::k_EButton_ApplicationMenu)) ? 1 : 0;
          buttonsData[3] = (controllerState.ulButtonPressed & vr::ButtonMaskFromId(vr::k_EButton_Grip)) ? 1 : 0;
          buttonsData[4] = (controllerState.ulButtonPressed & vr::ButtonMaskFromId(vr::k_EButton_SteamVR_Touchpad)) ? 1 : 0;
          buttonsData[5] = (controllerState.ulButtonPressed & vr::ButtonMaskFromId(vr::k_EButton_SteamVR_Trigger)) ? 1 : 0;

          buttonsData[6] = (controllerState.ulButtonTouched & vr::ButtonMaskFromId(vr::k_EButton_System)) ? 1 : 0;
          buttonsData[7] = (controllerState.ulButtonTouched & vr::ButtonMaskFromId(vr::k_EButton_ApplicationMenu)) ? 1 : 0;
          buttonsData[8] = (controllerState.ulButtonTouched & vr::ButtonMaskFromId(vr::k_EButton_Grip)) ? 1 : 0;
          buttonsData[9] = (controllerState.ulButtonTouched & vr::ButtonMaskFromId(vr::k_EButton_SteamVR_Touchpad)) ? 1 : 0;
          buttonsData[10] = (controllerState.ulButtonTouched & vr::ButtonMaskFromId(vr::k_EButton_SteamVR_Trigger)) ? 1 : 0;

          buttonsData[11] = controllerState.rAxis[0].x;
          buttonsData[12] = controllerState.rAxis[0].y;
          buttonsData[13] = controllerState.rAxis[1].x;
          buttonsData[14] = controllerState.rAxis[1].y;
          buttonsData[15] = controllerState.rAxis[2].x;
          buttonsData[16] = controllerState.rAxis[2].y;
          buttonsData[17] = controllerState.rAxis[3].x;
          buttonsData[18] = controllerState.rAxis[3].y;
          buttonsData[19] = controllerState.rAxis[4].x;
          buttonsData[20] = controllerState.rAxis[4].y;
          
          /* {
            vr::InputPoseActionData_t inputPoseActionData;
            vr::EVRInputError error = vr::VRInput()->GetPoseActionDataForNextFrame(obj->poseActionHandle, vr::ETrackingUniverseOrigin::TrackingUniverseStanding, &inputPoseActionData, sizeof(inputPoseActionData), obj->handInputSourceHandles[i]);
            if (error != vr::EVRInputError::VRInputError_None) {
              exerr << "failed to get left hand pose data: " << error << std::endl;
            }
            const vr::TrackedDevicePose_t &trackedDevicePose = inputPoseActionData.pose;
            const vr::HmdMatrix34_t &matrix = trackedDevicePose.mDeviceToAbsoluteTracking;
            float hmdArray[16];
            for (unsigned int v = 0; v < 4; v++) {
              for (unsigned int u = 0; u < 3; u++) {
                hmdArray[v * 4 + u] = matrix.m[u][v];
              }
            }
            hmdArray[0 * 4 + 3] = 0;
            hmdArray[1 * 4 + 3] = 0;
            hmdArray[2 * 4 + 3] = 0;
            hmdArray[3 * 4 + 3] = 1;
          } */

          for (size_t i = 0; i < (5 + 31*(3+4)); i++) {
            buttonsData[21 + i] = std::numeric_limits<float>::quiet_NaN();
          }
          
          vr::VRSkeletalSummaryData_t skeletalSummaryData;
          vr::EVRInputError error = vr::VRInput()->GetSkeletalSummaryData(obj->handAnimActionHandles[side], vr::EVRSummaryType::VRSummaryType_FromDevice, &skeletalSummaryData);
          if (error == vr::EVRInputError::VRInputError_None) {
            for (int j = 0; j < 5; j++) {
              buttonsData[21 + j] = skeletalSummaryData.flFingerCurl[j];
            }
          } /* else {
            exerr << "failed to get hand anim skeletal summary data: " << error << std::endl;
          } */

          vr::InputSkeletalActionData_t skeletalActionData;
          error = vr::VRInput()->GetSkeletalActionData(obj->handAnimActionHandles[side], &skeletalActionData, sizeof(skeletalActionData));
          if (error == vr::EVRInputError::VRInputError_None) {
            if (skeletalActionData.bActive) {
              uint32_t boneCount = 0;
              error = vr::VRInput()->GetBoneCount(obj->handAnimActionHandles[side], &boneCount);
              if (error == vr::EVRInputError::VRInputError_None) {
                std::vector<vr::VRBoneTransform_t> bones(boneCount);
                error = vr::VRInput()->GetSkeletalBoneData(obj->handAnimActionHandles[side], vr::EVRSkeletalTransformSpace::VRSkeletalTransformSpace_Model, vr::EVRSkeletalMotionRange::VRSkeletalMotionRange_WithController, bones.data(), boneCount);
                if (error == vr::EVRInputError::VRInputError_None) {
                  int index = 21 + 5;
                  for (int j = 0; j < bones.size(); j++) {
                    vr::VRBoneTransform_t &bone = bones[j];
                    vr::HmdVector4_t &position = bone.position;
                    if (position.v[0] != 0 || position.v[1] != 0 || position.v[2] != 0) { 
                      vr::HmdQuaternionf_t &orientation = bone.orientation;
                      buttonsData[index++] = position.v[0];
                      buttonsData[index++] = position.v[1];
                      buttonsData[index++] = position.v[2];
                      buttonsData[index++] = orientation.x;
                      buttonsData[index++] = orientation.y;
                      buttonsData[index++] = orientation.z;
                      buttonsData[index++] = orientation.w;
                    }
                  }
                } /* else {
                  exerr << "failed to get hand anim skeletal bone data: " << error << std::endl;
                } */
              } /* else {
                exerr << "failed to get hand anim bone count" << std::endl;
              } */
            }
          } /* else {
            exerr << "failed to get hand anim skeletal action data: " << error << std::endl;
          } */

          return info.GetReturnValue().Set(Nan::New<Boolean>(true));
        }
      }
    }
  }

  return info.GetReturnValue().Set(Nan::New<Boolean>(false));
}

NAN_METHOD(IVRSystem::TriggerHapticPulse)
{
  IVRSystem* obj = ObjectWrap::Unwrap<IVRSystem>(info.Holder());

  if (info.Length() != 3)
  {
    Nan::ThrowError("Wrong number of arguments.");
    return;
  }
  if (!info[0]->IsNumber())
  {
    Nan::ThrowTypeError("Argument[0] must be a number.");
    return;
  }
  if (!info[1]->IsNumber())
  {
    Nan::ThrowTypeError("Argument[1] must be a number.");
    return;
  }
  if (!info[2]->IsNumber())
  {
    Nan::ThrowTypeError("Argument[2] must be a number.");
    return;
  }
  
  vr::TrackedDeviceIndex_t unControllerDeviceIndex = TO_UINT32(info[0]);
  uint32_t unAxisId = TO_UINT32(info[1]);
  unsigned short usDurationMicroSec  = TO_UINT32(info[2]);

  obj->self_->TriggerHapticPulse(unControllerDeviceIndex, unAxisId, usDurationMicroSec);
}

//=============================================================================
/// virtual bool CaptureInputFocus() = 0;
/* NAN_METHOD(IVRSystem::CaptureInputFocus)
{
  IVRSystem* obj = ObjectWrap::Unwrap<IVRSystem>(info.Holder());

  if (info.Length() != 0)
  {
    Nan::ThrowError("Wrong number of arguments.");
    return;
  }

  const auto result = obj->self_->CaptureInputFocus();
  info.GetReturnValue().Set(Nan::New<Boolean>(result));
}

//=============================================================================
/// virtual void ReleaseInputFocus() = 0;
NAN_METHOD(IVRSystem::ReleaseInputFocus)
{
  IVRSystem* obj = ObjectWrap::Unwrap<IVRSystem>(info.Holder());

  if (info.Length() != 0)
  {
    Nan::ThrowError("Wrong number of arguments.");
    return;
  }

  obj->self_->ReleaseInputFocus();
}

//=============================================================================
/// virtual bool IsInputFocusCapturedByAnotherProcess() = 0;
NAN_METHOD(IVRSystem::IsInputFocusCapturedByAnotherProcess)
{
  IVRSystem* obj = ObjectWrap::Unwrap<IVRSystem>(info.Holder());

  if (info.Length() != 0)
  {
    Nan::ThrowError("Wrong number of arguments.");
    return;
  }

  const auto result = obj->self_->IsInputFocusCapturedByAnotherProcess();
  info.GetReturnValue().Set(Nan::New<Boolean>(result));
} */

//=============================================================================
/// virtual void AcknowledgeQuit_Exiting() = 0;
NAN_METHOD(IVRSystem::AcknowledgeQuit_Exiting)
{
  IVRSystem* obj = ObjectWrap::Unwrap<IVRSystem>(info.Holder());

  if (info.Length() != 0)
  {
    Nan::ThrowError("Wrong number of arguments.");
    return;
  }

  obj->self_->AcknowledgeQuit_Exiting();
}

//=============================================================================
/// virtual void AcknowledgeQuit_UserPrompt() = 0;
NAN_METHOD(IVRSystem::AcknowledgeQuit_UserPrompt)
{
  IVRSystem* obj = ObjectWrap::Unwrap<IVRSystem>(info.Holder());

  if (info.Length() != 0)
  {
    Nan::ThrowError("Wrong number of arguments.");
    return;
  }

  obj->self_->AcknowledgeQuit_UserPrompt();
}

NAN_METHOD(IVRSystem::GetModelName)
{
  IVRSystem* obj = ObjectWrap::Unwrap<IVRSystem>(info.Holder());

  if (info.Length() != 1)
  {
    Nan::ThrowError("Wrong number of arguments.");
    return;
  }

  uint32_t hand = TO_UINT32(info[0]);
  vr::ETrackedControllerRole role = static_cast<vr::ETrackedControllerRole>(hand + 1);
  vr::TrackedDeviceIndex_t deviceClass = obj->self_->GetTrackedDeviceIndexForControllerRole(role);

  char buf[4096];
  vr::TrackedPropertyError error;
  uint32_t size = obj->self_->GetStringTrackedDeviceProperty(deviceClass, vr::ETrackedDeviceProperty::Prop_ModelNumber_String, buf, sizeof(buf), &error);
  Local<String> modelName = Nan::New<String>(buf, size).ToLocalChecked();
  info.GetReturnValue().Set(modelName);
}

