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

#import "base/ios/crb_protocol_observers.h"

#include <objc/runtime.h>
#include <stddef.h>

#include <algorithm>
#include <vector>

#include "base/check.h"
#include "base/notreached.h"

@interface CRBProtocolObservers () {
  Protocol* _protocol;
  // ivars declared here are private to the implementation but must be
  // public for allowing the C++ |Iterator| class access to those ivars.
 @public
  // vector of weak pointers to observers.
  std::vector<__weak id> _observers;
  // The nested level of observer iteration.
  // A depth of 0 means nobody is currently iterating on the list of observers.
  int _invocationDepth;
}

// Removes nil observers from the list and is called when the
// |_invocationDepth| reaches 0.
- (void)compact;

@end

namespace {

class Iterator {
 public:
  explicit Iterator(CRBProtocolObservers* protocol_observers);
  ~Iterator();
  id GetNext();

 private:
  CRBProtocolObservers* protocol_observers_;
  size_t index_;
  size_t max_index_;
};

Iterator::Iterator(CRBProtocolObservers* protocol_observers)
    : protocol_observers_(protocol_observers),
      index_(0),
      max_index_(protocol_observers->_observers.size()) {
  DCHECK(protocol_observers_);
  ++protocol_observers->_invocationDepth;
}

Iterator::~Iterator() {
  if (protocol_observers_ && --protocol_observers_->_invocationDepth == 0) {
    [protocol_observers_ compact];
  }
}

id Iterator::GetNext() {
  if (!protocol_observers_) {
    return nil;
  }
  auto& observers = protocol_observers_->_observers;
  // Skip nil elements.
  size_t max_index = std::min(max_index_, observers.size());
  while (index_ < max_index && !observers[index_]) {
    ++index_;
  }
  return index_ < max_index ? observers[index_++] : nil;
}
}  // namespace

@interface CRBProtocolObservers ()

// Designated initializer.
- (instancetype)initWithProtocol:(Protocol*)protocol;

@end

@implementation CRBProtocolObservers

+ (instancetype)observersWithProtocol:(Protocol*)protocol {
  return [[self alloc] initWithProtocol:protocol];
}

- (id)init {
  NOTREACHED();
}

- (id)initWithProtocol:(Protocol*)protocol {
  self = [super init];
  if (self) {
    _protocol = protocol;
  }
  return self;
}

- (Protocol*)protocol {
  return _protocol;
}

- (void)addObserver:(id)observer {
  DCHECK(observer);
  DCHECK([observer conformsToProtocol:self.protocol]);

  if (std::ranges::contains(_observers, observer)) {
    return;
  }

  _observers.push_back(observer);
}

- (void)removeObserver:(id)observer {
  DCHECK(observer);
  auto it = std::ranges::find(_observers, observer);
  if (it != _observers.end()) {
    if (_invocationDepth) {
      *it = nil;
    } else {
      _observers.erase(it);
    }
  }
}

- (BOOL)empty {
  for (id observer : _observers) {
    if (observer != nil) {
      return NO;
    }
  }
  return YES;
}

- (void)executeOnObservers:(ExecutionWithObserverBlock)callback {
  DCHECK(callback);
  if (_observers.empty()) {
    return;
  }
  Iterator it(self);
  @autoreleasepool {
    id observer;
    while ((observer = it.GetNext()) != nil) {
      callback(observer);
    }
  }
}

#pragma mark - NSObject

- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector {
  NSMethodSignature* signature = [super methodSignatureForSelector:selector];
  if (signature) {
    return signature;
  }

  // Look for a required method in the protocol. protocol_getMethodDescription
  // returns a struct whose fields are null if a method for the selector was
  // not found.
  struct objc_method_description description =
      protocol_getMethodDescription(self.protocol, selector, YES, YES);
  if (description.types) {
    return [NSMethodSignature signatureWithObjCTypes:description.types];
  }

  // Look for an optional method in the protocol.
  description = protocol_getMethodDescription(self.protocol, selector, NO, YES);
  if (description.types) {
    return [NSMethodSignature signatureWithObjCTypes:description.types];
  }

  // There is neither a required nor optional method with this selector in the
  // protocol, so invoke -[NSObject doesNotRecognizeSelector:] to raise
  // NSInvalidArgumentException.
  [self doesNotRecognizeSelector:selector];
  return nil;
}

- (void)forwardInvocation:(NSInvocation*)invocation {
  DCHECK(invocation);
  if (_observers.empty()) {
    return;
  }
  SEL selector = [invocation selector];
  Iterator it(self);
  @autoreleasepool {
    id observer;
    while ((observer = it.GetNext()) != nil) {
      if ([observer respondsToSelector:selector]) {
        [invocation invokeWithTarget:observer];
      }
    }
  }
}

#pragma mark - Private

- (void)compact {
  DCHECK(!_invocationDepth);
  std::erase(_observers, nil);
}

@end
