/*
 * Copyright (c) 2016, 2017 Apple Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_LICENSE_HEADER_END@
 */

#import "SCTest.h"
#import "SCTestUtils.h"

@interface SCTestDynamicStore : SCTest
@property SCDynamicStoreRef store;
@property dispatch_semaphore_t sem;
@property int counter;
@end

@implementation SCTestDynamicStore

+ (NSString *)command
{
	return @"dynamic_store";
}

+ (NSString *)commandDescription
{
	return @"Tests the SCDynamicStore code path";
}

- (instancetype)initWithOptions:(NSDictionary *)options
{
	self = [super initWithOptions:options];
	if (self) {
		_store = SCDynamicStoreCreate(kCFAllocatorDefault,
						  CFSTR("SCTest"),
						  NULL,
						  NULL);
		if (_store == NULL) {
			SCTestLog("Could not create session");
			ERR_EXIT;
		}
	}
	return self;
}

- (void)dealloc
{
	if (self.store != NULL) {
		CFRelease(self.store);
		self.store = NULL;
	}
}

- (void)start
{
	CFStringRef key;
	if (self.options[kSCTestDynamicStoreOptionDNS]) {
		key = SCDynamicStoreKeyCreateNetworkGlobalEntity(kCFAllocatorDefault, kSCDynamicStoreDomainState, kSCEntNetDNS);
		CFPropertyListRef value = SCDynamicStoreCopyValue(self.store, key);
		SCTestLog("%@ : %@", key, value);
		CFRelease(key);
		if (value != NULL) {
			CFRelease(value);
		}
	}

	if (self.options[kSCTestDynamicStoreOptionIPv4]) {
		key = SCDynamicStoreKeyCreateNetworkGlobalEntity(kCFAllocatorDefault, kSCDynamicStoreDomainState, kSCEntNetIPv4);
		CFPropertyListRef value = SCDynamicStoreCopyValue(self.store, key);
		SCTestLog("%@ : %@", key, value);
		CFRelease(key);
		if (value != NULL) {
			CFRelease(value);
		}
	}

	if (self.options[kSCTestDynamicStoreOptionIPv6]) {
		key = SCDynamicStoreKeyCreateNetworkGlobalEntity(kCFAllocatorDefault, kSCDynamicStoreDomainState, kSCEntNetIPv6);
		CFPropertyListRef value = SCDynamicStoreCopyValue(self.store, key);
		SCTestLog("%@ : %@", key, value);
		CFRelease(key);
		if (value != NULL) {
			CFRelease(value);
		}
	}

	if (self.options[kSCTestDynamicStoreOptionProxies]) {
		key = SCDynamicStoreKeyCreateNetworkGlobalEntity(kCFAllocatorDefault, kSCDynamicStoreDomainState, kSCEntNetProxies);
		CFPropertyListRef value = SCDynamicStoreCopyValue(self.store, key);
		SCTestLog("%@ : %@", key, value);
		CFRelease(key);
		if (value != NULL) {
			CFRelease(value);
		}
	}

	[self cleanupAndExitWithErrorCode:0];
}

- (void)cleanupAndExitWithErrorCode:(int)error
{
	[super cleanupAndExitWithErrorCode:error];
}

- (BOOL)setup
{
	return YES;
}

- (BOOL)unitTest
{
	if(![self setup]) {
		return NO;
	}

	BOOL allUnitTestsPassed = YES;
	allUnitTestsPassed &= [self unitTestSetAndRemoveValue];
	allUnitTestsPassed &= [self unitTestCopyMultiple];
	allUnitTestsPassed &= [self unitTestSCDynamicStoreCallbackStress];
	allUnitTestsPassed &= [self unitTestSCDynamicStoreSetMultipleStress];

	if(![self tearDown]) {
		return NO;
	}

	return allUnitTestsPassed;
}

- (BOOL)tearDown
{
	return YES;
}

- (BOOL)unitTestSetAndRemoveValue
{
	int iterations = 1000;
	NSDictionary *bogusValue;
	SCTestDynamicStore *test;

	test = [[SCTestDynamicStore alloc] initWithOptions:self.options];
	bogusValue = @{@"Pretty":@"Useless"};

	for (int i = 0; i < iterations; i++) {
		NSUUID *uuid = [NSUUID UUID];
		CFStringRef key;
		BOOL ok;

		key = SCDynamicStoreKeyCreateNetworkServiceEntity(kCFAllocatorDefault,
								  CFSTR("SCTest"),
								  (__bridge CFStringRef)uuid.UUIDString,
								  kSCEntNetDNS);
		ok = SCDynamicStoreSetValue(test.store, key, (__bridge CFDictionaryRef)bogusValue);
		if (!ok) {
			SCTestLog("Failed to set value in SCDynamicStore. Error: %s", SCErrorString(SCError()));
			CFRelease(key);
			return NO;
		}

		ok = SCDynamicStoreRemoveValue(test.store, key);
		if (!ok) {
			SCTestLog("Failed to remove value from SCDynamicStore. Error: %s", SCErrorString(SCError()));
			CFRelease(key);
			return NO;
		}

		CFRelease(key);
	}

	SCTestLog("Successfully completed setAndRemove unit test");
	return YES;
}

- (BOOL)unitTestCopyMultiple
{
	NSString *pattern;
	SCTestDynamicStore *test;
	CFArrayRef keyList;
	int iterations = 1000;

	test = [[SCTestDynamicStore alloc] initWithOptions:self.options];
	pattern = (__bridge_transfer NSString *)SCDynamicStoreKeyCreate(kCFAllocatorDefault,
									CFSTR("%@/%@/%@/%@/%@"),
									kSCDynamicStoreDomainState,
									kSCCompNetwork,
									kSCCompInterface,
									kSCCompAnyRegex,
									kSCCompAnyRegex);

	keyList = SCDynamicStoreCopyKeyList(test.store, (__bridge CFStringRef)pattern);
	for (int i = 0; i < iterations; i++) {
		CFDictionaryRef value = SCDynamicStoreCopyMultiple(test.store, keyList, NULL);
		if (value == NULL) {
			SCTestLog("Failed to copy multiple values from SCDynamicStore. Error: %s", SCErrorString(SCError()));
			CFRelease(keyList);
			return NO;
		}
		CFRelease(value);
	}
	CFRelease(keyList);

	pattern = (__bridge_transfer NSString *)SCDynamicStoreKeyCreate(kCFAllocatorDefault,
									CFSTR("%@/%@/%@/%@/%@"),
									kSCDynamicStoreDomainSetup,
									kSCCompNetwork,
									kSCCompService,
									kSCCompAnyRegex,
									kSCCompAnyRegex);

	keyList = SCDynamicStoreCopyKeyList(test.store, (__bridge CFStringRef)pattern);
	for (int i = 0; i < iterations; i++) {
		CFDictionaryRef value = SCDynamicStoreCopyMultiple(test.store, keyList, NULL);
		if (value == NULL) {
			SCTestLog("Failed to copy multiple values from SCDynamicStore. Error: %s", SCErrorString(SCError()));
			CFRelease(keyList);
			return NO;
		}
		CFRelease(value);
	}
	CFRelease(keyList);

	SCTestLog("Successfully completed copyMultiple unit test");
	return YES;
}

void
myTestCallback(SCDynamicStoreRef store, CFArrayRef changedKeys, void *ctx)
{
#pragma unused(store)
#pragma unused(changedKeys)
	SCTestDynamicStore *test = (__bridge SCTestDynamicStore *)ctx;
	test.counter++;
	if (test.sem != NULL) {
		dispatch_semaphore_signal(test.sem);
	}
}

- (BOOL)unitTestSCDynamicStoreCallbackStress
{
	SCTestDynamicStore *test;
	int iterations = 100;
	NSString *testKey = @"State:/myTestKey";
	BOOL ok;
	dispatch_queue_t callbackQ;

	test = [[SCTestDynamicStore alloc] initWithOptions:self.options];
	if (test.store != NULL) {
		CFRelease(test.store);
		test.store = NULL;
	}

	SCDynamicStoreContext ctx = {0, (__bridge void * _Nullable)(test), CFRetain, CFRelease, NULL};
	test.store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("SCTest"), myTestCallback, &ctx);

	ok = SCDynamicStoreSetNotificationKeys(test.store, (__bridge CFArrayRef)@[testKey], NULL);
	if (!ok) {
		SCTestLog("Failed to set notification keys. Error: %s", SCErrorString(SCError()));
		return NO;
	}

	callbackQ = dispatch_queue_create("SCTestDynamicStore callback queue", NULL);
	ok = SCDynamicStoreSetDispatchQueue(test.store, callbackQ);
	if (!ok) {
		SCTestLog("Failed to set dispatch queue. Error: %s", SCErrorString(SCError()));
		return NO;
	}

	for (int i = 0; i < iterations; i++) {
		NSUUID *uuid = [NSUUID UUID];
		ok = SCDynamicStoreSetValue(test.store, (__bridge CFStringRef)testKey, (__bridge CFStringRef)uuid.UUIDString);
		if (!ok) {
			SCTestLog("Failed to set value. Error: %s", SCErrorString(SCError()));
			return NO;
		}
		// Perform a write to Dynamic Store every 10ms.
		[test waitFor:0.01];
	}

	ok = SCDynamicStoreRemoveValue(test.store, (__bridge CFStringRef)testKey);
	if (!ok) {
		SCTestLog("Failed to remove value. Error: %s", SCErrorString(SCError()));
		return NO;
	}

	if (test.counter < iterations) {
		// Not all callbacks were received
		SCTestLog("Not all SCDynamicStore callbacks were received (%d/%d). Something went wrong", test.counter, iterations);
		return NO;
	}

	SCTestLog("Successfully completed SCDynamicStore callbacks unit test");
	return YES;
}

- (BOOL)unitTestSCDynamicStoreSetMultipleStress
{
	SCTestDynamicStore *test;
	int iterations = 100;
	NSMutableArray *testKeyArray;
	NSMutableDictionary *testSetDictionary;
	int expectedCallbackCount = 0;
	BOOL ok;
	dispatch_queue_t callbackQ;
	uint8_t waitTime = 1; // second

	test = [[SCTestDynamicStore alloc] initWithOptions:self.options];
	if (test.store != NULL) {
		CFRelease(test.store);
		test.store = NULL;
	}

	SCDynamicStoreContext ctx = {0, (__bridge void * _Nullable)(test), CFRetain, CFRelease, NULL};
	test.store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("SCTest"), myTestCallback, &ctx);
	test.sem = dispatch_semaphore_create(0);

	testKeyArray = [[NSMutableArray alloc] init];

	for (int i = 0; i < iterations; i++) {
		NSUUID *uuid = [NSUUID UUID];
		NSString *str = [NSString stringWithFormat:@"State:/%@", uuid];
		[testKeyArray addObject:str];
	}

	testSetDictionary = [[NSMutableDictionary alloc] init];
	for (NSString *key in testKeyArray) {
		NSUUID *uuid = [NSUUID UUID];
		[testSetDictionary setObject:@[uuid.UUIDString] forKey:key];
	}

	callbackQ = dispatch_queue_create("SCTestDynamicStore callback queue", NULL);
	ok = SCDynamicStoreSetNotificationKeys(test.store, (__bridge CFArrayRef)testKeyArray, NULL);
	if (!ok) {
		SCTestLog("Failed to set notification keys. Error: %s", SCErrorString(SCError()));
		return NO;
	}

	ok = SCDynamicStoreSetDispatchQueue(test.store, callbackQ);
	if (!ok) {
		SCTestLog("Failed to set dispatch queue. Error: %s", SCErrorString(SCError()));
		return NO;
	}

	ok = SCDynamicStoreSetMultiple(test.store, (__bridge CFDictionaryRef)testSetDictionary, NULL, NULL);
	if (!ok) {
		SCTestLog("Failed to set multiple keys. Error: %s", SCErrorString(SCError()));
		return NO;
	} else {
		expectedCallbackCount++; // One callback for set multiple
	}

	if(dispatch_semaphore_wait(test.sem, dispatch_time(DISPATCH_TIME_NOW, waitTime * NSEC_PER_SEC))) {
		SCTestLog("Failed to get a callback when multiple keys are set");
		return NO;
	}

	ok = SCDynamicStoreSetMultiple(test.store, NULL, NULL, (__bridge CFArrayRef)testKeyArray);
	if (!ok) {
		SCTestLog("Failed to set multiple keys. Error: %s", SCErrorString(SCError()));
		return NO;
	} else {
		expectedCallbackCount++; // One callback for fake notification
	}

	if(dispatch_semaphore_wait(test.sem, dispatch_time(DISPATCH_TIME_NOW, waitTime * NSEC_PER_SEC))) {
		SCTestLog("Failed to get a callback when multiple keys are notified");
		return NO;
	}

	ok = SCDynamicStoreSetMultiple(test.store, NULL, (__bridge CFArrayRef)testKeyArray, NULL);
	if (!ok) {
		SCTestLog("Failed to set multiple keys. Error: %s", SCErrorString(SCError()));
		return NO;
	} else {
		expectedCallbackCount++; // One callback for key removal
	}

	if(dispatch_semaphore_wait(test.sem, dispatch_time(DISPATCH_TIME_NOW, waitTime * NSEC_PER_SEC))) {
		SCTestLog("Failed to get a callback when multiple keys are removed");
		return NO;
	}

	if (test.counter < expectedCallbackCount) {
		// Not all callbacks were received
		SCTestLog("Not all SCDynamicStore callbacks were received (%d/%d). Something went wrong", test.counter, expectedCallbackCount);
		return NO;
	}

	SCTestLog("Successfully completed SCDynamicStore set multiple unit test");
	return YES;

}


@end
