#import <objc/runtime.h>
#import "XCTestCase+Specta.h"
#import "SPTSpec.h"
#import "SPTExample.h"
#import "SPTSharedExampleGroups.h"
#import "SpectaUtility.h"
#import "XCTest+Private.h"

@interface XCTestCase (xct_allSubclasses)

- (NSArray *)allSubclasses;
- (void)_dequeueFailures;

@end

@implementation XCTestCase (Specta)

+ (void)load {
  Method allSubclasses = class_getClassMethod(self, @selector(allSubclasses));
  Method allSubclasses_swizzle = class_getClassMethod(self , @selector(spt_allSubclasses_swizzle));
  method_exchangeImplementations(allSubclasses, allSubclasses_swizzle);

  Method dequeueFailures = class_getInstanceMethod(self, @selector(_dequeueFailures));
  Method dequeueFailures_swizzle = class_getInstanceMethod(self, @selector(spt_dequeueFailures));
  method_exchangeImplementations(dequeueFailures, dequeueFailures_swizzle);
}

+ (NSArray *)spt_allSubclasses_swizzle {
  NSArray *subclasses = [self spt_allSubclasses_swizzle]; // call original
  NSMutableArray *filtered = [NSMutableArray arrayWithCapacity:[subclasses count]];
  // exclude SPTSpec base class and all subclasses of SPTSharedExampleGroups
  for (id subclass in subclasses) {
    if (subclass != [SPTSpec class] && ![subclass isKindOfClass:[SPTSharedExampleGroups class]]) {
      [filtered addObject:subclass];
    }
  }
  return spt_shuffle(filtered);
}

- (void)spt_dequeueFailures {
  void(^dequeueFailures)(void) = ^() {
    [self spt_dequeueFailures];
  };

  BOOL isMainThread = [NSThread isMainThread];
  BOOL isSpectaTest = [self isKindOfClass:[SPTSpec class]];

  if (!isSpectaTest || isMainThread) {
    dequeueFailures();
  } else {
    dispatch_sync(dispatch_get_main_queue(), dequeueFailures);
  }
}

- (void)spt_handleException:(NSException *)exception {
  NSString *description = [exception reason];
  if ([exception userInfo]) {
    id line = [exception userInfo][@"line"];
    id file = [exception userInfo][@"file"];
    if ([line isKindOfClass:[NSNumber class]] && [file isKindOfClass:[NSString class]]) {
      XCTSourceCodeLocation *location = [[XCTSourceCodeLocation alloc] initWithFilePath:file
                                                                             lineNumber:[line unsignedIntegerValue]];
      XCTSourceCodeContext *context = [[XCTSourceCodeContext alloc] initWithLocation:location];
      XCTIssue *issue = [[XCTIssue alloc] initWithType:XCTIssueTypeAssertionFailure
                                    compactDescription:description
                                   detailedDescription:@""
                                     sourceCodeContext:context
                                       associatedError:nil
                                           attachments:@[]];
      [self recordIssue:issue];

      return;
    }
  }
  [self _recordUnexpectedFailureWithDescription:description exception:exception];
}

@end
