#import "OccurenceUtils.h"
#import "StyleBase.h"

@implementation OccurenceUtils

+ (BOOL)detect:(NSAttributedStringKey _Nonnull)key
         withHost:(id<EnrichedViewHost> _Nonnull)host
          inRange:(NSRange)range
    withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value,
                                                NSRange range))condition {
  __block NSInteger totalLength = 0;
  [host.textView.textStorage
      enumerateAttribute:key
                 inRange:range
                 options:0
              usingBlock:^(id _Nullable value, NSRange range,
                           BOOL *_Nonnull stop) {
                if (condition(value, range)) {
                  totalLength += range.length;
                }
              }];
  return totalLength == range.length;
}

// checkPrevious flag is used for styles like lists or blockquotes
// it means that first character of paragraph will be checked instead if the
// detection is not in input's selected range and at the end of the input
+ (BOOL)detect:(NSAttributedStringKey _Nonnull)key
         withHost:(id<EnrichedViewHost> _Nonnull)host
          atIndex:(NSUInteger)index
    checkPrevious:(BOOL)checkPrev
    withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value,
                                                NSRange range))condition {
  NSRange detectionRange = NSMakeRange(index, 0);
  id attrValue;
  // Only trust typingAttributes when the textView is actually editable.
  // Non-editable hosts (e.g. EnrichedTextView) keep selectedRange pinned at
  // (0, 0), so without this gate every detection at index 0 would match the
  // selection and read stale/default typingAttributes instead of the real
  // attribute at that position in textStorage.
  if (host.textView.isEditable &&
      NSEqualRanges(host.textView.selectedRange, detectionRange)) {
    attrValue = host.textView.typingAttributes[key];
  } else if (index == host.textView.textStorage.string.length) {
    if (checkPrev) {
      NSRange paragraphRange = [host.textView.textStorage.string
          paragraphRangeForRange:detectionRange];
      if (paragraphRange.location == detectionRange.location) {
        return NO;
      } else {
        return [self detect:key
                   withHost:host
                    inRange:NSMakeRange(paragraphRange.location, 1)
              withCondition:condition];
      }
    } else {
      return NO;
    }
  } else {
    NSRange attrRange = NSMakeRange(0, 0);
    attrValue = [host.textView.textStorage attribute:key
                                             atIndex:index
                                      effectiveRange:&attrRange];
  }
  return condition(attrValue, detectionRange);
}

+ (BOOL)detectMultiple:(NSArray<NSAttributedStringKey> *_Nonnull)keys
              withHost:(id<EnrichedViewHost> _Nonnull)host
               inRange:(NSRange)range
         withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value,
                                                     NSRange range))condition {
  __block NSInteger totalLength = 0;
  for (NSString *key in keys) {
    [host.textView.textStorage
        enumerateAttribute:key
                   inRange:range
                   options:0
                usingBlock:^(id _Nullable value, NSRange range,
                             BOOL *_Nonnull stop) {
                  if (condition(value, range)) {
                    totalLength += range.length;
                  }
                }];
  }
  return totalLength == range.length;
}

+ (BOOL)any:(NSAttributedStringKey _Nonnull)key
         withHost:(id<EnrichedViewHost> _Nonnull)host
          inRange:(NSRange)range
    withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value,
                                                NSRange range))condition {
  __block BOOL found = NO;
  [host.textView.textStorage
      enumerateAttribute:key
                 inRange:range
                 options:0
              usingBlock:^(id _Nullable value, NSRange range,
                           BOOL *_Nonnull stop) {
                if (condition(value, range)) {
                  found = YES;
                  *stop = YES;
                }
              }];
  return found;
}

+ (BOOL)anyMultiple:(NSArray<NSAttributedStringKey> *_Nonnull)keys
           withHost:(id<EnrichedViewHost> _Nonnull)host
            inRange:(NSRange)range
      withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value,
                                                  NSRange range))condition {
  __block BOOL found = NO;
  for (NSString *key in keys) {
    [host.textView.textStorage
        enumerateAttribute:key
                   inRange:range
                   options:0
                usingBlock:^(id _Nullable value, NSRange range,
                             BOOL *_Nonnull stop) {
                  if (condition(value, range)) {
                    found = YES;
                    *stop = YES;
                  }
                }];
    if (found) {
      return YES;
    }
  }
  return NO;
}

+ (NSArray<StylePair *> *_Nullable)all:(NSAttributedStringKey _Nonnull)key
                              withHost:(id<EnrichedViewHost> _Nonnull)host
                               inRange:(NSRange)range
                         withCondition:
                             (BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value,
                                                           NSRange range))
                                 condition {
  __block NSMutableArray<StylePair *> *occurences =
      [[NSMutableArray<StylePair *> alloc] init];
  [host.textView.textStorage
      enumerateAttribute:key
                 inRange:range
                 options:0
              usingBlock:^(id _Nullable value, NSRange range,
                           BOOL *_Nonnull stop) {
                if (condition(value, range)) {
                  StylePair *pair = [[StylePair alloc] init];
                  pair.rangeValue = [NSValue valueWithRange:range];
                  pair.styleValue = value;
                  [occurences addObject:pair];
                }
              }];
  return occurences;
}

+ (NSArray<StylePair *> *_Nullable)
      allMultiple:(NSArray<NSAttributedStringKey> *_Nonnull)keys
         withHost:(id<EnrichedViewHost> _Nonnull)host
          inRange:(NSRange)range
    withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value,
                                                NSRange range))condition {
  __block NSMutableArray<StylePair *> *occurences =
      [[NSMutableArray<StylePair *> alloc] init];
  for (NSString *key in keys) {
    [host.textView.textStorage
        enumerateAttribute:key
                   inRange:range
                   options:0
                usingBlock:^(id _Nullable value, NSRange range,
                             BOOL *_Nonnull stop) {
                  if (condition(value, range)) {
                    StylePair *pair = [[StylePair alloc] init];
                    pair.rangeValue = [NSValue valueWithRange:range];
                    pair.styleValue = value;
                    [occurences addObject:pair];
                  }
                }];
  }
  return occurences;
}

@end
