/* -*- 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 "SpeakerManager.h"

#include "mozilla/Services.h"

#include "mozilla/dom/Event.h"

#include "AudioChannelService.h"
#include "nsIDocShell.h"
#include "nsIDOMClassInfo.h"
#include "nsIDOMEventListener.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIPermissionManager.h"
#include "SpeakerManagerService.h"

namespace mozilla {
namespace dom {

NS_IMPL_QUERY_INTERFACE_INHERITED(SpeakerManager, DOMEventTargetHelper,
                                  nsIDOMEventListener)
NS_IMPL_ADDREF_INHERITED(SpeakerManager, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(SpeakerManager, DOMEventTargetHelper)

SpeakerManager::SpeakerManager()
  : mForcespeaker(false)
  , mVisible(false)
{
  SpeakerManagerService *service =
    SpeakerManagerService::GetOrCreateSpeakerManagerService();
  MOZ_ASSERT(service);
  service->RegisterSpeakerManager(this);
}

SpeakerManager::~SpeakerManager()
{
  SpeakerManagerService *service = SpeakerManagerService::GetOrCreateSpeakerManagerService();
  MOZ_ASSERT(service);

  service->UnRegisterSpeakerManager(this);
  nsCOMPtr<EventTarget> target = do_QueryInterface(GetOwner());
  NS_ENSURE_TRUE_VOID(target);

  target->RemoveSystemEventListener(NS_LITERAL_STRING("visibilitychange"),
                                    this,
                                    /* useCapture = */ true);
}

bool
SpeakerManager::Speakerforced()
{
  // If a background app calls forcespeaker=true that doesn't change anything.
  // 'speakerforced' remains false everywhere.
  if (mForcespeaker && !mVisible) {
    return false;
  }
  SpeakerManagerService *service = SpeakerManagerService::GetOrCreateSpeakerManagerService();
  MOZ_ASSERT(service);
  return service->GetSpeakerStatus();

}

bool
SpeakerManager::Forcespeaker()
{
  return mForcespeaker;
}

void
SpeakerManager::SetForcespeaker(bool aEnable)
{
  SpeakerManagerService *service = SpeakerManagerService::GetOrCreateSpeakerManagerService();
  MOZ_ASSERT(service);

  service->ForceSpeaker(aEnable, mVisible);
  mForcespeaker = aEnable;
}

void
SpeakerManager::DispatchSimpleEvent(const nsAString& aStr)
{
  MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
  nsresult rv = CheckInnerWindowCorrectness();
  if (NS_FAILED(rv)) {
    return;
  }

  RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
  event->InitEvent(aStr, false, false);
  event->SetTrusted(true);

  rv = DispatchDOMEvent(nullptr, event, nullptr, nullptr);
  if (NS_FAILED(rv)) {
    NS_ERROR("Failed to dispatch the event!!!");
    return;
  }
}

void
SpeakerManager::Init(nsPIDOMWindowInner* aWindow)
{
  BindToOwner(aWindow);

  nsCOMPtr<nsIDocShell> docshell = GetOwner()->GetDocShell();
  NS_ENSURE_TRUE_VOID(docshell);
  docshell->GetIsActive(&mVisible);

  nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(GetOwner());
  NS_ENSURE_TRUE_VOID(target);

  target->AddSystemEventListener(NS_LITERAL_STRING("visibilitychange"),
                                 this,
                                 /* useCapture = */ true,
                                 /* wantsUntrusted = */ false);
}

nsPIDOMWindowInner*
SpeakerManager::GetParentObject() const
{
  return GetOwner();
}

/* static */ already_AddRefed<SpeakerManager>
SpeakerManager::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv)
{
  nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aGlobal.GetAsSupports());
  if (!sgo) {
    aRv.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

  nsCOMPtr<nsPIDOMWindowInner> ownerWindow = do_QueryInterface(aGlobal.GetAsSupports());
  if (!ownerWindow) {
    aRv.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

  nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
  NS_ENSURE_TRUE(permMgr, nullptr);

  uint32_t permission = nsIPermissionManager::DENY_ACTION;
  nsresult rv =
    permMgr->TestPermissionFromWindow(ownerWindow, "speaker-control",
                                      &permission);
  NS_ENSURE_SUCCESS(rv, nullptr);

  if (permission != nsIPermissionManager::ALLOW_ACTION) {
    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
    return nullptr;
  }

  RefPtr<SpeakerManager> object = new SpeakerManager();
  object->Init(ownerWindow);
  return object.forget();
}

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

NS_IMETHODIMP
SpeakerManager::HandleEvent(nsIDOMEvent* aEvent)
{
  nsAutoString type;
  aEvent->GetType(type);

  if (!type.EqualsLiteral("visibilitychange")) {
    return NS_ERROR_FAILURE;
  }

  nsCOMPtr<nsIDocShell> docshell = do_GetInterface(GetOwner());
  NS_ENSURE_TRUE(docshell, NS_ERROR_FAILURE);
  docshell->GetIsActive(&mVisible);

  // If an app that has called forcespeaker=true is switched
  // from the background to the foreground 'speakerforced'
  // switches to true in all apps. I.e. the app doesn't have to
  // call forcespeaker=true again when it comes into foreground.
  SpeakerManagerService *service =
    SpeakerManagerService::GetOrCreateSpeakerManagerService();
  MOZ_ASSERT(service);

  if (mVisible && mForcespeaker) {
    service->ForceSpeaker(mForcespeaker, mVisible);
  }
  // If an application that has called forcespeaker=true, but no audio is
  // currently playing in the app itself, if application switch to
  // the background, we switch 'speakerforced' to false.
  if (!mVisible && mForcespeaker) {
    RefPtr<AudioChannelService> audioChannelService =
      AudioChannelService::GetOrCreate();
    if (audioChannelService && !audioChannelService->AnyAudioChannelIsActive()) {
      service->ForceSpeaker(false, mVisible);
    }
  }
  return NS_OK;
}

void
SpeakerManager::SetAudioChannelActive(bool isActive)
{
  if (mForcespeaker) {
    SpeakerManagerService *service =
      SpeakerManagerService::GetOrCreateSpeakerManagerService();
    MOZ_ASSERT(service);
    service->ForceSpeaker(isActive, mVisible);
  }
}

} // namespace dom
} // namespace mozilla