/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=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 "sdnAccessible-inl.h"
#include "ISimpleDOM_i.c"

#include "DocAccessibleWrap.h"

#include "nsAttrName.h"
#include "nsCoreUtils.h"
#include "nsIAccessibleTypes.h"
#include "nsIDOMHTMLElement.h"
#include "nsIDOMCSSStyleDeclaration.h"
#include "nsNameSpaceManager.h"
#include "nsServiceManagerUtils.h"
#include "nsWinUtils.h"
#include "nsRange.h"

#include "mozilla/dom/BorrowedAttrInfo.h"
#include "mozilla/dom/Element.h"

using namespace mozilla;
using namespace mozilla::a11y;

sdnAccessible::~sdnAccessible()
{
  if (mUniqueId.isSome()) {
    AccessibleWrap::ReleaseChildID(WrapNotNull(this));
  }
}

STDMETHODIMP
sdnAccessible::QueryInterface(REFIID aREFIID, void** aInstancePtr)
{
  if (!aInstancePtr)
    return E_FAIL;
  *aInstancePtr = nullptr;

  if (aREFIID == IID_IClientSecurity) {
    // Some code might QI(IID_IClientSecurity) to detect whether or not we are
    // a proxy. Right now that can potentially happen off the main thread, so we
    // look for this condition immediately so that we don't trigger other code
    // that might not be thread-safe.
    return E_NOINTERFACE;
  }

  if (aREFIID == IID_ISimpleDOMNode) {
    *aInstancePtr = static_cast<ISimpleDOMNode*>(this);
    AddRef();
    return S_OK;
  }

  AccessibleWrap* accessible = GetAccessible();
  if (accessible)
    return accessible->QueryInterface(aREFIID, aInstancePtr);

  // IUnknown* is the canonical one if and only if this accessible doesn't have
  // an accessible.
  if (aREFIID == IID_IUnknown) {
    *aInstancePtr = static_cast<ISimpleDOMNode*>(this);
    AddRef();
    return S_OK;
  }

  return E_NOINTERFACE;
}

STDMETHODIMP
sdnAccessible::get_nodeInfo(BSTR __RPC_FAR* aNodeName,
                            short __RPC_FAR* aNameSpaceID,
                            BSTR __RPC_FAR* aNodeValue,
                            unsigned int __RPC_FAR* aNumChildren,
                            unsigned int __RPC_FAR* aUniqueID,
                            unsigned short __RPC_FAR* aNodeType)
{
  if (!aNodeName || !aNameSpaceID || !aNodeValue || !aNumChildren ||
      !aUniqueID || !aNodeType)
    return E_INVALIDARG;

  *aNodeName = nullptr;
  *aNameSpaceID = 0;
  *aNodeValue = nullptr;
  *aNumChildren = 0;
  *aUniqueID = 0;
  *aNodeType = 0;

  if (IsDefunct())
    return CO_E_OBJNOTCONNECTED;

  nsCOMPtr<nsIDOMNode> DOMNode(do_QueryInterface(mNode));

  uint16_t nodeType = 0;
  DOMNode->GetNodeType(&nodeType);
  *aNodeType = static_cast<unsigned short>(nodeType);

  if (*aNodeType !=  NODETYPE_TEXT) {
    nsAutoString nodeName;
    DOMNode->GetNodeName(nodeName);
    *aNodeName = ::SysAllocString(nodeName.get());
  }

  nsAutoString nodeValue;
  DOMNode->GetNodeValue(nodeValue);
  *aNodeValue = ::SysAllocString(nodeValue.get());

  *aNameSpaceID = mNode->IsNodeOfType(nsINode::eCONTENT) ?
    static_cast<short>(mNode->AsContent()->GetNameSpaceID()) : 0;

  // This is a unique ID for every content node. The 3rd party accessibility
  // application can compare this to the childID we return for events such as
  // focus events, to correlate back to data nodes in their internal object
  // model.
  AccessibleWrap* accessible = GetAccessible();
  if (accessible) {
    *aUniqueID = AccessibleWrap::GetChildIDFor(accessible);
  } else {
    if (mUniqueId.isNothing()) {
      AccessibleWrap::AssignChildIDTo(WrapNotNull(this));
    }
    MOZ_ASSERT(mUniqueId.isSome());
    *aUniqueID = mUniqueId.value();
  }

  *aNumChildren = mNode->GetChildCount();

  return S_OK;
}

STDMETHODIMP
sdnAccessible::get_attributes(unsigned  short aMaxAttribs,
                              BSTR __RPC_FAR* aAttribNames,
                              short __RPC_FAR* aNameSpaceIDs,
                              BSTR __RPC_FAR* aAttribValues,
                              unsigned short __RPC_FAR* aNumAttribs)
{
  if (!aAttribNames || !aNameSpaceIDs || !aAttribValues || !aNumAttribs)
    return E_INVALIDARG;

  *aNumAttribs = 0;

  if (IsDefunct())
    return CO_E_OBJNOTCONNECTED;

  if (!mNode->IsElement())
    return S_FALSE;

  dom::Element* elm = mNode->AsElement();
  uint32_t numAttribs = elm->GetAttrCount();
  if (numAttribs > aMaxAttribs)
    numAttribs = aMaxAttribs;

  *aNumAttribs = static_cast<unsigned short>(numAttribs);

  for (uint32_t index = 0; index < numAttribs; index++) {
    aNameSpaceIDs[index] = 0;
    aAttribValues[index] = aAttribNames[index] = nullptr;
    nsAutoString attributeValue;

    dom::BorrowedAttrInfo attr = elm->GetAttrInfoAt(index);
    attr.mValue->ToString(attributeValue);

    aNameSpaceIDs[index] = static_cast<short>(attr.mName->NamespaceID());
    aAttribNames[index] = ::SysAllocString(attr.mName->LocalName()->GetUTF16String());
    aAttribValues[index] = ::SysAllocString(attributeValue.get());
  }

  return S_OK;
}

STDMETHODIMP
sdnAccessible::get_attributesForNames(unsigned short aMaxAttribs,
                                      BSTR __RPC_FAR* aAttribNames,
                                      short __RPC_FAR* aNameSpaceID,
                                      BSTR __RPC_FAR* aAttribValues)
{
  if (!aAttribNames || !aNameSpaceID || !aAttribValues)
    return E_INVALIDARG;

  if (IsDefunct())
    return CO_E_OBJNOTCONNECTED;

  if (!mNode->IsElement())
    return S_FALSE;

  dom::Element* domElement = mNode->AsElement();
  nsNameSpaceManager* nameSpaceManager = nsNameSpaceManager::GetInstance();

  int32_t index = 0;
  for (index = 0; index < aMaxAttribs; index++) {
    aAttribValues[index] = nullptr;
    if (aAttribNames[index]) {
      nsAutoString attributeValue, nameSpaceURI;
      nsAutoString attributeName(nsDependentString(
        static_cast<const wchar_t*>(aAttribNames[index])));

      if (aNameSpaceID[index]>0 &&
        NS_SUCCEEDED(nameSpaceManager->GetNameSpaceURI(aNameSpaceID[index],
                                                       nameSpaceURI))) {
          domElement->GetAttributeNS(nameSpaceURI, attributeName,
                                          attributeValue);
      } else {
        domElement->GetAttribute(attributeName, attributeValue);
      }

      aAttribValues[index] = ::SysAllocString(attributeValue.get());
    }
  }

  return S_OK;
}

STDMETHODIMP
sdnAccessible::get_computedStyle(unsigned short aMaxStyleProperties,
                                 boolean aUseAlternateView,
                                 BSTR __RPC_FAR* aStyleProperties,
                                 BSTR __RPC_FAR* aStyleValues,
                                 unsigned short __RPC_FAR* aNumStyleProperties)
{
  if (!aStyleProperties || aStyleValues || !aNumStyleProperties)
    return E_INVALIDARG;

  if (IsDefunct())
    return CO_E_OBJNOTCONNECTED;

  *aNumStyleProperties = 0;

  if (mNode->IsNodeOfType(nsINode::eDOCUMENT))
    return S_FALSE;

  nsCOMPtr<nsIDOMCSSStyleDeclaration> cssDecl =
    nsWinUtils::GetComputedStyleDeclaration(mNode->AsContent());
  NS_ENSURE_TRUE(cssDecl, E_FAIL);

  uint32_t length = 0;
  cssDecl->GetLength(&length);

  uint32_t index = 0, realIndex = 0;
  for (index = realIndex = 0; index < length && realIndex < aMaxStyleProperties;
       index ++) {
    nsAutoString property, value;

    // Ignore -moz-* properties.
    if (NS_SUCCEEDED(cssDecl->Item(index, property)) && property.CharAt(0) != '-')
      cssDecl->GetPropertyValue(property, value);  // Get property value

    if (!value.IsEmpty()) {
      aStyleProperties[realIndex] = ::SysAllocString(property.get());
      aStyleValues[realIndex] = ::SysAllocString(value.get());
      ++realIndex;
    }
  }

  *aNumStyleProperties = static_cast<unsigned short>(realIndex);

  return S_OK;
}

STDMETHODIMP
sdnAccessible::get_computedStyleForProperties(unsigned short aNumStyleProperties,
                                              boolean aUseAlternateView,
                                              BSTR __RPC_FAR* aStyleProperties,
                                              BSTR __RPC_FAR* aStyleValues)
{
  if (!aStyleProperties || !aStyleValues)
    return E_INVALIDARG;

  if (IsDefunct())
    return CO_E_OBJNOTCONNECTED;

  if (mNode->IsNodeOfType(nsINode::eDOCUMENT))
    return S_FALSE;

  nsCOMPtr<nsIDOMCSSStyleDeclaration> cssDecl =
    nsWinUtils::GetComputedStyleDeclaration(mNode->AsContent());
  NS_ENSURE_TRUE(cssDecl, E_FAIL);

  uint32_t index = 0;
  for (index = 0; index < aNumStyleProperties; index++) {
    nsAutoString value;
    if (aStyleProperties[index])
      cssDecl->GetPropertyValue(nsDependentString(aStyleProperties[index]), value);  // Get property value
    aStyleValues[index] = ::SysAllocString(value.get());
  }

  return S_OK;
}

STDMETHODIMP
sdnAccessible::scrollTo(boolean aScrollTopLeft)
{
  DocAccessible* document = GetDocument();
  if (!document) // that's IsDefunct check
    return CO_E_OBJNOTCONNECTED;

  if (!mNode->IsContent())
    return S_FALSE;

  uint32_t scrollType =
    aScrollTopLeft ? nsIAccessibleScrollType::SCROLL_TYPE_TOP_LEFT :
                     nsIAccessibleScrollType::SCROLL_TYPE_BOTTOM_RIGHT;

  nsCoreUtils::ScrollTo(document->PresShell(), mNode->AsContent(), scrollType);
  return S_OK;
}

STDMETHODIMP
sdnAccessible::get_parentNode(ISimpleDOMNode __RPC_FAR *__RPC_FAR* aNode)
{
  if (!aNode)
    return E_INVALIDARG;
  *aNode = nullptr;

  if (IsDefunct())
    return CO_E_OBJNOTCONNECTED;

  nsINode* resultNode = mNode->GetParentNode();
  if (resultNode) {
    *aNode = static_cast<ISimpleDOMNode*>(new sdnAccessible(resultNode));
    (*aNode)->AddRef();
  }

  return S_OK;
}

STDMETHODIMP
sdnAccessible::get_firstChild(ISimpleDOMNode __RPC_FAR *__RPC_FAR* aNode)
{
  if (!aNode)
    return E_INVALIDARG;
  *aNode = nullptr;

  if (IsDefunct())
    return CO_E_OBJNOTCONNECTED;

  nsINode* resultNode = mNode->GetFirstChild();
  if (resultNode) {
    *aNode = static_cast<ISimpleDOMNode*>(new sdnAccessible(resultNode));
    (*aNode)->AddRef();
  }

  return S_OK;
}

STDMETHODIMP
sdnAccessible::get_lastChild(ISimpleDOMNode __RPC_FAR *__RPC_FAR* aNode)
{
  if (!aNode)
    return E_INVALIDARG;
  *aNode = nullptr;

  if (IsDefunct())
    return CO_E_OBJNOTCONNECTED;

  nsINode* resultNode = mNode->GetLastChild();
  if (resultNode) {
    *aNode = static_cast<ISimpleDOMNode*>(new sdnAccessible(resultNode));
    (*aNode)->AddRef();
  }

  return S_OK;
}

STDMETHODIMP
sdnAccessible::get_previousSibling(ISimpleDOMNode __RPC_FAR *__RPC_FAR* aNode)
{
  if (!aNode)
    return E_INVALIDARG;
  *aNode = nullptr;

  if (IsDefunct())
    return CO_E_OBJNOTCONNECTED;

  nsINode* resultNode = mNode->GetPreviousSibling();
  if (resultNode) {
    *aNode = static_cast<ISimpleDOMNode*>(new sdnAccessible(resultNode));
    (*aNode)->AddRef();
  }

  return S_OK;
}

STDMETHODIMP
sdnAccessible::get_nextSibling(ISimpleDOMNode __RPC_FAR *__RPC_FAR* aNode)
{
  if (!aNode)
    return E_INVALIDARG;
  *aNode = nullptr;

  if (IsDefunct())
    return CO_E_OBJNOTCONNECTED;

  nsINode* resultNode = mNode->GetNextSibling();
  if (resultNode) {
    *aNode = static_cast<ISimpleDOMNode*>(new sdnAccessible(resultNode));
    (*aNode)->AddRef();
  }

  return S_OK;
}

STDMETHODIMP
sdnAccessible::get_childAt(unsigned aChildIndex,
                           ISimpleDOMNode __RPC_FAR *__RPC_FAR* aNode)
{
  if (!aNode)
    return E_INVALIDARG;
  *aNode = nullptr;

  if (IsDefunct())
    return CO_E_OBJNOTCONNECTED;

  nsINode* resultNode = mNode->GetChildAt(aChildIndex);
  if (resultNode) {
    *aNode = static_cast<ISimpleDOMNode*>(new sdnAccessible(resultNode));
    (*aNode)->AddRef();
  }


  return S_OK;
}

STDMETHODIMP
sdnAccessible::get_innerHTML(BSTR __RPC_FAR* aInnerHTML)
{
  if (!aInnerHTML)
    return E_INVALIDARG;
  *aInnerHTML = nullptr;

  if (IsDefunct())
    return CO_E_OBJNOTCONNECTED;

  if (!mNode->IsElement())
    return S_FALSE;

  nsAutoString innerHTML;
  mNode->AsElement()->GetInnerHTML(innerHTML);
  if (innerHTML.IsEmpty())
    return S_FALSE;

  *aInnerHTML = ::SysAllocStringLen(innerHTML.get(), innerHTML.Length());
  if (!*aInnerHTML)
    return E_OUTOFMEMORY;

  return S_OK;
}

STDMETHODIMP
sdnAccessible::get_localInterface(void __RPC_FAR *__RPC_FAR* aLocalInterface)
{
  if (!aLocalInterface)
    return E_INVALIDARG;
  *aLocalInterface = nullptr;

  if (IsDefunct())
    return CO_E_OBJNOTCONNECTED;

  *aLocalInterface = this;
  AddRef();

  return S_OK;
}

STDMETHODIMP
sdnAccessible::get_language(BSTR __RPC_FAR* aLanguage)
{
  if (!aLanguage)
    return E_INVALIDARG;
  *aLanguage = nullptr;

  if (IsDefunct())
    return CO_E_OBJNOTCONNECTED;

  nsAutoString language;
  if (mNode->IsContent())
    nsCoreUtils::GetLanguageFor(mNode->AsContent(), nullptr, language);
  if (language.IsEmpty()) { // Nothing found, so use document's language
    mNode->OwnerDoc()->GetHeaderData(nsGkAtoms::headerContentLanguage,
                                     language);
  }

  if (language.IsEmpty())
    return S_FALSE;

  *aLanguage = ::SysAllocStringLen(language.get(), language.Length());
  if (!*aLanguage)
   return E_OUTOFMEMORY;

  return S_OK;
}
