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

#include "Pivot.h"
#include "ia2AccessibleTable.h"
#include "mozilla/a11y/TableAccessible.h"
#include "nsIAccessiblePivot.h"

using namespace mozilla;
using namespace mozilla::a11y;

// Helpers

// Used to search for all row and column headers in a table. This could be slow,
// as it potentially walks all cells in the table. However, it's unclear if,
// when or how often clients will use this. If this proves to be a performance
// problem, we will need to add methods to TableAccessible to get all row and
// column headers in a faster way.
class HeaderRule : public PivotRule {
 public:
  explicit HeaderRule(role aRole) : mRole(aRole) {}

  virtual uint16_t Match(Accessible* aAcc) override {
    role accRole = aAcc->Role();
    if (accRole == mRole) {
      return nsIAccessibleTraversalRule::FILTER_MATCH |
             nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
    }
    if (accRole == roles::CAPTION || aAcc->IsTableCell()) {
      return nsIAccessibleTraversalRule::FILTER_IGNORE |
             nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
    }
    return nsIAccessibleTraversalRule::FILTER_IGNORE;
  }

 private:
  role mRole;
};

static SAFEARRAY* GetAllHeaders(Accessible* aTable, role aRole) {
  AutoTArray<Accessible*, 20> headers;
  Pivot pivot(aTable);
  HeaderRule rule(aRole);
  for (Accessible* header = pivot.Next(aTable, rule); header;
       header = pivot.Next(header, rule)) {
    headers.AppendElement(header);
  }
  return AccessibleArrayToUiaArray(headers);
}

// UiaGrid

Accessible* UiaGrid::Acc() {
  auto* ia2t = static_cast<ia2AccessibleTable*>(this);
  return ia2t->MsaaAccessible::Acc();
}

TableAccessible* UiaGrid::TableAcc() {
  Accessible* acc = Acc();
  return acc ? acc->AsTable() : nullptr;
}

// IGridProvider methods

STDMETHODIMP
UiaGrid::GetItem(int aRow, int aColumn,
                 __RPC__deref_out_opt IRawElementProviderSimple** aRetVal) {
  if (!aRetVal) {
    return E_INVALIDARG;
  }
  *aRetVal = nullptr;
  TableAccessible* table = TableAcc();
  if (!table) {
    return CO_E_OBJNOTCONNECTED;
  }
  Accessible* cell = table->CellAt(aRow, aColumn);
  if (!cell) {
    return E_INVALIDARG;
  }
  RefPtr<IRawElementProviderSimple> uia = MsaaAccessible::GetFrom(cell);
  uia.forget(aRetVal);
  return S_OK;
}

STDMETHODIMP
UiaGrid::get_RowCount(__RPC__out int* aRetVal) {
  if (!aRetVal) {
    return E_INVALIDARG;
  }
  TableAccessible* table = TableAcc();
  if (!table) {
    return CO_E_OBJNOTCONNECTED;
  }
  *aRetVal = table->RowCount();
  return S_OK;
}

STDMETHODIMP
UiaGrid::get_ColumnCount(__RPC__out int* aRetVal) {
  if (!aRetVal) {
    return E_INVALIDARG;
  }
  TableAccessible* table = TableAcc();
  if (!table) {
    return CO_E_OBJNOTCONNECTED;
  }
  *aRetVal = table->ColCount();
  return S_OK;
}

// ITableProvider methods

STDMETHODIMP
UiaGrid::GetRowHeaders(__RPC__deref_out_opt SAFEARRAY** aRetVal) {
  if (!aRetVal) {
    return E_INVALIDARG;
  }
  Accessible* acc = Acc();
  if (!acc) {
    return CO_E_OBJNOTCONNECTED;
  }
  *aRetVal = GetAllHeaders(acc, roles::ROWHEADER);
  return S_OK;
}

STDMETHODIMP
UiaGrid::GetColumnHeaders(__RPC__deref_out_opt SAFEARRAY** aRetVal) {
  if (!aRetVal) {
    return E_INVALIDARG;
  }
  Accessible* acc = Acc();
  if (!acc) {
    return CO_E_OBJNOTCONNECTED;
  }
  *aRetVal = GetAllHeaders(acc, roles::COLUMNHEADER);
  return S_OK;
}

STDMETHODIMP
UiaGrid::get_RowOrColumnMajor(__RPC__out enum RowOrColumnMajor* aRetVal) {
  if (!aRetVal) {
    return E_INVALIDARG;
  }
  // HTML and ARIA tables are always in row major order.
  *aRetVal = RowOrColumnMajor_RowMajor;
  return S_OK;
}
