//
//  ZBLog.m
//  Pods-ZBLog_Example
//
//  Created by Jumbo on 2021/3/10.
//

#import "ZBLog.h"
#import "ZBBaseDestination.h"

@interface ZBLog() {
    NSMutableSet *_zb_destinations;
}

@end

@implementation ZBLog

/**
 *  Returns the singleton `ZBLog`.
 *  The instance is used by `ZBLog` class methods.
 *
 *  @return The singleton `ZBLog`.
 */
+ (instancetype)zb_sharedInstance {
    static id zb_sharedInstance = nil;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        zb_sharedInstance = [[self alloc] init];
    });

    return zb_sharedInstance;
}

// a set of active destinations
- (NSMutableSet *)zb_destinations {
    if (!_zb_destinations) {
        _zb_destinations = [[NSMutableSet alloc] init];
    }
    return _zb_destinations;
}

// MARK: Destination Handling

// returns boolean about success
+ (BOOL)zb_addDestination:(ZBBaseDestination *)zb_destination {
    return [self.zb_sharedInstance zb_addDestination:zb_destination];
}

- (BOOL)zb_addDestination:(ZBBaseDestination *)zb_destination {
    if ([self.zb_destinations containsObject:zb_destination]) {
        return NO;
    }
    [self.zb_destinations addObject:zb_destination];
    return YES;
}

// returns boolean about success
+ (BOOL)zb_removeDestination:(ZBBaseDestination *)zb_destination {
    return [self.zb_sharedInstance zb_removeDestination:zb_destination];
}

- (BOOL)zb_removeDestination:(ZBBaseDestination *)zb_destination {
    if (![self.zb_destinations containsObject:zb_destination]) {
        return NO;
    }
    [self.zb_destinations removeObject:zb_destination];
    return YES;
}

// if you need to start fresh
+ (void)zb_removeAllDestinations {
    [self.zb_sharedInstance zb_removeAllDestinations];
}

- (void)zb_removeAllDestinations {
    [self.zb_destinations removeAllObjects];
}

// returns the amount of destinations
+ (NSInteger)zb_countDestinations {
    return [self.zb_sharedInstance zb_countDestinations];
}

- (NSUInteger)zb_countDestinations {
    return self.zb_destinations.count;
}

// returns the current thread name
+ (NSString *)zb_threadName {
    if (NSThread.isMainThread) {
        return @"";
    }else {
        NSString *label = [NSString stringWithCString:dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) encoding:NSUTF8StringEncoding];
        return label ?: NSThread.currentThread.name;
    }
}

// custom logging to manually adjust values, should just be used by other frameworks
+ (void)zb_custom:(ZBLogLevel)zb_level
          zb_file:(const char *)zb_file
      zb_function:(const char *)zb_function
          zb_line:(NSUInteger)zb_line
       zb_context:(id)zb_context
        zb_format:(NSString *)zb_format, ... {
    va_list args;
    
    if (zb_format) {
        va_start(args, zb_format);
        
        NSString *zb_message = [[NSString alloc] initWithFormat:zb_format arguments:args];
        
        va_end(args);
        
        va_start(args, zb_format);
        
        [self.zb_sharedInstance zb_dispatch_send:zb_level
                                   zb_message:zb_message
                                    zb_thread:[self zb_threadName]
                                      zb_file:[NSString stringWithFormat:@"%s", zb_file]
                                  zb_function:[NSString stringWithFormat:@"%s", zb_function]
                                      zb_line:zb_line
                                   zb_context:zb_context];
        
        va_end(args);
    }
}

// internal helper which dispatches send to dedicated queue if minLevel is ok
- (void)zb_dispatch_send:(ZBLogLevel)zb_level
              zb_message:(NSString *)zb_message
               zb_thread:(NSString *)zb_thread
                 zb_file:(NSString *)zb_file
             zb_function:(NSString *)zb_function
                 zb_line:(NSUInteger)zb_line
              zb_context:(id)zb_context {
    
    for (ZBBaseDestination *zb_dest in self.zb_destinations) {
        
        NSString *zb_resolvedMessage;
        
        if (!zb_dest.zb_queue) continue;
        
        zb_resolvedMessage = zb_resolvedMessage == nil ? zb_message : zb_resolvedMessage;
        
        if ([zb_dest zb_shouldLevelBeLogged:zb_level zb_path:zb_file zb_function:zb_function zb_message:zb_message]) {
            // try to convert msg object to String and put it on queue
            NSString *zb_msgStr = zb_resolvedMessage == nil ? zb_message :zb_resolvedMessage;
            
            NSString *zb_f = [self zb_stripParams:zb_function];
            
            if (zb_dest.zb_asynchronously) {
                dispatch_async(zb_dest.zb_queue, ^{
                    [zb_dest zb_send:zb_level zb_msg:zb_msgStr zb_thread:zb_thread zb_file:zb_file zb_function:zb_f zb_line:zb_line
                          zb_context:zb_context];
                });
            }else {
                dispatch_sync(zb_dest.zb_queue, ^{
                    [zb_dest zb_send:zb_level zb_msg:zb_msgStr zb_thread:zb_thread zb_file:zb_file zb_function:zb_f zb_line:zb_line
                          zb_context:zb_context];
                });
            }
        }
    }
}

- (NSString *)zb_stripParams:(NSString *)zb_function {
    NSString *zb_f = zb_function;
    NSRange zb_range = [zb_f rangeOfString:@"("];
    
    if (zb_range.location != NSNotFound) {
        zb_f = [zb_f substringToIndex:zb_range.location];
    }
    zb_f = [zb_f stringByAppendingString:@"()"];
    return zb_f;
}

@end
