//
//  secd-210-keyinterest.m
//  Security
//
//  Created by Mitch Adler on 10/31/16.
//
//

#import <Foundation/Foundation.h>

#include "secd_regressions.h"

#import "CKDStore.h"
#import "CKDKVSProxy.h"
#import "CKDSimulatedStore.h"
#import "CKDSimulatedAccount.h"
#import "CKDAKSLockMonitor.h"
#include "SOSCloudKeychainConstants.h"
#include "SOSAccountTesting.h"


#if SOS_ENABLED

@interface CKDSimulatedLockMonitor : NSObject<CKDLockMonitor>

@property (readwrite) BOOL unlockedSinceBoot;
@property (readwrite) BOOL locked;

@property (weak) NSObject<CKDLockListener>* listener;

+ (instancetype) monitor;

- (instancetype) init;

- (void) recheck;

- (void) notifyListener;
- (void) connectTo: (NSObject<CKDLockListener>*) listener;

- (void) lock;
- (void) unlock;

@end


@implementation CKDSimulatedLockMonitor

+ (instancetype) monitor {
    return [[CKDSimulatedLockMonitor alloc] init];
}

- (instancetype) init {
    if ((self = [super init])) {
        _locked = true;
        _unlockedSinceBoot = false;
    }

    [self notifyListener];

    return self;
}

- (void) recheck {
}

- (void) notifyListener {
    // Take a strong reference:
    __strong __typeof(self.listener) listener = self.listener;

    if (listener) {
        if (self.locked) {
            [listener locked];
        } else {
            [listener unlocked];
        }
    }
}

- (void) connectTo: (NSObject<CKDLockListener>*) listener {
    self.listener = listener;
    [self notifyListener];
}

- (void) lock {
    self.locked = true;
    [self notifyListener];
}
- (void) unlock {
    self.locked = false;
    self.unlockedSinceBoot = true;
    [self notifyListener];
}


@end

@interface UbiqitousKVSProxy (Testing)
- (void) flush;
@end

@implementation UbiqitousKVSProxy (Testing)
- (void) flush {
    dispatch_semaphore_t sema = dispatch_semaphore_create(0);
    
    [self doAfterFlush:^{
        dispatch_semaphore_signal(sema);
    }];

    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
@end

static void tests(void) {
    CKDSimulatedStore* store = [CKDSimulatedStore simulatedInterface];
    CKDSimulatedAccount* account = [[CKDSimulatedAccount alloc] init];
    CKDSimulatedLockMonitor* monitor = [CKDSimulatedLockMonitor monitor];

    NSString * testKey = @"TestKey";

    UbiqitousKVSProxy * proxy = [UbiqitousKVSProxy withAccount:account
                                                         store:store
                                                   lockMonitor:monitor
                                                   persistence:[NSURL fileURLWithPath:@"/tmp/kvsPersistenceTestFile"]];

    NSDictionary* interests = @{ [NSString stringWithUTF8String:kMessageKeyParameter]:@{ @"UnlockedKeys":@[ testKey ] } };
    NSString* accountID = @"Account1";

    dispatch_sync([proxy ckdkvsproxy_queue], ^{
        [proxy registerKeys:interests forAccount:accountID];
    });

    is([[account extractKeyChanges] count], (NSUInteger)0, "No changes yet");

    [store remoteSetObject:@1 forKey:testKey];
    [proxy flush];

    is([[account extractKeyChanges] count], (NSUInteger)0, "Still none while locked");

    [monitor unlock];
    [proxy flush];

    is([[account extractKeyChanges] count], (NSUInteger)1, "Notified after unlock");

    [monitor lock];
    [monitor unlock];
    [proxy flush];

    is([[account extractKeyChanges] count], (NSUInteger)0, "lock unlock and nothing changes");

    [store remoteSetObject:@2 forKey:testKey];
    [proxy flush];

    {
        NSDictionary<NSString*, NSObject*> *changes = [account extractKeyChanges];
        is([changes count], (NSUInteger)1, "lock, nothing changes");
        is(changes[testKey], @2, "Sent second value");
    }

    [monitor lock];
    [store remoteSetObject:@3 forKey:testKey];
    [proxy flush];

    is([[account extractKeyChanges] count], (NSUInteger)0, "Changes to Unlocked not when locked");

    [monitor unlock];
    [proxy flush];

    {
        NSDictionary<NSString*, NSObject*> *changes = [account extractKeyChanges];
        is([changes count], (NSUInteger)1, "Change defered to after unlock");
        is(changes[testKey], @3, "Correct value");
    }

    dispatch_sync([proxy ckdkvsproxy_queue], ^{
        [proxy registerKeys:interests forAccount:accountID];
    });
    [proxy flush];

    is([[account extractKeyChanges] count], (NSUInteger)0, "Same interests, no new data");

    dispatch_sync([proxy ckdkvsproxy_queue], ^{
        [proxy registerKeys:interests forAccount:@"different"];
    });
    [proxy flush];

    {
        NSDictionary<NSString*, NSObject*> *changes = [account extractKeyChanges];
        is([changes count], (NSUInteger)1, "New account, same interests, new data");
        is(changes[testKey], @3, "Latest value for new data");
    }

}

#endif

int secd_210_keyinterest(int argc, char *const *argv)
{
#if SOS_ENABLED
    plan_tests(12);
    tests();
#else
    plan_tests(0);
#endif
    return 0;
}
