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

#include "mozilla/dom/Document.h"

#include "xpcAccessiblePivot.h"
#include "nsAccessibilityService.h"
#include "xpcAccessibleApplication.h"
#include "xpcAccessibleDocument.h"
#include "xpcAccessibleTextLeafRange.h"

#ifdef A11Y_LOG
#  include "Logging.h"
#endif

using namespace mozilla;
using namespace mozilla::a11y;
using namespace mozilla::dom;

xpcAccessibilityService* xpcAccessibilityService::gXPCAccessibilityService =
    nullptr;

////////////////////////////////////////////////////////////////////////////////
// nsISupports

void xpcAccessibilityService::ShutdownCallback(nsITimer* aTimer,
                                               void* aClosure) {
  MaybeShutdownAccService(nsAccessibilityService::eXPCOM);
  xpcAccessibilityService* xpcAccService =
      reinterpret_cast<xpcAccessibilityService*>(aClosure);

  if (xpcAccService->mShutdownTimer) {
    xpcAccService->mShutdownTimer->Cancel();
    xpcAccService->mShutdownTimer = nullptr;
  }
}

NS_IMETHODIMP_(MozExternalRefCountType)
xpcAccessibilityService::AddRef(void) {
  MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(xpcAccessibilityService)
  MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt");
  if (!nsAutoRefCnt::isThreadSafe) {
    NS_ASSERT_OWNINGTHREAD(xpcAccessibilityService);
  }
  nsrefcnt count = ++mRefCnt;
  NS_LOG_ADDREF(this, count, "xpcAccessibilityService", sizeof(*this));

  // We want refcount to be > 1 because one reference is added in the XPCOM
  // accessibility service getter.
  if (mRefCnt > 1) {
    if (mShutdownTimer) {
      mShutdownTimer->Cancel();
      mShutdownTimer = nullptr;
    }

    GetOrCreateAccService(nsAccessibilityService::eXPCOM);
  }

  return count;
}

NS_IMETHODIMP_(MozExternalRefCountType)
xpcAccessibilityService::Release(void) {
  MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");

  if (!nsAutoRefCnt::isThreadSafe) {
    NS_ASSERT_OWNINGTHREAD(xpcAccessibilityService);
  }

  nsrefcnt count = --mRefCnt;
  NS_LOG_RELEASE(this, count, "xpcAccessibilityService");

  if (count == 0) {
    if (!nsAutoRefCnt::isThreadSafe) {
      NS_ASSERT_OWNINGTHREAD(xpcAccessibilityService);
    }

    mRefCnt = 1; /* stabilize */
    delete (this);
    return 0;
  }

  // When ref count goes down to 1 (held internally as a static reference),
  // it means that there are no more external references to the
  // xpcAccessibilityService and we can attempt to shut down acceessiblity
  // service.
  if (count == 1 && !mShutdownTimer) {
    NS_NewTimerWithFuncCallback(
        getter_AddRefs(mShutdownTimer), ShutdownCallback, this, 100,
        nsITimer::TYPE_ONE_SHOT, "xpcAccessibilityService::Release"_ns);
  }

  return count;
}

NS_IMPL_QUERY_INTERFACE(xpcAccessibilityService, nsIAccessibilityService)

NS_IMETHODIMP
xpcAccessibilityService::GetApplicationAccessible(
    nsIAccessible** aAccessibleApplication) {
  NS_ENSURE_ARG_POINTER(aAccessibleApplication);

  NS_IF_ADDREF(*aAccessibleApplication = XPCApplicationAcc());
  return NS_OK;
}

NS_IMETHODIMP
xpcAccessibilityService::GetAccessibleFor(nsINode* aNode,
                                          nsIAccessible** aAccessible) {
  NS_ENSURE_ARG_POINTER(aAccessible);
  *aAccessible = nullptr;
  if (!aNode) {
    return NS_OK;
  }

  nsAccessibilityService* accService = GetAccService();
  if (!accService) {
    return NS_ERROR_SERVICE_NOT_AVAILABLE;
  }

  DocAccessible* document = accService->GetDocAccessible(aNode->OwnerDoc());
  if (document) {
    NS_IF_ADDREF(*aAccessible =
                     ToXPC(document->GetAccessibleEvenIfNotInMap(aNode)));
  }

  return NS_OK;
}

NS_IMETHODIMP
xpcAccessibilityService::GetAccessibleDescendantFor(
    nsINode* aNode, nsIAccessible** aAccessible) {
  NS_ENSURE_ARG_POINTER(aAccessible);
  *aAccessible = nullptr;
  if (!aNode) {
    return NS_OK;
  }

  nsAccessibilityService* accService = GetAccService();
  if (!accService) {
    return NS_ERROR_SERVICE_NOT_AVAILABLE;
  }

  DocAccessible* document = accService->GetDocAccessible(aNode->OwnerDoc());
  if (document) {
    NS_IF_ADDREF(*aAccessible =
                     ToXPC(document->GetAccessibleOrDescendant(aNode)));
  }

  return NS_OK;
}

NS_IMETHODIMP
xpcAccessibilityService::GetStringRole(uint32_t aRole, nsAString& aString) {
  nsAccessibilityService* accService = GetAccService();
  if (!accService) {
    return NS_ERROR_SERVICE_NOT_AVAILABLE;
  }

  accService->GetStringRole(aRole, aString);
  return NS_OK;
}

NS_IMETHODIMP
xpcAccessibilityService::GetStringStates(uint32_t aState, uint32_t aExtraState,
                                         nsISupports** aStringStates) {
  nsAccessibilityService* accService = GetAccService();
  if (!accService) {
    return NS_ERROR_SERVICE_NOT_AVAILABLE;
  }

  accService->GetStringStates(aState, aExtraState, aStringStates);
  return NS_OK;
}

NS_IMETHODIMP
xpcAccessibilityService::GetStringEventType(uint32_t aEventType,
                                            nsAString& aString) {
  nsAccessibilityService* accService = GetAccService();
  if (!accService) {
    return NS_ERROR_SERVICE_NOT_AVAILABLE;
  }

  accService->GetStringEventType(aEventType, aString);
  return NS_OK;
}

NS_IMETHODIMP
xpcAccessibilityService::GetStringRelationType(uint32_t aRelationType,
                                               nsAString& aString) {
  nsAccessibilityService* accService = GetAccService();
  if (!accService) {
    return NS_ERROR_SERVICE_NOT_AVAILABLE;
  }

  accService->GetStringRelationType(aRelationType, aString);
  return NS_OK;
}

NS_IMETHODIMP
xpcAccessibilityService::GetAccessibleFromCache(nsINode* aNode,
                                                nsIAccessible** aAccessible) {
  NS_ENSURE_ARG_POINTER(aAccessible);
  *aAccessible = nullptr;
  if (!aNode) {
    return NS_OK;
  }

  nsAccessibilityService* accService = GetAccService();
  if (!accService) {
    return NS_ERROR_SERVICE_NOT_AVAILABLE;
  }

  // Search for an accessible in each of our per document accessible object
  // caches. If we don't find it, and the given node is itself a document, check
  // our cache of document accessibles (document cache). Note usually shutdown
  // document accessibles are not stored in the document cache, however an
  // "unofficially" shutdown document (i.e. not from DocManager) can still
  // exist in the document cache.
  LocalAccessible* accessible = accService->FindAccessibleInCache(aNode);
  if (!accessible && aNode->IsDocument()) {
    accessible = mozilla::a11y::GetExistingDocAccessible(aNode->AsDocument());
  }

  NS_IF_ADDREF(*aAccessible = ToXPC(accessible));
  return NS_OK;
}

NS_IMETHODIMP
xpcAccessibilityService::SetCacheDomains(uint64_t aCacheDomains) {
  nsAccessibilityService* accService = GetAccService();
  if (!accService) {
    return NS_ERROR_SERVICE_NOT_AVAILABLE;
  }

  accService->SetCacheDomains(aCacheDomains);
  return NS_OK;
}

NS_IMETHODIMP
xpcAccessibilityService::CreateAccessiblePivot(nsIAccessible* aRoot,
                                               nsIAccessiblePivot** aPivot) {
  NS_ENSURE_ARG_POINTER(aPivot);
  NS_ENSURE_ARG(aRoot);
  *aPivot = nullptr;

  xpcAccessiblePivot* pivot = new xpcAccessiblePivot(aRoot);
  NS_ADDREF(*aPivot = pivot);

  return NS_OK;
}

NS_IMETHODIMP
xpcAccessibilityService::CreateTextLeafPoint(
    nsIAccessible* aAccessible, int32_t aOffset,
    nsIAccessibleTextLeafPoint** aPoint) {
  NS_ENSURE_ARG_POINTER(aPoint);
  NS_ENSURE_ARG(aAccessible);
  *aPoint = nullptr;

  auto point = MakeRefPtr<xpcAccessibleTextLeafPoint>(aAccessible, aOffset);
  point.forget(aPoint);

  return NS_OK;
}

NS_IMETHODIMP
xpcAccessibilityService::SetLogging(const nsACString& aModules) {
#ifdef A11Y_LOG
  logging::Enable(PromiseFlatCString(aModules));
#endif
  return NS_OK;
}

NS_IMETHODIMP
xpcAccessibilityService::IsLogged(const nsAString& aModule, bool* aIsLogged) {
  NS_ENSURE_ARG_POINTER(aIsLogged);
  *aIsLogged = false;

#ifdef A11Y_LOG
  *aIsLogged = logging::IsEnabled(aModule);
#endif

  return NS_OK;
}

NS_IMETHODIMP
xpcAccessibilityService::GetConsumers(nsAString& aString) {
  nsAccessibilityService* accService = GetAccService();
  if (!accService) {
    return NS_ERROR_SERVICE_NOT_AVAILABLE;
  }

  accService->GetConsumers(aString);
  return NS_OK;
}

////////////////////////////////////////////////////////////////////////////////
// NS_GetAccessibilityService
////////////////////////////////////////////////////////////////////////////////

nsresult NS_GetAccessibilityService(nsIAccessibilityService** aResult) {
  NS_ENSURE_TRUE(aResult, NS_ERROR_NULL_POINTER);
  *aResult = nullptr;

  if (!GetOrCreateAccService(nsAccessibilityService::eXPCOM)) {
    return NS_ERROR_SERVICE_NOT_AVAILABLE;
  }

  xpcAccessibilityService* service = new xpcAccessibilityService();
  xpcAccessibilityService::gXPCAccessibilityService = service;
  NS_ADDREF(*aResult = service);

  return NS_OK;
}
