#import "DirectoryWatcher.h"
#import <CoreServices/CoreServices.h>

void callback(ConstFSEventStreamRef streamRef, void * info, size_t numEvents, void * eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[]);

@interface DirectoryWatcher ()

@property NSString * path;
@property NSMutableSet<NSString*>* knownFiles;
@property FSEventStreamRef stream;

- (void)rescanPathWithEvents:(BOOL)sendEvents;

@end

@implementation DirectoryWatcher

- (instancetype)initWithPath:(NSString *)path {
    if (!path) {
        return nil;
    }
    
    self = [super init];
    if (self) {
        self.path = path;
    }
    return self;
}

- (void)dealloc {
    [self stopWatching];
}

- (void)startWatching {
    self.knownFiles = [NSMutableSet new];
    [self rescanPathWithEvents:NO];
    
    CFStringRef path = (__bridge CFStringRef)(self.path);
    CFArrayRef pathsToWatch = CFArrayCreate(NULL, (const void**)&path, 1, NULL);
    CFAbsoluteTime latency = 1;
    FSEventStreamContext context = { 0, (__bridge void * _Nullable)(self), NULL, NULL, NULL };
    self.stream = FSEventStreamCreate(NULL, &callback, &context, pathsToWatch, kFSEventStreamEventIdSinceNow, latency, kFSEventStreamCreateFlagNone);
    FSEventStreamScheduleWithRunLoop(self.stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
    FSEventStreamStart(self.stream);
}

- (void)stopWatching {
    if (self.stream) {
        FSEventStreamStop(self.stream);
        FSEventStreamInvalidate(self.stream);
        FSEventStreamRelease(self.stream);
        self.stream = nil;
    }
}

- (void)rescanPathWithEvents:(BOOL)sendEvents {
    NSArray<NSString*>* downloadFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.path error:nil];
    NSMutableSet<NSString*>* fullSet = [NSMutableSet new];
    for (NSString * file in downloadFiles) {
        [fullSet addObject:file];
        if (![self.knownFiles containsObject:file]) {
            if (sendEvents) {
                [self.delegate watcher:self observedNewFileAtPath:file];
            }
        }
    }
    self.knownFiles = fullSet;
}

@end

void callback(ConstFSEventStreamRef streamRef, void * info, size_t numEvents, void * eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[]) {
    DirectoryWatcher * watcher = (__bridge DirectoryWatcher*)info;
    [watcher rescanPathWithEvents:YES];
}