//
//  SimpleDatabase.m
//

#import "SimpleDatabase.h"
#import "NanoStore.h"
#import "FileURLHelper.h"

static NSFMatchType SimpleDatabase_NSFMatchTypeFromString(NSString * string) {
 	if ([string isEqualToString:@"NSFEqualTo"]) return NSFEqualTo;
	if ([string isEqualToString:@"NSFBeginsWith"]) return NSFBeginsWith;
	if ([string isEqualToString:@"NSFContains"]) return NSFContains;
	if ([string isEqualToString:@"NSFEndsWith"]) return NSFEndsWith;
	if ([string isEqualToString:@"NSFInsensitiveEqualTo"]) return NSFInsensitiveEqualTo;
	if ([string isEqualToString:@"NSFInsensitiveBeginsWith"]) return NSFInsensitiveBeginsWith;
	if ([string isEqualToString:@"NSFInsensitiveContains"]) return NSFInsensitiveContains;
	if ([string isEqualToString:@"NSFInsensitiveEndsWith"]) return NSFInsensitiveEndsWith;
	if ([string isEqualToString:@"NSFGreaterThan"]) return NSFGreaterThan;
	if ([string isEqualToString:@"NSFLessThan"]) return NSFLessThan;
	if ([string isEqualToString:@"NSFNotEqualTo"]) return NSFNotEqualTo;
	return 0;
}

static const NSString * defaultIDAttribute = @"$id";
static const NSString * defaultTimestampAttribute = @"$timestamp";

@implementation SimpleDatabase {
	NSFNanoStore * _nanoStore;
	NSArray * _allEntries;
}

- (void)awakeFromNib {
	[super awakeFromNib];
	//NSLog(@"identifier: %@", self.identifier);
	NSError * error = nil;
	NSString * path = GetFileURLForDocumentWithID(self.identifier, @"db", @"Databases").path;
	BOOL exists = [NSFileManager.defaultManager fileExistsAtPath:path];
	_nanoStore = [NSFNanoStore createAndOpenStoreWithType:NSFPersistentStoreType path:path error:&error];
	if (_nanoStore.isClosed) {
		NSLog(@"The store is closed. %@", error);
		return;
	}
	if (!exists && self.initialContentsJSON) {
		NSArray * initialContents = [NSJSONSerialization JSONObjectWithData:[self.initialContentsJSON dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
		if (initialContents.count) {
			for (NSDictionary * entry in initialContents) {
				//NSFNanoObject * object = [NSFNanoObject.alloc initFromDictionaryRepresentation:entry];
				//if (object) [_nanoStore addObject:object error:nil];
				[self saveEntry:entry];
			}
		}
	}
}

- (void)addObserver:(id)observer {
	[self numberOfSections];
	[super addObserver:observer];
}

- (NSInteger)numberOfSections {
	if (!_allEntries) {
		_allEntries = [self searchWithInfo:nil];
		//NSLog(@"_allEntries: %@", _allEntries);
	}
	return 1;
}

- (NSInteger)numberOfItemsInSection:(NSInteger)section {
	return _allEntries.count;
}

- (NSDictionary *)datasetForItemAtIndexPath:(NSIndexPath *)indexPath {
	if (indexPath.row < 0 || indexPath.row >= _allEntries.count) return nil;
	return _allEntries[indexPath.row];
}

- (id)contents {
	return _allEntries;
}

+ (instancetype)databaseNamed:(NSString *)identifier
{
	NSString * path = @"";//GetFileURLFromString([NSString stringWithFormat:@"file:%@",identifier]).path;
	return [self databaseFromFile:path];
}

+ (instancetype)databaseFromFile:(NSString *)path {
	if (!path) return nil;
	
	static NSMutableDictionary * storeLUT;
	if (!storeLUT) storeLUT = @{}.mutableCopy;
	if (storeLUT[path]) return storeLUT[path];
	SimpleDatabase * database = [[SimpleDatabase alloc] initWithFile:path];
	storeLUT[path] = database;
	return database;
}

- (instancetype)initWithFile:(NSString *)path {
	self = [super init];
	_nanoStore = [NSFNanoStore createAndOpenStoreWithType:NSFPersistentStoreType path:path error:nil];
	return self;
}

- (BOOL)entryHasIdentifier:(NSDictionary *)entry {
	id theID = [entry objectForKey:defaultIDAttribute];
	if (!theID) return NO;
	if (theID == NSNull.null) return NO;
	return YES;
}

- (BOOL)entryHasTimestamp:(NSDictionary *)entry {
	id timestamp = [entry objectForKey:defaultTimestampAttribute];
	if (!timestamp) return NO;
	if (timestamp == NSNull.null) return NO;
	return YES;
}

- (NSDictionary *)identifiedEntry:(NSDictionary *)entry {
	if ([self entryHasIdentifier:entry]) return entry;
	NSMutableDictionary * mutableEntry = entry.mutableCopy;
	[mutableEntry setObject:[[NSProcessInfo processInfo] globallyUniqueString] forKey:defaultIDAttribute];
	return mutableEntry;
}

- (NSDictionary *)timestampedEntry:(NSDictionary *)entry {
	if ([self entryHasTimestamp:entry]) return entry;
	NSMutableDictionary * mutableEntry = entry.mutableCopy;
	[mutableEntry setObject:[NSNumber numberWithLong:NSDate.date.timeIntervalSince1970] forKey:defaultTimestampAttribute];
	return mutableEntry;
}

- (void)saveEntry:(NSDictionary *)entry
{
	if (![entry isKindOfClass:[NSDictionary class]]) return;

	if ([self entryHasIdentifier:entry]) {
		[self updateEntry:entry];
		return;
	}
	
	entry = [self identifiedEntry:entry];
	entry = [self timestampedEntry:entry];
	
	NSError * error;
	NSFNanoObject * nanoObject = [NSFNanoObject nanoObjectWithDictionary:entry];
	[_nanoStore addObject:nanoObject error:&error];

	if (error) {
		NSLog(@"Error inserting object: %@", error.localizedDescription);
		return;
	}
	
	[self clearAllEntriesCacheAndNotifyObservers];
}

- (void)clearAllEntriesCacheAndNotifyObservers {
	if (_allEntries) {
		_allEntries = [self searchWithInfo:nil];
	}
#if TARGET_OS_IPHONE
	[self notifyObserversWithUpdate:YES];
#endif
}

- (void)deleteEntry:(NSDictionary *)entry {
	//NSLog(@"deleteEntry: %@", entry);
	if (!entry) return;
	
	NSFNanoObject * obj = [self nanoObjectWithIdentifier:[entry objectForKey:defaultIDAttribute]];
	if (!obj) return;
	[_nanoStore removeObject:obj error:nil];
	[self clearAllEntriesCacheAndNotifyObservers];
}

- (void)updateEntry:(NSDictionary *)entry {
	//NSLog(@"updateEntry: %@", entry);

	NSFNanoObject * obj = [self nanoObjectWithIdentifier:[entry objectForKey:defaultIDAttribute]];
	if (!obj) return;
	
	[obj addEntriesFromDictionary:entry];
	
	[_nanoStore addObject:obj error:nil];
	
	[self clearAllEntriesCacheAndNotifyObservers];
}
						   
- (NSFNanoObject *)nanoObjectWithIdentifier:(NSString *)identifier {
	NSFNanoSearch * search = [NSFNanoSearch searchWithStore:_nanoStore];
	search.attribute = defaultIDAttribute.copy;
	search.match = NSFEqualTo;
	search.value = identifier;
	
	NSDictionary * objects = [search searchObjectsWithReturnType:NSFReturnObjects error:nil];
	if (objects.count) return [[objects allValues] objectAtIndex:0];
	return nil;
}

- (void)addEntries:(NSArray *)entries {
	[entries enumerateObjectsUsingBlock:^(NSDictionary * entry, NSUInteger idx, BOOL *stop) {
		[self saveEntry:entry];
	}];
}

- (void)setValues:(NSArray *)values {
	[self addEntries:values];
}

- (void)clear {
	NSError * error = nil;
	[_nanoStore removeAllObjectsFromStoreAndReturnError:&error];
	if (error) {
		NSLog(@"Error: %@", error);
	}
	[self clearAllEntriesCacheAndNotifyObservers];
}

- (NSArray *)searchWithInfo:(NSDictionary *)info {
	NSFNanoSearch *search = [NSFNanoSearch searchWithStore:_nanoStore];
	
	if (info) {
		search.attribute = [info valueForKey:@"attribute"];
		search.match = SimpleDatabase_NSFMatchTypeFromString([info valueForKey:@"match"]);
		search.value = [info valueForKey:@"value"];
	}
	
	NSFNanoSortDescriptor * sortByTimestamp = [[NSFNanoSortDescriptor alloc] initWithAttribute:defaultTimestampAttribute.copy ascending:YES];
	search.sort = @[sortByTimestamp];
	
	// Returns a dictionary with the UUID of the object (key) and the NanoObject (value).
	NSDictionary *searchResults = [search searchObjectsWithReturnType:NSFReturnObjects error:nil];
	
	NSMutableArray * values = @[].mutableCopy;
	//for (NSString * uuid in searchResults) {
	for (NSFNanoObject * obj in searchResults) {
		//NSFNanoObject * obj = searchResults[uuid];
		[values addObject:obj.dictionaryRepresentation];
	}
	
	return values;
}

- (BOOL)isEditable {
	return _nanoStore != nil;
}

- (BOOL)deleteItemAtIndexPath:(NSIndexPath *)indexPath {
	NSDictionary * entry = [self datasetForItemAtIndexPath:indexPath];
	NSFNanoObject * obj = [self nanoObjectWithIdentifier:[entry objectForKey:defaultIDAttribute]];
	if (!obj) return NO;
	[_nanoStore removeObject:obj error:nil];
	_allEntries = nil;
	return YES;
}

@end

#if TARGET_OS_IPHONE

static NSString * NoNilString(NSString * str) {
	if (!str) return @"";
	return str;
}

@implementation SimpleDatabaseSearch {
	BOOL _didInit;
	BOOL _isRefreshing;
	NSArray * _searchResults;
}

- (void)awakeFromNib {
	[super awakeFromNib];
	_didInit = YES;
	
	//NSLog(@"-->: %@", @"awakeFromNib");
	[self refreshDelayed];
}

- (void)load {
	//NSLog(@"-->: %@", @"load");
	_searchResults = [self.database searchWithInfo:@{
										  @"attribute": NoNilString(self.attribute),
										  @"match": NoNilString(self.match),
										  @"value": NoNilString(self.value)
										  }];
	[self notifyObservers];
}

- (void)refresh {
	//NSLog(@"-->: %@", @"refresh");
	[super refresh];
	_isRefreshing = NO;
}

- (void)refreshDelayed {
	if (_isRefreshing) return;
	_isRefreshing = YES;
	[self performSelector:@selector(refresh) withObject:nil afterDelay:0.1];
}


- (void)setAttribute:(NSString *)attribute {
	_attribute = attribute;
	if (_didInit) {
		[self refreshDelayed];
	}
	//NSLog(@"-->: attribute: %@", attribute);
}

- (void)setMatch:(NSString *)match {
	_match = match;
	if (_didInit) {
		[self refreshDelayed];
	}
	//NSLog(@"-->: match: %@", match);
}

- (void)setValue:(NSString *)value {
	_value = value;
	if (_didInit) {
		[self refreshDelayed];
	}
	//NSLog(@"-->: value: %@", value);
}

- (NSInteger)numberOfItemsInSection:(NSInteger)section {
	return _searchResults.count;
}

- (NSInteger)numberOfSections {
	return 1;
}

- (NSDictionary *)datasetForItemAtIndexPath:(NSIndexPath *)indexPath {
	
	if (indexPath.row < 0 || indexPath.row > _searchResults.count) return nil;
	return [_searchResults objectAtIndex:indexPath.row];
}

@end

#endif
