#import "RNFirebaseDatabaseReference.h"

@implementation RNFirebaseDatabaseReference

#if __has_include(<FirebaseDatabase/FIRDatabase.h>)

- (id)initWithPathAndModifiers:(RCTEventEmitter *)emitter
                           app:(NSString *) app
                           key:(NSString *) key
                       refPath:(NSString *) refPath
                     modifiers:(NSArray *) modifiers {
    self = [super init];
    if (self) {
        _emitter = emitter;
        _app = app;
        _key = key;
        _path = refPath;
        _listeners = [[NSMutableDictionary alloc] init];
        _query = [self buildQueryAtPathWithModifiers:refPath modifiers:modifiers];
    }
    return self;
}

- (void)removeEventListener:(NSString *)eventRegistrationKey {
    FIRDatabaseHandle handle = (FIRDatabaseHandle) [_listeners[eventRegistrationKey] integerValue];
    if (handle) {
        [_query removeObserverWithHandle:handle];
        [_listeners removeObjectForKey:eventRegistrationKey];
    }
}

- (void)on:(NSString *) eventType registration:(NSDictionary *) registration {
    NSString *eventRegistrationKey = registration[@"eventRegistrationKey"];
    if (![self hasEventListener:eventRegistrationKey]) {
        id andPreviousSiblingKeyWithBlock = ^(FIRDataSnapshot *_Nonnull snapshot, NSString *_Nullable previousChildName) {
            [self handleDatabaseEvent:eventType registration:registration dataSnapshot:snapshot previousChildName:previousChildName];
        };
        id errorBlock = ^(NSError *_Nonnull error) {
            NSLog(@"Error onDBEvent: %@", [error debugDescription]);
            [self removeEventListener:eventRegistrationKey];
            [self handleDatabaseError:registration error:error];
        };
        FIRDataEventType firDataEventType = (FIRDataEventType)[self eventTypeFromName:eventType];
        FIRDatabaseHandle handle = [_query observeEventType:firDataEventType andPreviousSiblingKeyWithBlock:andPreviousSiblingKeyWithBlock withCancelBlock:errorBlock];
        _listeners[eventRegistrationKey] = @(handle);
    }
}

- (void)once:(NSString *) eventType
    resolver:(RCTPromiseResolveBlock) resolve
    rejecter:(RCTPromiseRejectBlock) reject {
    FIRDataEventType firDataEventType = (FIRDataEventType)[self eventTypeFromName:eventType];
    [_query observeSingleEventOfType:firDataEventType andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *_Nonnull snapshot, NSString *_Nullable previousChildName) {
        NSDictionary *data = [RNFirebaseDatabaseReference snapshotToDictionary:snapshot previousChildName:previousChildName];
        resolve(data);
    } withCancelBlock:^(NSError *_Nonnull error) {
        NSLog(@"Error onDBEventOnce: %@", [error debugDescription]);
        [RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
    }];
}

- (void)handleDatabaseEvent:(NSString *) eventType
               registration:(NSDictionary *) registration
               dataSnapshot:(FIRDataSnapshot *) dataSnapshot
          previousChildName:(NSString *) previousChildName {
    NSMutableDictionary *event = [[NSMutableDictionary alloc] init];
    NSDictionary *data = [RNFirebaseDatabaseReference snapshotToDictionary:dataSnapshot previousChildName:previousChildName];
    
    [event setValue:data forKey:@"data"];
    [event setValue:_key forKey:@"key"];
    [event setValue:eventType forKey:@"eventType"];
    [event setValue:registration forKey:@"registration"];
    
    // TODO: Temporary fix for https://github.com/invertase/react-native-firebase/issues/233
    // until a better solution comes around
    if (_emitter.bridge) {
        [_emitter sendEventWithName:DATABASE_SYNC_EVENT body:event];
    }
}

- (void)handleDatabaseError:(NSDictionary *) registration
                      error:(NSError *)error {
    NSMutableDictionary *event = [[NSMutableDictionary alloc] init];
    [event setValue:_key forKey:@"key"];
    [event setValue:[RNFirebaseDatabase getJSError:error] forKey:@"error"];
    [event setValue:registration forKey:@"registration"];
    
    // TODO: Temporary fix for https://github.com/invertase/react-native-firebase/issues/233
    // until a better solution comes around
    if (_emitter) {
        [_emitter sendEventWithName:DATABASE_SYNC_EVENT body:event];
    }
}

+ (NSDictionary *)snapshotToDictionary:(FIRDataSnapshot *) dataSnapshot
                     previousChildName:(NSString *) previousChildName {
    NSMutableDictionary *result = [[NSMutableDictionary alloc] init];
    NSDictionary *snapshot = [RNFirebaseDatabaseReference snapshotToDict:dataSnapshot];

    [result setValue:snapshot forKey:@"snapshot"];
    [result setValue:previousChildName forKey:@"previousChildName"];

    return result;
}

+ (NSDictionary *)snapshotToDict:(FIRDataSnapshot *)dataSnapshot {
    NSMutableDictionary *snapshot = [[NSMutableDictionary alloc] init];
    
    [snapshot setValue:dataSnapshot.key forKey:@"key"];
    [snapshot setValue:@(dataSnapshot.exists) forKey:@"exists"];
    [snapshot setValue:@(dataSnapshot.hasChildren) forKey:@"hasChildren"];
    [snapshot setValue:@(dataSnapshot.childrenCount) forKey:@"childrenCount"];
    [snapshot setValue:[RNFirebaseDatabaseReference getChildKeys:dataSnapshot] forKey:@"childKeys"];
    [snapshot setValue:dataSnapshot.priority forKey:@"priority"];
    [snapshot setValue:dataSnapshot.value forKey:@"value"];
    
    return snapshot;
}

+ (NSMutableArray *) getChildKeys:(FIRDataSnapshot *) snapshot {
    NSMutableArray *childKeys = [NSMutableArray array];
    if (snapshot.childrenCount > 0) {
        NSEnumerator *children = [snapshot children];
        FIRDataSnapshot *child;
        while (child = [children nextObject]) {
            [childKeys addObject:child.key];
        }
    }
    return childKeys;
}

- (FIRDatabaseQuery *)buildQueryAtPathWithModifiers:(NSString *) path
                                          modifiers:(NSArray *)modifiers {
    FIRDatabase *firebaseDatabase = [RNFirebaseDatabase getDatabaseForApp:_app];
    FIRDatabaseQuery *query = [[firebaseDatabase reference] child:path];

    for (NSDictionary *modifier in modifiers) {
        NSString *type = [modifier valueForKey:@"type"];
        NSString *name = [modifier valueForKey:@"name"];
        if ([type isEqualToString:@"orderBy"]) {
            if ([name isEqualToString:@"orderByKey"]) {
                query = [query queryOrderedByKey];
            } else if ([name isEqualToString:@"orderByPriority"]) {
                query = [query queryOrderedByPriority];
            } else if ([name isEqualToString:@"orderByValue"]) {
                query = [query queryOrderedByValue];
            } else if ([name isEqualToString:@"orderByChild"]) {
                NSString *key = [modifier valueForKey:@"key"];
                query = [query queryOrderedByChild:key];
            }
        } else if ([type isEqualToString:@"limit"]) {
            int limit = [[modifier valueForKey:@"limit"] integerValue];
            if ([name isEqualToString:@"limitToLast"]) {
                query = [query queryLimitedToLast:limit];
            } else if ([name isEqualToString:@"limitToFirst"]) {
                query = [query queryLimitedToFirst:limit];
            }
        } else if ([type isEqualToString:@"filter"]) {
            NSString *valueType = [modifier valueForKey:@"valueType"];
            NSString *key = [modifier valueForKey:@"key"];
            id value = [self getIdValue:[modifier valueForKey:@"value"] type:valueType];
            if ([name isEqualToString:@"equalTo"]) {
                if (key != nil) {
                    query = [query queryEqualToValue:value childKey:key];
                } else {
                    query = [query queryEqualToValue:value];
                }
            } else if ([name isEqualToString:@"endAt"]) {
                if (key != nil) {
                    query = [query queryEndingAtValue:value childKey:key];
                } else {
                    query = [query queryEndingAtValue:value];
                }
            } else if ([name isEqualToString:@"startAt"]) {
                if (key != nil) {
                    query = [query queryStartingAtValue:value childKey:key];
                } else {
                    query = [query queryStartingAtValue:value];
                }
            }
        }
    }

    return query;
}

- (id)getIdValue:(NSString *)value type:(NSString *)type {
    if ([type isEqualToString:@"number"]) {
        return @(value.doubleValue);
    } else if ([type isEqualToString:@"boolean"]) {
        return @(value.boolValue);
    } else {
        return value;
    }
}

- (BOOL)hasEventListener:(NSString *) eventRegistrationKey {
    return _listeners[eventRegistrationKey] != nil;
}

- (BOOL)hasListeners {
    return [[_listeners allKeys] count] > 0;
}

- (int)eventTypeFromName:(NSString *)name {
    int eventType = FIRDataEventTypeValue;

    if ([name isEqualToString:DATABASE_VALUE_EVENT]) {
        eventType = FIRDataEventTypeValue;
    } else if ([name isEqualToString:DATABASE_CHILD_ADDED_EVENT]) {
        eventType = FIRDataEventTypeChildAdded;
    } else if ([name isEqualToString:DATABASE_CHILD_MODIFIED_EVENT]) {
        eventType = FIRDataEventTypeChildChanged;
    } else if ([name isEqualToString:DATABASE_CHILD_REMOVED_EVENT]) {
        eventType = FIRDataEventTypeChildRemoved;
    } else if ([name isEqualToString:DATABASE_CHILD_MOVED_EVENT]) {
        eventType = FIRDataEventTypeChildMoved;
    }
    return eventType;
}

#endif

@end
