#import "TGBridgeUserCache.h"
#import "TGFileCache.h"

#import <libkern/OSAtomic.h>

#import "TGBridgeUser.h"
#import "TGBridgeBotInfo.h"

@interface TGBridgeUserCache ()
{
    NSMutableDictionary *_userByUid;
    OSSpinLock _userByUidLock;
    
    NSMutableDictionary *_botInfoByUid;
    OSSpinLock _botInfoByUidLock;
    
    TGFileCache *_fileCache;
}
@end

@implementation TGBridgeUserCache

- (instancetype)init
{
    self = [super init];
    if (self != nil)
    {
        _userByUid = [[NSMutableDictionary alloc] init];
        _botInfoByUid = [[NSMutableDictionary alloc] init];
        
        _fileCache = [[TGFileCache alloc] initWithName:@"users" useMemoryCache:false];
    }
    return self;
}

- (TGBridgeUser *)userWithId:(int32_t)userId
{
    __block TGBridgeUser *user = nil;
    
    OSSpinLockLock(&_userByUidLock);
    user = _userByUid[@(userId)];
    OSSpinLockUnlock(&_userByUidLock);
    
    return user;
}

- (NSDictionary *)usersWithIndexSet:(NSIndexSet *)indexSet
{
    NSMutableDictionary *users = [[NSMutableDictionary alloc] init];
    NSMutableIndexSet *neededUsers = [indexSet mutableCopy];
    
    NSMutableIndexSet *foundUsers = [[NSMutableIndexSet alloc] init];
    
    OSSpinLockLock(&_userByUidLock);
    [neededUsers enumerateIndexesUsingBlock:^(NSUInteger index, BOOL * _Nonnull stop)
    {
        TGBridgeUser *user = _userByUid[@(index)];
        if (user != nil)
        {
            users[@(index)] = user;
            [foundUsers addIndex:index];
        }
    }];
    OSSpinLockUnlock(&_userByUidLock);
    
    [neededUsers removeIndexes:foundUsers];
    
    return users;
}

- (void)storeUser:(TGBridgeUser *)user
{
    if (user == nil)
        return;
    
    [self storeUsers:@[ user ]];
}

- (void)storeUsers:(NSArray *)users
{
    OSSpinLockLock(&_userByUidLock);
    for (id peer in users)
    {
        if ([peer isKindOfClass:[TGBridgeUser class]])
            _userByUid[@(((TGBridgeUser *)peer).identifier)] = peer;
    }
    OSSpinLockUnlock(&_userByUidLock);
}

- (NSArray *)applyUserChanges:(NSArray *)userChanges
{
    NSMutableArray *missedUserIds = [[NSMutableArray alloc] init];
    NSMutableArray *updatedUsers = [[NSMutableArray alloc] init];
    for (TGBridgeUserChange *change in userChanges)
    {
        TGBridgeUser *user = [self userWithId:change.userIdentifier];
        if (user != nil)
        {
            TGBridgeUser *updatedUser = [user userByApplyingChange:change];
            [updatedUsers addObject:updatedUser];
        }
        else
        {
            [missedUserIds addObject:@(change.userIdentifier)];
        }
    }
    
    [self storeUsers:updatedUsers];
    
    if (missedUserIds.count == 0)
        return nil;
    
    return missedUserIds;
}

- (TGBridgeBotInfo *)botInfoForUserId:(int32_t)userId
{
    __block TGBridgeBotInfo *botInfo = nil;
    
    OSSpinLockLock(&_botInfoByUidLock);
    botInfo = _botInfoByUid[@(userId)];
    OSSpinLockUnlock(&_botInfoByUidLock);
    
    if (botInfo == nil)
    {
        [_fileCache fetchDataForKey:[NSString stringWithFormat:@"botInfo-%d", userId] synchronous:true unserializeBlock:^id(NSData *data)
        {
            id object = [NSKeyedUnarchiver unarchiveObjectWithData:data];
            if ([object isKindOfClass:[TGBridgeBotInfo class]])
                return object;
             
            return nil;
        } completion:^(TGBridgeBotInfo *result)
        {
            if (result != nil)
            {
                botInfo = result;
                OSSpinLockLock(&_botInfoByUidLock);
                _botInfoByUid[@(userId)] = botInfo;
                OSSpinLockUnlock(&_botInfoByUidLock);
            }
        }];
    }
    
    return botInfo;
}

- (void)storeBotInfo:(TGBridgeBotInfo *)botInfo forUserId:(int32_t)userId
{
    OSSpinLockLock(&_botInfoByUidLock);
    _botInfoByUid[@(userId)] = botInfo;
    
    [_fileCache cacheData:botInfo key:[NSString stringWithFormat:@"botInfo-%d", userId] synchronous:true serializeBlock:^NSData *(NSObject<NSCoding> *object)
    {
        return [NSKeyedArchiver archivedDataWithRootObject:object];
    } completion:nil];
    OSSpinLockUnlock(&_botInfoByUidLock);
}

+ (instancetype)instance
{
    static dispatch_once_t onceToken;
    static TGBridgeUserCache *userCache;
    dispatch_once(&onceToken, ^
    {
        userCache = [[TGBridgeUserCache alloc] init];
    });
    return userCache;
}

@end
