/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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 "AudioContext.h"
#include "AudioListener.h"
#include "MediaTrackGraphImpl.h"
#include "Tracing.h"
#include "mozilla/dom/AudioListenerBinding.h"

namespace mozilla::dom {

NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(AudioListener, mContext)

AudioListenerEngine::AudioListenerEngine()
    : mPosition(), mFrontVector(0., 0., -1.), mRightVector(1., 0., 0.) {}

void AudioListenerEngine::RecvListenerEngineEvent(
    AudioListenerEngine::AudioListenerParameter aParameter,
    const ThreeDPoint& aValue) {
  switch (aParameter) {
    case AudioListenerParameter::POSITION:
      mPosition = aValue;
      break;
    case AudioListenerParameter::FRONT:
      mFrontVector = aValue;
      break;
    case AudioListenerParameter::RIGHT:
      mRightVector = aValue;
      break;
    default:
      MOZ_CRASH("Not handled");
  }
}

const ThreeDPoint& AudioListenerEngine::Position() const { return mPosition; }
const ThreeDPoint& AudioListenerEngine::FrontVector() const {
  return mFrontVector;
}
const ThreeDPoint& AudioListenerEngine::RightVector() const {
  return mRightVector;
}

AudioListener::AudioListener(AudioContext* aContext)
    : mContext(aContext),
      mEngine(new AudioListenerEngine()),
      mPosition(),
      mFrontVector(0., 0., -1.),
      mRightVector(1., 0., 0.) {
  MOZ_ASSERT(aContext);
}

JSObject* AudioListener::WrapObject(JSContext* aCx,
                                    JS::Handle<JSObject*> aGivenProto) {
  return AudioListener_Binding::Wrap(aCx, this, aGivenProto);
}

void AudioListener::SetOrientation(double aX, double aY, double aZ, double aXUp,
                                   double aYUp, double aZUp) {
  ThreeDPoint front(aX, aY, aZ);
  // The panning effect and the azimuth and elevation calculation in the Web
  // Audio spec becomes undefined with linearly dependent vectors, so keep
  // existing state in these situations.
  if (front.IsZero()) {
    return;
  }
  // Normalize before using CrossProduct() to avoid overflow.
  front.Normalize();
  ThreeDPoint up(aXUp, aYUp, aZUp);
  if (up.IsZero()) {
    return;
  }
  up.Normalize();
  ThreeDPoint right = front.CrossProduct(up);
  if (right.IsZero()) {
    return;
  }
  right.Normalize();

  if (!mFrontVector.FuzzyEqual(front)) {
    mFrontVector = front;
    SendListenerEngineEvent(AudioListenerEngine::AudioListenerParameter::FRONT,
                            mFrontVector);
  }
  if (!mRightVector.FuzzyEqual(right)) {
    mRightVector = right;
    SendListenerEngineEvent(AudioListenerEngine::AudioListenerParameter::RIGHT,
                            mRightVector);
  }
}

void AudioListener::SetPosition(double aX, double aY, double aZ) {
  if (WebAudioUtils::FuzzyEqual(mPosition.x, aX) &&
      WebAudioUtils::FuzzyEqual(mPosition.y, aY) &&
      WebAudioUtils::FuzzyEqual(mPosition.z, aZ)) {
    return;
  }
  mPosition.x = aX;
  mPosition.y = aY;
  mPosition.z = aZ;
  SendListenerEngineEvent(AudioListenerEngine::AudioListenerParameter::POSITION,
                          mPosition);
}

void AudioListener::SendListenerEngineEvent(
    AudioListenerEngine::AudioListenerParameter aParameter,
    const ThreeDPoint& aValue) {
  class Message final : public ControlMessage {
   public:
    Message(AudioListenerEngine* aEngine,
            AudioListenerEngine::AudioListenerParameter aParameter,
            const ThreeDPoint& aValue)
        : ControlMessage(nullptr),
          mEngine(aEngine),
          mParameter(aParameter),
          mValue(aValue) {}
    void Run() override {
      TRACE("AudioListener::RecvListenerEngineEvent");
      mEngine->RecvListenerEngineEvent(mParameter, mValue);
    }
    RefPtr<AudioListenerEngine> mEngine;
    AudioListenerEngine::AudioListenerParameter mParameter;
    ThreeDPoint mValue;
  };

  mContext->DestinationTrack()->GraphImpl()->AppendMessage(
      MakeUnique<Message>(Engine(), aParameter, aValue));
}

size_t AudioListener::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
  return aMallocSizeOf(this);
}

}  // namespace mozilla::dom
