// Copyright 2014 The Chromium Authors. All rights reserved.
// 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 "base/ios/weak_nsobject.h"
#include "base/mac/scoped_nsautorelease_pool.h"
#include "base/mac/scoped_nsobject.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/gtest_mac.h"
#include "testing/platform_test.h"

@protocol TestObserver

@required
- (void)requiredMethod;
- (void)reset;

@optional
- (void)optionalMethod;

@end

// Implements only the required methods in the TestObserver protocol.
@interface TestPartialObserver : NSObject<TestObserver>
@property(nonatomic, readonly) BOOL requiredMethodInvoked;
@end

// Implements all the methods in the TestObserver protocol.
@interface TestCompleteObserver : TestPartialObserver<TestObserver>
@property(nonatomic, readonly) BOOL optionalMethodInvoked;
@end

namespace {

class CRBProtocolObserversTest : public PlatformTest {
 public:
  CRBProtocolObserversTest() {}

 protected:
  void SetUp() override {
    PlatformTest::SetUp();

    observers_.reset([[CRBProtocolObservers observersWithProtocol:
        @protocol(TestObserver)] retain]);

    partial_observer_.reset([[TestPartialObserver alloc] init]);
    EXPECT_FALSE([partial_observer_ requiredMethodInvoked]);

    complete_observer_.reset([[TestCompleteObserver alloc] init]);
    EXPECT_FALSE([complete_observer_ requiredMethodInvoked]);
    EXPECT_FALSE([complete_observer_ optionalMethodInvoked]);
  }

  base::scoped_nsobject<id> observers_;
  base::scoped_nsobject<TestPartialObserver> partial_observer_;
  base::scoped_nsobject<TestCompleteObserver> complete_observer_;
};

// Verifies basic functionality of -[CRBProtocolObservers addObserver:] and
// -[CRBProtocolObservers removeObserver:].
TEST_F(CRBProtocolObserversTest, AddRemoveObserver) {
  // Add an observer and verify that the CRBProtocolObservers instance forwards
  // an invocation to it.
  [observers_ addObserver:partial_observer_];
  [observers_ requiredMethod];
  EXPECT_TRUE([partial_observer_ requiredMethodInvoked]);

  [partial_observer_ reset];
  EXPECT_FALSE([partial_observer_ requiredMethodInvoked]);

  // Remove the observer and verify that the CRBProtocolObservers instance no
  // longer forwards an invocation to it.
  [observers_ removeObserver:partial_observer_];
  [observers_ requiredMethod];
  EXPECT_FALSE([partial_observer_ requiredMethodInvoked]);
}

// Verifies that CRBProtocolObservers correctly forwards the invocation of a
// required method in the protocol.
TEST_F(CRBProtocolObserversTest, RequiredMethods) {
  [observers_ addObserver:partial_observer_];
  [observers_ addObserver:complete_observer_];
  [observers_ requiredMethod];
  EXPECT_TRUE([partial_observer_ requiredMethodInvoked]);
  EXPECT_TRUE([complete_observer_ requiredMethodInvoked]);
}

// Verifies that CRBProtocolObservers correctly forwards the invocation of an
// optional method in the protocol.
TEST_F(CRBProtocolObserversTest, OptionalMethods) {
  [observers_ addObserver:partial_observer_];
  [observers_ addObserver:complete_observer_];
  [observers_ optionalMethod];
  EXPECT_FALSE([partial_observer_ requiredMethodInvoked]);
  EXPECT_FALSE([complete_observer_ requiredMethodInvoked]);
  EXPECT_TRUE([complete_observer_ optionalMethodInvoked]);
}

// Verifies that CRBProtocolObservers only holds a weak reference to an
// observer.
TEST_F(CRBProtocolObserversTest, WeakReference) {
  base::WeakNSObject<TestPartialObserver> weak_observer(
      partial_observer_);
  EXPECT_TRUE(weak_observer);

  [observers_ addObserver:partial_observer_];

  {
    // Need an autorelease pool here, because
    // -[CRBProtocolObservers forwardInvocation:] creates a temporary
    // autoreleased array that holds all the observers.
    base::mac::ScopedNSAutoreleasePool pool;
    [observers_ requiredMethod];
    EXPECT_TRUE([partial_observer_ requiredMethodInvoked]);
  }

  partial_observer_.reset();
  EXPECT_FALSE(weak_observer.get());
}

}  // namespace

@implementation TestPartialObserver {
  BOOL _requiredMethodInvoked;
}

- (BOOL)requiredMethodInvoked {
  return _requiredMethodInvoked;
}

- (void)requiredMethod {
  _requiredMethodInvoked = YES;
}

- (void)reset {
  _requiredMethodInvoked = NO;
}

@end

@implementation TestCompleteObserver {
  BOOL _optionalMethodInvoked;
}

- (BOOL)optionalMethodInvoked {
  return _optionalMethodInvoked;
}

- (void)optionalMethod {
  _optionalMethodInvoked = YES;
}

- (void)reset {
  [super reset];
  _optionalMethodInvoked = NO;
}

@end