// 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/mac/scoped_objc_class_swizzler.h"

#import "base/mac/scoped_nsobject.h"
#include "testing/gtest/include/gtest/gtest.h"

@interface ObjCClassSwizzlerTestOne : NSObject
+ (NSInteger)function;
- (NSInteger)method;
- (NSInteger)modifier;
@end

@interface ObjCClassSwizzlerTestTwo : NSObject
+ (NSInteger)function;
- (NSInteger)method;
- (NSInteger)modifier;
@end

@implementation ObjCClassSwizzlerTestOne : NSObject

+ (NSInteger)function {
  return 10;
}

- (NSInteger)method {
  // Multiply by a modifier to ensure |self| in a swizzled implementation
  // refers to the original object.
  return 1 * [self modifier];
}

- (NSInteger)modifier {
  return 3;
}

@end

@implementation ObjCClassSwizzlerTestTwo : NSObject

+ (NSInteger)function {
  return 20;
}

- (NSInteger)method {
  return 2 * [self modifier];
}

- (NSInteger)modifier {
  return 7;
}

@end

@interface ObjCClassSwizzlerTestOne (AlternateCategory)
- (NSInteger)alternate;
@end

@implementation ObjCClassSwizzlerTestOne (AlternateCategory)
- (NSInteger)alternate {
  return 3 * [self modifier];
}
@end

@interface ObjCClassSwizzlerTestOneChild : ObjCClassSwizzlerTestOne
- (NSInteger)childAlternate;
@end

@implementation ObjCClassSwizzlerTestOneChild
- (NSInteger)childAlternate {
  return 5 * [self modifier];
}
@end

namespace base {
namespace mac {

TEST(ObjCClassSwizzlerTest, SwizzleInstanceMethods) {
  base::scoped_nsobject<ObjCClassSwizzlerTestOne> object_one(
      [[ObjCClassSwizzlerTestOne alloc] init]);
  base::scoped_nsobject<ObjCClassSwizzlerTestTwo> object_two(
      [[ObjCClassSwizzlerTestTwo alloc] init]);
  EXPECT_EQ(3, [object_one method]);
  EXPECT_EQ(14, [object_two method]);

  {
    base::mac::ScopedObjCClassSwizzler swizzler(
        [ObjCClassSwizzlerTestOne class],
        [ObjCClassSwizzlerTestTwo class],
        @selector(method));
    EXPECT_EQ(6, [object_one method]);
    EXPECT_EQ(7, [object_two method]);

    IMP original = swizzler.GetOriginalImplementation();
    id expected_result = reinterpret_cast<id>(3);
    EXPECT_EQ(expected_result, original(object_one, @selector(method)));
  }

  EXPECT_EQ(3, [object_one method]);
  EXPECT_EQ(14, [object_two method]);
}

TEST(ObjCClassSwizzlerTest, SwizzleClassMethods) {
  EXPECT_EQ(10, [ObjCClassSwizzlerTestOne function]);
  EXPECT_EQ(20, [ObjCClassSwizzlerTestTwo function]);

  {
    base::mac::ScopedObjCClassSwizzler swizzler(
        [ObjCClassSwizzlerTestOne class],
        [ObjCClassSwizzlerTestTwo class],
        @selector(function));
    EXPECT_EQ(20, [ObjCClassSwizzlerTestOne function]);
    EXPECT_EQ(10, [ObjCClassSwizzlerTestTwo function]);

    IMP original = swizzler.GetOriginalImplementation();
    id expected_result = reinterpret_cast<id>(10);
    EXPECT_EQ(expected_result,
              original([ObjCClassSwizzlerTestOne class], @selector(function)));
  }

  EXPECT_EQ(10, [ObjCClassSwizzlerTestOne function]);
  EXPECT_EQ(20, [ObjCClassSwizzlerTestTwo function]);
}

TEST(ObjCClassSwizzlerTest, SwizzleViaCategory) {
  base::scoped_nsobject<ObjCClassSwizzlerTestOne> object_one(
      [[ObjCClassSwizzlerTestOne alloc] init]);
  EXPECT_EQ(3, [object_one method]);

  {
    base::mac::ScopedObjCClassSwizzler swizzler(
        [ObjCClassSwizzlerTestOne class],
        @selector(method),
        @selector(alternate));
    EXPECT_EQ(9, [object_one method]);

    IMP original = swizzler.GetOriginalImplementation();
    id expected_result = reinterpret_cast<id>(3);
    EXPECT_EQ(expected_result, original(object_one, @selector(method)));
  }

  EXPECT_EQ(3, [object_one method]);
}

TEST(ObjCClassSwizzlerTest, SwizzleViaInheritance) {
  base::scoped_nsobject<ObjCClassSwizzlerTestOneChild> child(
      [[ObjCClassSwizzlerTestOneChild alloc] init]);
  EXPECT_EQ(3, [child method]);

  {
    base::mac::ScopedObjCClassSwizzler swizzler(
        [ObjCClassSwizzlerTestOneChild class],
        @selector(method),
        @selector(childAlternate));
    EXPECT_EQ(15, [child method]);

    IMP original = swizzler.GetOriginalImplementation();
    id expected_result = reinterpret_cast<id>(3);
    EXPECT_EQ(expected_result, original(child, @selector(method)));
  }

  EXPECT_EQ(3, [child method]);
}

}  // namespace mac
}  // namespace base
