/* -*- 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/. */

#ifndef mozilla_a11y_ProxyAccessibleBase_h
#define mozilla_a11y_ProxyAccessibleBase_h

#include "mozilla/a11y/Role.h"
#include "nsIAccessibleText.h"
#include "nsIAccessibleTypes.h"
#include "Accessible.h"
#include "nsString.h"
#include "nsTArray.h"
#include "nsRect.h"
#include "Accessible.h"

namespace mozilla {
namespace a11y {

class Accessible;
class Attribute;
class DocAccessibleParent;
class ProxyAccessible;
enum class RelationType;

enum Interfaces
{
  HYPERTEXT = 1,
  HYPERLINK = 1 << 1,
  IMAGE = 1 << 2,
  VALUE = 1 << 3,
  TABLE = 1 << 4,
  TABLECELL = 1 << 5,
  DOCUMENT = 1 << 6,
  SELECTION = 1 << 7,
  ACTION = 1 << 8,
};

template <class Derived>
class ProxyAccessibleBase
{
public:
  ~ProxyAccessibleBase()
  {
    MOZ_ASSERT(!mWrapper);
  }

  void AddChildAt(uint32_t aIdx, Derived* aChild)
  { mChildren.InsertElementAt(aIdx, aChild); }

  uint32_t ChildrenCount() const { return mChildren.Length(); }
  Derived* ChildAt(uint32_t aIdx) const { return mChildren[aIdx]; }
  Derived* FirstChild() const
    { return mChildren.Length() ? mChildren[0] : nullptr; }
  Derived* LastChild() const
    { return mChildren.Length() ? mChildren[mChildren.Length() - 1] : nullptr; }
  Derived* PrevSibling() const
  {
    size_t idx = IndexInParent();
    return idx > 0 ? Parent()->mChildren[idx - 1] : nullptr;
  }
  Derived* NextSibling() const
  {
    size_t idx = IndexInParent();
    return idx + 1 < Parent()->mChildren.Length() ? Parent()->mChildren[idx + 1]
    : nullptr;
  }

  // XXX evaluate if this is fast enough.
  size_t IndexInParent() const { return
    Parent()->mChildren.IndexOf(static_cast<const Derived*>(this)); }
  uint32_t EmbeddedChildCount() const;
  int32_t IndexOfEmbeddedChild(const Derived* aChild);
  Derived* EmbeddedChildAt(size_t aChildIdx);
  bool MustPruneChildren() const;

  void Shutdown();

  void SetChildDoc(DocAccessibleParent* aChildDoc);
  void ClearChildDoc(DocAccessibleParent* aChildDoc);

  /**
   * Remove The given child.
   */
  void RemoveChild(Derived* aChild)
    { mChildren.RemoveElement(aChild); }

  /**
   * Return the proxy for the parent of the wrapped accessible.
   */
  Derived* Parent() const { return mParent; }

  Accessible* OuterDocOfRemoteBrowser() const;

  /**
   * Get the role of the accessible we're proxying.
   */
  role Role() const { return mRole; }

  /**
   * Return true if this is an embedded object.
   */
  bool IsEmbeddedObject() const
  {
    role role = Role();
    return role != roles::TEXT_LEAF &&
           role != roles::WHITESPACE &&
           role != roles::STATICTEXT;
  }

  /**
   * Allow the platform to store a pointers worth of data on us.
   */
  uintptr_t GetWrapper() const { return mWrapper; }
  void SetWrapper(uintptr_t aWrapper) { mWrapper = aWrapper; }

  /*
   * Return the ID of the accessible being proxied.
   */
  uint64_t ID() const { return mID; }

  /**
   * Return the document containing this proxy, or the proxy itself if it is a
   * document.
   */
  DocAccessibleParent* Document() const { return mDoc; }

  /**
   * Return true if this proxy is a DocAccessibleParent.
   */
  bool IsDoc() const { return mIsDoc; }
  DocAccessibleParent* AsDoc() const { return IsDoc() ? mDoc : nullptr; }

  // XXX checking mRole alone may not result in same behavior as Accessibles
  // due to ARIA roles. See bug 1210477.
  inline bool IsTable() const
  {
    return mRole == roles::TABLE || mRole == roles::MATHML_TABLE;
  }
  inline bool IsTableRow() const
  {
    return (mRole == roles::ROW ||
        mRole == roles::MATHML_TABLE_ROW ||
        mRole == roles::MATHML_LABELED_ROW);
  }
  inline bool IsTableCell() const
  {
    return (mRole == roles::CELL ||
        mRole == roles::COLUMNHEADER ||
        mRole == roles::ROWHEADER ||
        mRole == roles::GRID_CELL ||
        mRole == roles::MATHML_CELL);
  }

protected:
  ProxyAccessibleBase(uint64_t aID, Derived* aParent,
                      DocAccessibleParent* aDoc, role aRole,
                      uint32_t aInterfaces)
    : mParent(aParent)
    , mDoc(aDoc)
    , mWrapper(0)
    , mID(aID)
    , mRole(aRole)
    , mOuterDoc(false)
    , mIsDoc(false)
    , mHasValue(aInterfaces & Interfaces::VALUE)
    , mIsHyperLink(aInterfaces & Interfaces::HYPERLINK)
    , mIsHyperText(aInterfaces & Interfaces::HYPERTEXT)
  {
  }

  explicit ProxyAccessibleBase(DocAccessibleParent* aThisAsDoc) :
    mParent(nullptr), mDoc(aThisAsDoc), mWrapper(0), mID(0),
    mRole(roles::DOCUMENT), mOuterDoc(false), mIsDoc(true), mHasValue(false),
    mIsHyperLink(false), mIsHyperText(false)
  {}

protected:
  Derived* mParent;

private:
  friend Derived;

  nsTArray<Derived*> mChildren;
  DocAccessibleParent* mDoc;
  uintptr_t mWrapper;
  uint64_t mID;

protected:
  // XXX DocAccessibleParent gets to change this to change the role of
  // documents.
  role mRole : 27;

private:
  bool mOuterDoc : 1;

public:
  const bool mIsDoc: 1;
  const bool mHasValue: 1;
  const bool mIsHyperLink: 1;
  const bool mIsHyperText: 1;
};

extern template class ProxyAccessibleBase<ProxyAccessible>;

}
}

#endif