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

#include "DocAccessible-inl.h"
#include "mozilla/a11y/DocAccessibleParent.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "nsAccUtils.h"
#include "xpcAccessibleImage.h"
#include "xpcAccessibleTable.h"
#include "xpcAccessibleTableCell.h"

using namespace mozilla;
using namespace mozilla::a11y;

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

NS_IMPL_QUERY_INTERFACE_INHERITED(xpcAccessibleDocument, xpcAccessibleHyperText,
                                  nsIAccessibleDocument)
NS_IMPL_ADDREF_INHERITED(xpcAccessibleDocument, xpcAccessibleHyperText)
NS_IMETHODIMP_(MozExternalRefCountType) xpcAccessibleDocument::Release(void) {
  nsrefcnt r = xpcAccessibleHyperText::Release();
  NS_LOG_RELEASE(this, r, "xpcAccessibleDocument");

  // The only reference to the xpcAccessibleDocument is in DocManager's cache.
  if (r == 1 && !!mIntl && mCache.Count() == 0) {
    if (mIntl->IsLocal()) {
      GetAccService()->RemoveFromXPCDocumentCache(mIntl->AsLocal()->AsDoc());
    } else {
      GetAccService()->RemoveFromRemoteXPCDocumentCache(
          mIntl->AsRemote()->AsDoc());
    }
  }
  return r;
}

////////////////////////////////////////////////////////////////////////////////
// nsIAccessibleDocument

NS_IMETHODIMP
xpcAccessibleDocument::GetURL(nsAString& aURL) {
  if (!mIntl) return NS_ERROR_FAILURE;

  nsAccUtils::DocumentURL(mIntl, aURL);
  return NS_OK;
}

NS_IMETHODIMP
xpcAccessibleDocument::GetTitle(nsAString& aTitle) {
  if (!Intl()) return NS_ERROR_FAILURE;

  nsAutoString title;
  Intl()->Title(title);
  aTitle = std::move(title);
  return NS_OK;
}

NS_IMETHODIMP
xpcAccessibleDocument::GetMimeType(nsAString& aType) {
  if (!mIntl) return NS_ERROR_FAILURE;

  nsAccUtils::DocumentMimeType(mIntl, aType);
  return NS_OK;
}

NS_IMETHODIMP
xpcAccessibleDocument::GetDocType(nsAString& aType) {
  if (!Intl()) return NS_ERROR_FAILURE;

  Intl()->DocType(aType);
  return NS_OK;
}

NS_IMETHODIMP
xpcAccessibleDocument::GetDOMDocument(dom::Document** aDOMDocument) {
  NS_ENSURE_ARG_POINTER(aDOMDocument);
  *aDOMDocument = nullptr;

  if (!Intl()) return NS_ERROR_FAILURE;

  if (Intl()->DocumentNode()) NS_ADDREF(*aDOMDocument = Intl()->DocumentNode());

  return NS_OK;
}

NS_IMETHODIMP
xpcAccessibleDocument::GetWindow(mozIDOMWindowProxy** aDOMWindow) {
  NS_ENSURE_ARG_POINTER(aDOMWindow);
  *aDOMWindow = nullptr;

  if (!Intl()) return NS_ERROR_FAILURE;

  NS_IF_ADDREF(*aDOMWindow = Intl()->DocumentNode()->GetWindow());
  return NS_OK;
}

NS_IMETHODIMP
xpcAccessibleDocument::GetParentDocument(nsIAccessibleDocument** aDocument) {
  NS_ENSURE_ARG_POINTER(aDocument);
  *aDocument = nullptr;

  if (!Intl()) return NS_ERROR_FAILURE;

  NS_IF_ADDREF(*aDocument = ToXPCDocument(Intl()->ParentDocument()));
  return NS_OK;
}

NS_IMETHODIMP
xpcAccessibleDocument::GetChildDocumentCount(uint32_t* aCount) {
  NS_ENSURE_ARG_POINTER(aCount);
  *aCount = 0;

  if (!Intl()) return NS_ERROR_FAILURE;

  *aCount = Intl()->ChildDocumentCount();
  return NS_OK;
}

NS_IMETHODIMP
xpcAccessibleDocument::GetChildDocumentAt(uint32_t aIndex,
                                          nsIAccessibleDocument** aDocument) {
  NS_ENSURE_ARG_POINTER(aDocument);
  *aDocument = nullptr;

  if (!Intl()) return NS_ERROR_FAILURE;

  NS_IF_ADDREF(*aDocument = ToXPCDocument(Intl()->GetChildDocumentAt(aIndex)));
  return *aDocument ? NS_OK : NS_ERROR_INVALID_ARG;
}

NS_IMETHODIMP
xpcAccessibleDocument::GetBrowsingContext(
    dom::BrowsingContext** aBrowsingContext) {
  NS_ENSURE_ARG_POINTER(aBrowsingContext);
  *aBrowsingContext = nullptr;
  if (!mIntl) {
    return NS_ERROR_FAILURE;
  }
  if (LocalAccessible* local = mIntl->AsLocal()) {
    NS_IF_ADDREF(*aBrowsingContext =
                     local->AsDoc()->DocumentNode()->GetBrowsingContext());
  } else {
    NS_IF_ADDREF(*aBrowsingContext =
                     mIntl->AsRemote()->AsDoc()->GetBrowsingContext());
  }
  return NS_OK;
}

////////////////////////////////////////////////////////////////////////////////
// xpcAccessibleDocument

xpcAccessibleGeneric* xpcAccessibleDocument::GetAccessible(
    Accessible* aAccessible) {
  if (aAccessible->IsLocal() &&
      ToXPCDocument(aAccessible->AsLocal()->Document()) != this) {
    NS_ERROR(
        "This XPCOM document is not related with given internal accessible!");
    return nullptr;
  }

  if (aAccessible->IsRemote() &&
      ToXPCDocument(aAccessible->AsRemote()->Document()) != this) {
    NS_ERROR(
        "This XPCOM document is not related with given internal accessible!");
    return nullptr;
  }

  if (aAccessible->IsDoc()) return this;

  return mCache.LookupOrInsertWith(aAccessible, [&]() -> xpcAccessibleGeneric* {
    if (aAccessible->IsImage()) {
      return new xpcAccessibleImage(aAccessible);
    }
    if (aAccessible->IsTable()) {
      return new xpcAccessibleTable(aAccessible);
    }
    if (aAccessible->IsTableCell()) {
      return new xpcAccessibleTableCell(aAccessible);
    }
    if (aAccessible->IsHyperText()) {
      return new xpcAccessibleHyperText(aAccessible);
    }

    return new xpcAccessibleGeneric(aAccessible);
  });
}

void xpcAccessibleDocument::Shutdown() {
  for (auto iter = mCache.Iter(); !iter.Done(); iter.Next()) {
    iter.Data()->Shutdown();
    iter.Remove();
  }
  xpcAccessibleGeneric::Shutdown();
}
