/* clang-format off */
/* clang-format on */
/* 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/. */

#import "MacUtils.h"
#include "mozAccessible.h"

#include "LocalAccessible.h"
#include "DocAccessible.h"
#include "DocAccessibleParent.h"
#include "nsCocoaUtils.h"
#include "mozilla/a11y/PDocAccessible.h"

namespace mozilla {
namespace a11y {
namespace utils {

/**
 * Get a localized string from the a11y string bundle.
 * Return nil if not found.
 */
NSString* LocalizedString(const nsString& aString) {
  nsString text;

  Accessible::TranslateString(aString, text);

  return text.IsEmpty() ? nil : nsCocoaUtils::ToNSString(text);
}

NSString* GetAccAttr(mozAccessible* aNativeAccessible, nsAtom* aAttrName) {
  nsAutoString result;
  Accessible* acc = [aNativeAccessible geckoAccessible];
  RefPtr<AccAttributes> attributes = acc->Attributes();

  if (!attributes) {
    return nil;
  }

  attributes->GetAttribute(aAttrName, result);

  if (!result.IsEmpty()) {
    return nsCocoaUtils::ToNSString(result);
  }

  return nil;
}

bool DocumentExists(Accessible* aDoc, uintptr_t aDocPtr) {
  if (reinterpret_cast<uintptr_t>(aDoc) == aDocPtr) {
    return true;
  }

  if (aDoc->IsLocal()) {
    DocAccessible* docAcc = aDoc->AsLocal()->AsDoc();
    uint32_t docCount = docAcc->ChildDocumentCount();
    for (uint32_t i = 0; i < docCount; i++) {
      if (DocumentExists(docAcc->GetChildDocumentAt(i), aDocPtr)) {
        return true;
      }
    }
  } else {
    DocAccessibleParent* docProxy = aDoc->AsRemote()->AsDoc();
    size_t docCount = docProxy->ChildDocCount();
    for (uint32_t i = 0; i < docCount; i++) {
      if (DocumentExists(docProxy->ChildDocAt(i), aDocPtr)) {
        return true;
      }
    }
  }

  return false;
}

static NSColor* ColorFromColor(const Color& aColor) {
  return [NSColor colorWithCalibratedRed:NS_GET_R(aColor.mValue) / 255.0
                                   green:NS_GET_G(aColor.mValue) / 255.0
                                    blue:NS_GET_B(aColor.mValue) / 255.0
                                   alpha:1.0];
}

NSDictionary* StringAttributesFromAccAttributes(AccAttributes* aAttributes,
                                                Accessible* aContainer) {
  if (!aAttributes) {
    if (mozAccessible* mozAcc = GetNativeFromGeckoAccessible(aContainer)) {
      // If we don't have attributes provided this is probably a control like
      // a button or empty entry. Just provide the accessible as an
      // AXAttachment.
      return @{@"AXAttachment" : mozAcc};
    }
    return @{};
  }

  NSMutableDictionary* attrDict =
      [NSMutableDictionary dictionaryWithCapacity:aAttributes->Count()];
  NSMutableDictionary* fontAttrDict = [[NSMutableDictionary alloc] init];
  [attrDict setObject:fontAttrDict forKey:@"AXFont"];
  for (auto iter : *aAttributes) {
    if (iter.Name() == nsGkAtoms::background_color) {
      if (Maybe<Color> value = iter.Value<Color>()) {
        NSColor* color = ColorFromColor(*value);
        [attrDict setObject:(__bridge id)color.CGColor
                     forKey:@"AXBackgroundColor"];
      }
    } else if (iter.Name() == nsGkAtoms::color) {
      if (Maybe<Color> value = iter.Value<Color>()) {
        NSColor* color = ColorFromColor(*value);
        [attrDict setObject:(__bridge id)color.CGColor
                     forKey:@"AXForegroundColor"];
      }
    } else if (iter.Name() == nsGkAtoms::font_size) {
      if (Maybe<FontSize> pointSize = iter.Value<FontSize>()) {
        int32_t fontPixelSize = static_cast<int32_t>(pointSize->mValue * 4 / 3);
        [fontAttrDict setObject:@(fontPixelSize) forKey:@"AXFontSize"];
      }
    } else if (iter.Name() == nsGkAtoms::font_family) {
      nsAutoString fontFamily;
      iter.ValueAsString(fontFamily);
      [fontAttrDict setObject:nsCocoaUtils::ToNSString(fontFamily)
                       forKey:@"AXFontFamily"];
    } else if (iter.Name() == nsGkAtoms::textUnderlineColor) {
      [attrDict setObject:@1 forKey:@"AXUnderline"];
      if (Maybe<Color> value = iter.Value<Color>()) {
        NSColor* color = ColorFromColor(*value);
        [attrDict setObject:(__bridge id)color.CGColor
                     forKey:@"AXUnderlineColor"];
      }
    } else if (iter.Name() == nsGkAtoms::invalid) {
      // XXX: There is currently no attribute for grammar
      if (auto value = iter.Value<RefPtr<nsAtom>>()) {
        if (*value == nsGkAtoms::spelling) {
          [attrDict setObject:@YES
                       forKey:NSAccessibilityMarkedMisspelledTextAttribute];
        }
      }
    } else if (iter.Name() == nsGkAtoms::mark) {
      if (auto value = iter.Value<bool>()) {
        if (*value) {
          [attrDict setObject:@YES forKey:@"AXHighlight"];
        }
      }
    } else {
      nsAutoString valueStr;
      iter.ValueAsString(valueStr);
      nsAutoString keyStr;
      iter.NameAsString(keyStr);
      [attrDict setObject:nsCocoaUtils::ToNSString(valueStr)
                   forKey:nsCocoaUtils::ToNSString(keyStr)];
    }
  }

  mozAccessible* container = GetNativeFromGeckoAccessible(aContainer);
  id<MOXAccessible> link =
      [container moxFindAncestor:^BOOL(id<MOXAccessible> moxAcc, BOOL* stop) {
        return [[moxAcc moxRole] isEqualToString:NSAccessibilityLinkRole];
      }];
  if (link) {
    [attrDict setObject:link forKey:@"AXLink"];
  }

  id<MOXAccessible> heading =
      [container moxFindAncestor:^BOOL(id<MOXAccessible> moxAcc, BOOL* stop) {
        return [[moxAcc moxRole] isEqualToString:@"AXHeading"];
      }];
  if (heading) {
    [attrDict setObject:[heading moxValue] forKey:@"AXHeadingLevel"];
  }

  return attrDict;
}

NSScreen* GetNSScreenForAcc(mozAccessible* aAcc) {
  if (!aAcc) {
    return nil;
  }

  NSWindow* window = [aAcc moxWindow];
  NSScreen* screen = window ? [window screen] : nil;
  if (!screen) {
    // default to the main screen if we can't find one
    // specific to the window
    screen = [[NSScreen screens] objectAtIndex:0];
  }

  return screen;
}

NSRect GetCocoaScreenRectForAcc(mozAccessible* aAcc, LayoutDeviceIntRect& aRect,
                                bool aShouldUseCocoaCoords) {
  if (!aAcc) {
    return NSMakeRect(aRect.x, aRect.y, aRect.width, aRect.height);
  }

  NSScreen* screen = GetNSScreenForAcc(aAcc);
  CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(screen);

  // Regardless of screen selected above, VO is only happy if we use the
  // main screen height for Y coordinate conversion. This is consistent with
  // moxHitTest and GeckoTextMarkerRange::Bounds().
  NSScreen* mainScreen = [[NSScreen screens] objectAtIndex:0];
  CGFloat mainScreenHeight = [mainScreen frame].size.height;

  return NSMakeRect(
      static_cast<CGFloat>(aRect.x) / scaleFactor,
      aShouldUseCocoaCoords
          ? mainScreenHeight -
                static_cast<CGFloat>(aRect.y + aRect.height) / scaleFactor
          : static_cast<CGFloat>(aRect.y) / scaleFactor,
      static_cast<CGFloat>(aRect.width) / scaleFactor,
      static_cast<CGFloat>(aRect.height) / scaleFactor);
}
}  // namespace utils
}  // namespace a11y
}  // namespace mozilla
