// Copyright 2021 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/ax_utils_mac.h"

#include "base/mac/scoped_cftyperef.h"
#include "ui/accessibility/ax_range.h"
#include "ui/accessibility/platform/ax_platform_node_base.h"
#include "ui/accessibility/platform/ax_platform_node_cocoa.h"
#include "ui/accessibility/platform/ax_platform_node_delegate.h"

namespace ui {

bool IsAXTextMarker(id object) {
  if (object == nil)
    return false;

  AXTextMarkerRef cf_text_marker = static_cast<AXTextMarkerRef>(object);
  DCHECK(cf_text_marker);
  return CFGetTypeID(cf_text_marker) == AXTextMarkerGetTypeID();
}

bool IsAXTextMarkerRange(id object) {
  if (object == nil)
    return false;

  AXTextMarkerRangeRef cf_marker_range =
      static_cast<AXTextMarkerRangeRef>(object);
  DCHECK(cf_marker_range);
  return CFGetTypeID(cf_marker_range) == AXTextMarkerRangeGetTypeID();
}

AXPlatformNodeDelegate::AXPosition AXTextMarkerToAXPosition(id text_marker) {
  if (!IsAXTextMarker(text_marker))
    return AXNodePosition::CreateNullPosition();

  AXTextMarkerRef cf_text_marker = static_cast<AXTextMarkerRef>(text_marker);
  if (AXTextMarkerGetLength(cf_text_marker) !=
      sizeof(AXPlatformNodeDelegate::SerializedPosition))
    return AXNodePosition::CreateNullPosition();

  const UInt8* source_buffer = AXTextMarkerGetBytePtr(cf_text_marker);
  if (!source_buffer)
    return AXNodePosition::CreateNullPosition();

  return AXNodePosition::Unserialize(
      *reinterpret_cast<const AXPlatformNodeDelegate::SerializedPosition*>(
          source_buffer));
}

AXPlatformNodeDelegate::AXRange AXTextMarkerRangeToAXRange(
    id text_marker_range) {
  if (!IsAXTextMarkerRange(text_marker_range)) {
    return AXPlatformNodeDelegate::AXRange();
  }

  AXTextMarkerRangeRef cf_marker_range =
      static_cast<AXTextMarkerRangeRef>(text_marker_range);

  base::ScopedCFTypeRef<AXTextMarkerRef> start_marker(
      AXTextMarkerRangeCopyStartMarker(cf_marker_range));
  base::ScopedCFTypeRef<AXTextMarkerRef> end_marker(
      AXTextMarkerRangeCopyEndMarker(cf_marker_range));
  if (!start_marker.get() || !end_marker.get())
    return AXPlatformNodeDelegate::AXRange();

  // |AXPlatformNodeDelegate::AXRange| takes ownership of its anchor and focus.
  AXPlatformNodeDelegate::AXPosition anchor =
      AXTextMarkerToAXPosition(static_cast<id>(start_marker.get()));
  AXPlatformNodeDelegate::AXPosition focus =
      AXTextMarkerToAXPosition(static_cast<id>(end_marker.get()));
  return AXPlatformNodeDelegate::AXRange(std::move(anchor), std::move(focus));
}

id AXPositionToAXTextMarker(AXPlatformNodeDelegate::AXPosition position) {
  // AXTextMarkerCreate is a system function that makes a copy of the data
  // buffer given to it.
  AXPlatformNodeDelegate::SerializedPosition serialized = position->Serialize();
  AXTextMarkerRef cf_text_marker = AXTextMarkerCreate(
      kCFAllocatorDefault, reinterpret_cast<const UInt8*>(&serialized),
      sizeof(AXPlatformNodeDelegate::SerializedPosition));
  return [static_cast<id>(cf_text_marker) autorelease];
}

id AXRangeToAXTextMarkerRange(AXPlatformNodeDelegate::AXRange range) {
  AXPlatformNodeDelegate::SerializedPosition serialized_anchor =
      range.anchor()->Serialize();
  AXPlatformNodeDelegate::SerializedPosition serialized_focus =
      range.focus()->Serialize();

  base::ScopedCFTypeRef<AXTextMarkerRef> start_marker(AXTextMarkerCreate(
      kCFAllocatorDefault, reinterpret_cast<const UInt8*>(&serialized_anchor),
      sizeof(AXPlatformNodeDelegate::SerializedPosition)));
  base::ScopedCFTypeRef<AXTextMarkerRef> end_marker(AXTextMarkerCreate(
      kCFAllocatorDefault, reinterpret_cast<const UInt8*>(&serialized_focus),
      sizeof(AXPlatformNodeDelegate::SerializedPosition)));

  AXTextMarkerRangeRef cf_marker_range =
      AXTextMarkerRangeCreate(kCFAllocatorDefault, start_marker, end_marker);
  return [static_cast<id>(cf_marker_range) autorelease];
}

id AXTextMarkerFrom(const AXPlatformNodeCocoa* anchor,
                    int offset,
                    ax::mojom::TextAffinity affinity) {
  AXPlatformNode* anchor_platform_node = [static_cast<id>(anchor) node];
  AXPlatformNodeDelegate* anchor_node = anchor_platform_node->GetDelegate();
  AXPlatformNodeDelegate::AXPosition position =
      anchor_node->CreateTextPositionAt(offset, affinity);
  return AXPositionToAXTextMarker(std::move(position));
}

id AXTextMarkerRangeFrom(id start_textmarker, id end_textmarker) {
  AXTextMarkerRangeRef cf_marker_range = AXTextMarkerRangeCreate(
      kCFAllocatorDefault, static_cast<AXTextMarkerRef>(start_textmarker),
      static_cast<AXTextMarkerRef>(end_textmarker));
  return [static_cast<id>(cf_marker_range) autorelease];
}

id AXTextMarkerRangeStart(id text_marker_range) {
  return static_cast<id>(AXTextMarkerRangeCopyStartMarker(
      static_cast<AXTextMarkerRangeRef>(text_marker_range)));
}

id AXTextMarkerRangeEnd(id text_marker_range) {
  return static_cast<id>(AXTextMarkerRangeCopyEndMarker(
      static_cast<AXTextMarkerRangeRef>(text_marker_range)));
}

}  // namespace ui