// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/accessibility/platform/inspect/ax_property_node.h"

#include <optional>

#include "testing/gtest/include/gtest/gtest.h"
#include "ui/accessibility/platform/inspect/ax_inspect.h"

using ui::AXPropertyFilter;
using ui::AXPropertyNode;

namespace ui {

class AXPropertyNodeTest : public testing::Test {
 public:
  AXPropertyNodeTest() = default;

  AXPropertyNodeTest(const AXPropertyNodeTest&) = delete;
  AXPropertyNodeTest& operator=(const AXPropertyNodeTest&) = delete;

  ~AXPropertyNodeTest() override = default;
};

AXPropertyNode Parse(const char* input) {
  AXPropertyFilter filter(input, AXPropertyFilter::ALLOW);
  return AXPropertyNode::From(filter);
}

AXPropertyNode GetArgumentNode(const char* input) {
  auto got = Parse(input);
  if (got.arguments.empty()) {
    return AXPropertyNode();
  }
  return std::move(got.arguments[0]);
}

void ParseAndCheck(const char* input, const char* expected) {
  auto got = Parse(input).ToFlatString();
  EXPECT_EQ(got, expected);
}

void ParseAndCheckTree(const char* input, const char* expected) {
  auto got = AXPropertyNode::From(input).ToTreeString();
  EXPECT_EQ(got, expected);
}

TEST_F(AXPropertyNodeTest, ParseProperty) {
  // Properties and methods.
  ParseAndCheck("Role", "Role");
  ParseAndCheck("ChildAt(3)", "ChildAt(3)");
  ParseAndCheck("Cell(3, 4)", "Cell(3, 4)");
  ParseAndCheck("Volume(3, 4, 5)", "Volume(3, 4, 5)");
  ParseAndCheck("TableFor(CellBy(id))", "TableFor(CellBy(id))");
  ParseAndCheck("A(B(1), 2)", "A(B(1), 2)");
  ParseAndCheck("A(B(1), 2, C(3, 4))", "A(B(1), 2, C(3, 4))");
  ParseAndCheck("[3, 4]", "[](3, 4)");
  ParseAndCheck("Cell([3, 4])", "Cell([](3, 4))");

  // Arguments
  ParseAndCheck("Text({val: 1})", "Text({}(val: 1))");
  ParseAndCheck("Text({lat: 1, len: 1})", "Text({}(lat: 1, len: 1))");
  ParseAndCheck("Text({dict: {val: 1}})", "Text({}(dict: {}(val: 1)))");
  ParseAndCheck("Text({dict: {val: 1}, 3})", "Text({}(dict: {}(val: 1), 3))");
  ParseAndCheck("Text({dict: [1, 2]})", "Text({}(dict: [](1, 2)))");
  ParseAndCheck("Text({dict: ValueFor(1)})", "Text({}(dict: ValueFor(1)))");

  // Nested arguments
  ParseAndCheck("AXIndexForTextMarker(AXTextMarkerForIndex(0))",
                "AXIndexForTextMarker(AXTextMarkerForIndex(0))");

  // Line indexes filter.
  ParseAndCheck(":3,:5;AXDOMClassList", ":3,:5;AXDOMClassList");

  // Wrong format.
  ParseAndCheck("Role(3", "Role(3)");
  ParseAndCheck("TableFor(CellBy(id", "TableFor(CellBy(id))");
  ParseAndCheck("[3, 4", "[](3, 4)");

  // Arguments conversion
  EXPECT_EQ(GetArgumentNode("ChildAt([3])").IsArray(), true);
  EXPECT_EQ(GetArgumentNode("Text({loc: 3, len: 2})").IsDict(), true);
  EXPECT_EQ(GetArgumentNode("ChildAt(3)").IsDict(), false);
  EXPECT_EQ(GetArgumentNode("ChildAt(3)").IsArray(), false);
  EXPECT_EQ(GetArgumentNode("ChildAt(3)").AsInt(), 3);
  EXPECT_EQ(GetArgumentNode("AXPerformAction(AXPress)").AsString(), "AXPress");
  EXPECT_EQ(GetArgumentNode("AXPerformAction('AXPress')").AsString(),
            "AXPress");

  // Dict: FindStringKey
  EXPECT_EQ(
      GetArgumentNode("Text({start: :1, dir: forward})").FindStringKey("start"),
      ":1");
  EXPECT_EQ(
      GetArgumentNode("Text({start: :1, dir: forward})").FindStringKey("dir"),
      "forward");
  EXPECT_EQ(GetArgumentNode("Text({start: :1, dir: forward})")
                .FindStringKey("notexists"),
            std::nullopt);

  // Dict: FindIntKey
  EXPECT_EQ(GetArgumentNode("Text({loc: 3, len: 2})").FindIntKey("loc"), 3);
  EXPECT_EQ(GetArgumentNode("Text({loc: 3, len: 2})").FindIntKey("len"), 2);
  EXPECT_EQ(GetArgumentNode("Text({loc: 3, len: 2})").FindIntKey("notexists"),
            std::nullopt);

  // `AXPropertyNode::FindKey()`
  EXPECT_EQ(GetArgumentNode("Text({anchor: {:1, 0, up}})")
                .FindKey("anchor")
                ->ToFlatString(),
            "anchor: {}(:1, 0, up)");

  EXPECT_EQ(GetArgumentNode("Text({anchor: {:1, 0, up}})").FindKey("focus"),
            nullptr);

  EXPECT_EQ(GetArgumentNode("AXStringForTextMarkerRange({anchor: {:2, 1, "
                            "down}, focus: {:2, 2, down}})")
                .FindKey("anchor")
                ->ToFlatString(),
            "anchor: {}(:2, 1, down)");
}

TEST_F(AXPropertyNodeTest, CallChains) {
  ParseAndCheckTree("textbox.name", R"~~(textbox.
name)~~");

  ParseAndCheckTree("textbox.parent.name",
                    R"~~(textbox.
parent.
name)~~");

  ParseAndCheckTree("table.rowAt(row.childIndex)",
                    R"~~(table.
rowAt(
  row.
  childIndex
))~~");

  ParseAndCheckTree(":1.AXDOMClassList", R"~~(:1.
AXDOMClassList)~~");

  ParseAndCheckTree(":1.AXIndexForTextMarker(:1.AXTextMarkerForIndex(0))",
                    R"~~(:1.
AXIndexForTextMarker(
  :1.
  AXTextMarkerForIndex(
    0
  )
))~~");

  ParseAndCheckTree("table.cellAt(cell.rowIndex, cell.columnIndex)",
                    R"~~(table.
cellAt(
  cell.
  rowIndex,
  cell.
  columnIndex
))~~");

  ParseAndCheckTree(
      "table.cellAt(table.rowIndexFor(cell), table.columnIndexFor(cell))",
      R"~~(table.
cellAt(
  table.
  rowIndexFor(
    cell
  ),
  table.
  columnIndexFor(
    cell
  )
))~~");
}

TEST_F(AXPropertyNodeTest, CallChains_Array) {
  ParseAndCheckTree("children[3]", R"~~(children.
[](
  3
))~~");

  ParseAndCheckTree("textbox.AXChildren[0]", R"~~(textbox.
AXChildren.
[](
  0
))~~");

  ParseAndCheckTree("textbox.AXChildrenFor(textbox_child)[0]", R"~~(textbox.
AXChildrenFor(
  textbox_child
).
[](
  0
))~~");

  ParseAndCheckTree("get(AXChildren[0])", R"~~(get(
  AXChildren.
  [](
    0
  )
))~~");

  ParseAndCheckTree("textbox.AXChildren[0].AXRole", R"~~(textbox.
AXChildren.
[](
  0
).
AXRole)~~");

  ParseAndCheckTree(
      "textarea.AXTextMarkerRangeForUIElement(textarea.AXChildren[0])",
      R"~~(textarea.
AXTextMarkerRangeForUIElement(
  textarea.
  AXChildren.
  [](
    0
  )
))~~");
}

TEST_F(AXPropertyNodeTest, Variables) {
  // Statement
  ParseAndCheckTree(
      "textmarker_range:= textarea.AXTextMarkerRangeForUIElement(textarea)",
      R"~~(textmarker_range:textarea.
AXTextMarkerRangeForUIElement(
  textarea
))~~");

  // Integer array
  ParseAndCheckTree("var:= [3, 4]",
                    R"~~(var:[](
  3,
  4
))~~");

  // Range dictionary
  ParseAndCheckTree("var:= {loc: 3, len: 2}",
                    R"~~(var:{}(
  loc:3,
  len:2
))~~");

  // TextMarker dictionary
  ParseAndCheckTree("var:= {:2, 2, down}",
                    R"~~(var:{}(
  :2,
  2,
  down
))~~");

  // TextMarker array
  ParseAndCheckTree("var:= [{:2, 2, down}, {:1, 1, up}]",
                    R"~~(var:[](
  {}(
    :2,
    2,
    down
  ),
  {}(
    :1,
    1,
    up
  )
))~~");

  // TextMarkerRange dictionary
  ParseAndCheckTree("var:= {anchor: {:2, 1, down}, focus: {:2, 2, down} }",
                    R"~~(var:{}(
  anchor:{}(
    :2,
    1,
    down
  ),
  focus:{}(
    :2,
    2,
    down
  )
))~~");
}

TEST_F(AXPropertyNodeTest, RValue) {
  ParseAndCheckTree("textarea.AXSelectedTextMarkerRange = {loc: 3, len: 2}",
                    R"~~(textarea.
AXSelectedTextMarkerRange=
{}(
  loc:3,
  len:2
))~~");
}

}  // namespace ui
