// Copyright 2019 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/base/test/ns_ax_tree_validator.h"

#include <Cocoa/Cocoa.h>

#include "base/logging.h"
#include "base/strings/sys_string_conversions.h"

namespace {

id<NSAccessibility> ToNSAccessibility(id obj) {
  return [obj conformsToProtocol:@protocol(NSAccessibility)] ? obj : nil;
}

void PrintNSAXTreeHelper(id<NSAccessibility> root, int depth) {
  std::string desc;
  for (int i = 0; i < depth; i++)
    desc += "  ";
  desc += base::SysNSStringToUTF8([NSString stringWithFormat:@"%@", root]);
  LOG(INFO) << desc;
  for (id child in root.accessibilityChildren)
    PrintNSAXTreeHelper(child, depth + 1);
}

}

namespace ui {

NSAXTreeProblemDetails::NSAXTreeProblemDetails(ProblemType type,
                                               id node_a,
                                               id node_b,
                                               id node_c)
    : type(type), node_a(node_a), node_b(node_b), node_c(node_c) {}

std::string NSAXTreeProblemDetails::ToString() {
  NSString* s;
  switch (type) {
    case NSAX_NOT_CHILD_OF_PARENT:
      s = [NSString
          stringWithFormat:@"Node %@ isn't a child of %@", node_a, node_b];
      break;
    case NSAX_CHILD_PARENT_NOT_THIS:
      s = [NSString stringWithFormat:@"Node %@'s child %@'s parent is %@",
                                     node_a, node_b, node_c];
      break;
    case NSAX_NOT_NSACCESSIBILITY:
      s = [NSString stringWithFormat:@"Node %@ does not conform to"
                                      " to NSAccessibility",
                                     node_a];
      break;
    case NSAX_PARENT_NOT_NSACCESSIBILITY:
      s = [NSString stringWithFormat:@"Node %@'s parent %@ does not conform"
                                      " to NSAccessibility",
                                     node_a, node_b];
      break;
  }
  return base::SysNSStringToUTF8(s);
}

absl::optional<NSAXTreeProblemDetails> ValidateNSAXTree(
    id<NSAccessibility> root,
    size_t* nodes_visited) {
  if (!ToNSAccessibility(root)) {
    return absl::make_optional<NSAXTreeProblemDetails>(
        NSAXTreeProblemDetails::NSAX_NOT_NSACCESSIBILITY, root, nil, nil);
  }
  (*nodes_visited)++;

  if (root.accessibilityParent) {
    id<NSAccessibility> parent = ToNSAccessibility(root.accessibilityParent);
    if (!parent) {
      return absl::make_optional<NSAXTreeProblemDetails>(
          NSAXTreeProblemDetails::NSAX_PARENT_NOT_NSACCESSIBILITY, root, parent,
          nil);
    }

    NSArray<id<NSAccessibility>>* parent_children =
        parent.accessibilityChildren;

    if ([parent_children indexOfObjectIdenticalTo:root] == NSNotFound) {
      return absl::make_optional<NSAXTreeProblemDetails>(
          NSAXTreeProblemDetails::NSAX_NOT_CHILD_OF_PARENT, root, parent, nil);
    }
  }

  NSArray<id<NSAccessibility>>* children = root.accessibilityChildren;
  for (id<NSAccessibility> child in children) {
    absl::optional<NSAXTreeProblemDetails> details =
        ValidateNSAXTree(child, nodes_visited);
    if (details.has_value())
      return details;
  }

  return absl::nullopt;
}

void PrintNSAXTree(id<NSAccessibility> root) {
  PrintNSAXTreeHelper(root, 0);
}

}  // namespace ui