/*
 * Copyright 2017 Google
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#import "FViewProcessor.h"
#import "FCompleteChildSource.h"
#import "FWriteTreeRef.h"
#import "FViewCache.h"
#import "FCacheNode.h"
#import "FNode.h"
#import "FOperation.h"
#import "FOperationSource.h"
#import "FChildChangeAccumulator.h"
#import "FNodeFilter.h"
#import "FOverwrite.h"
#import "FMerge.h"
#import "FAckUserWrite.h"
#import "FViewProcessorResult.h"
#import "FIRDataEventType.h"
#import "FChange.h"
#import "FEmptyNode.h"
#import "FChildrenNode.h"
#import "FPath.h"
#import "FKeyIndex.h"
#import "FCompoundWrite.h"
#import "FImmutableTree.h"

/**
* An implementation of FCompleteChildSource that never returns any additional children
*/
@interface FNoCompleteChildSource: NSObject<FCompleteChildSource>
@end

@implementation FNoCompleteChildSource
+ (FNoCompleteChildSource *) instance {
    static FNoCompleteChildSource *source = nil;
    static dispatch_once_t once;
    dispatch_once(&once, ^{
        source = [[FNoCompleteChildSource alloc] init];
    });
    return source;
}

- (id<FNode>) completeChild:(NSString *)childKey {
    return nil;
}

- (FNamedNode *) childByIndex:(id<FIndex>)index afterChild:(FNamedNode *)child isReverse:(BOOL)reverse {
    return nil;
}
@end

/**
* An implementation of FCompleteChildSource that uses a FWriteTree in addition to any other server data or
* old event caches available to calculate complete children.
*/
@interface FWriteTreeCompleteChildSource: NSObject<FCompleteChildSource>
@property (nonatomic, strong) FWriteTreeRef *writes;
@property (nonatomic, strong) FViewCache *viewCache;
@property (nonatomic, strong) id<FNode> optCompleteServerCache;
@end

@implementation FWriteTreeCompleteChildSource
- (id) initWithWrites:(FWriteTreeRef *)writes viewCache:(FViewCache *)viewCache serverCache:(id<FNode>)optCompleteServerCache {
    self = [super init];
    if (self) {
        self.writes = writes;
        self.viewCache = viewCache;
        self.optCompleteServerCache = optCompleteServerCache;
    }
    return self;
}

- (id<FNode>) completeChild:(NSString *)childKey {
    FCacheNode *node = self.viewCache.cachedEventSnap;
    if ([node isCompleteForChild:childKey]) {
        return [node.node getImmediateChild:childKey];
    } else {
        FCacheNode *serverNode;
        if (self.optCompleteServerCache) {
            // Since we're only ever getting child nodes, we can use the key index here
            FIndexedNode *indexed = [FIndexedNode indexedNodeWithNode:self.optCompleteServerCache index:[FKeyIndex keyIndex]];
            serverNode = [[FCacheNode alloc] initWithIndexedNode:indexed isFullyInitialized:YES isFiltered:NO];
        } else {
            serverNode = self.viewCache.cachedServerSnap;
        }
        return [self.writes calculateCompleteChild:childKey cache:serverNode];
    }
}

- (FNamedNode *) childByIndex:(id<FIndex>)index afterChild:(FNamedNode *)child isReverse:(BOOL)reverse {
    id<FNode> completeServerData = self.optCompleteServerCache != nil
            ? self.optCompleteServerCache
            : self.viewCache.completeServerSnap;
    return [self.writes calculateNextNodeAfterPost:child
                                completeServerData:completeServerData
                                           reverse:reverse
                                             index:index];
}

@end

@interface FViewProcessor ()
@property (nonatomic, strong) id<FNodeFilter> filter;
@end

@implementation FViewProcessor

- (id)initWithFilter:(id<FNodeFilter>)nodeFilter {
    self = [super init];
    if (self) {
        self.filter = nodeFilter;
    }
    return self;
}

- (FViewProcessorResult *)applyOperationOn:(FViewCache *)oldViewCache operation:(id<FOperation>)operation writesCache:(FWriteTreeRef *)writesCache completeCache:(id <FNode>)optCompleteCache {
    FChildChangeAccumulator *accumulator = [[FChildChangeAccumulator alloc] init];
    FViewCache *newViewCache;

    if (operation.type == FOperationTypeOverwrite) {
        FOverwrite *overwrite = (FOverwrite *) operation;
        if (operation.source.fromUser) {
            newViewCache = [self applyUserOverwriteTo:oldViewCache
                                           changePath:overwrite.path
                                          changedSnap:overwrite.snap
                                          writesCache:writesCache
                                        completeCache:optCompleteCache
                                          accumulator:accumulator];
        } else {
            NSAssert(operation.source.fromServer, @"Unknown source for overwrite.");
            // We filter the node if it's a tagged update or the node has been previously filtered  and the update is
            // not at the root in which case it is ok (and necessary) to mark the node unfiltered again
            BOOL filterServerNode = overwrite.source.isTagged || (oldViewCache.cachedServerSnap.isFiltered &&
                                                                  !overwrite.path.isEmpty);
            newViewCache = [self applyServerOverwriteTo:oldViewCache
                                             changePath:overwrite.path
                                                   snap:overwrite.snap
                                            writesCache:writesCache
                                          completeCache:optCompleteCache
                                       filterServerNode:filterServerNode
                                            accumulator:accumulator];
        }
    } else if (operation.type == FOperationTypeMerge) {
        FMerge *merge = (FMerge*)operation;
        if (operation.source.fromUser) {
            newViewCache = [self applyUserMergeTo:oldViewCache
                                             path:merge.path
                                  changedChildren:merge.children
                                      writesCache:writesCache
                                    completeCache:optCompleteCache
                                      accumulator:accumulator];
        } else {
            NSAssert(operation.source.fromServer, @"Unknown source for merge.");
            // We filter the node if it's a tagged update or the node has been previously filtered
            BOOL filterServerNode = merge.source.isTagged || oldViewCache.cachedServerSnap.isFiltered;
            newViewCache = [self applyServerMergeTo:oldViewCache
                                               path:merge.path
                                    changedChildren:merge.children
                                        writesCache:writesCache
                                      completeCache:optCompleteCache
                                   filterServerNode:filterServerNode
                                        accumulator:accumulator];
        }
    } else if (operation.type == FOperationTypeAckUserWrite) {
        FAckUserWrite *ackWrite = (FAckUserWrite *) operation;
        if (!ackWrite.revert) {
            newViewCache = [self ackUserWriteOn:oldViewCache
                                        ackPath:ackWrite.path
                                   affectedTree:ackWrite.affectedTree
                                    writesCache:writesCache
                                  completeCache:optCompleteCache
                                    accumulator:accumulator];
        } else {
            newViewCache = [self revertUserWriteOn:oldViewCache
                                      path:ackWrite.path
                               writesCache:writesCache
                             completeCache:optCompleteCache
                               accumulator:accumulator];
        }
    } else if (operation.type == FOperationTypeListenComplete) {
        newViewCache = [self listenCompleteOldCache:oldViewCache
                                               path:operation.path
                                        writesCache:writesCache
                                        serverCache:optCompleteCache
                                        accumulator:accumulator];
    } else {
        [NSException raise:NSInternalInconsistencyException
                    format:@"Unknown operation encountered %ld.", (long)operation.type];
        return nil;
    }

    NSArray *changes = [self maybeAddValueFromOldViewCache:oldViewCache newViewCache:newViewCache changes:accumulator.changes];
    FViewProcessorResult *results = [[FViewProcessorResult alloc] initWithViewCache:newViewCache changes:changes];
    return results;
}

- (NSArray *) maybeAddValueFromOldViewCache:(FViewCache *)oldViewCache newViewCache:(FViewCache *)newViewCache changes:(NSArray *)changes {
    NSArray *newChanges = changes;
    FCacheNode *eventSnap = newViewCache.cachedEventSnap;
    if (eventSnap.isFullyInitialized) {
        BOOL isLeafOrEmpty = eventSnap.node.isLeafNode || eventSnap.node.isEmpty;
        if ([changes count] > 0 ||
                !oldViewCache.cachedEventSnap.isFullyInitialized ||
                (isLeafOrEmpty && ![eventSnap.node isEqual:oldViewCache.completeEventSnap]) ||
                ![eventSnap.node.getPriority isEqual:oldViewCache.completeEventSnap.getPriority]) {
            FChange *valueChange = [[FChange alloc] initWithType:FIRDataEventTypeValue indexedNode:eventSnap.indexedNode];
            NSMutableArray *mutableChanges = [changes mutableCopy];
            [mutableChanges addObject:valueChange];
            newChanges = mutableChanges;
        }
    }
    return newChanges;
}

- (FViewCache *) generateEventCacheAfterServerEvent:(FViewCache *)viewCache
                                       path:(FPath *)changePath
                                writesCache:(FWriteTreeRef *)writesCache
                                     source:(id<FCompleteChildSource>)source
                                accumulator:(FChildChangeAccumulator *)accumulator {
    FCacheNode *oldEventSnap = viewCache.cachedEventSnap;
    if ([writesCache shadowingWriteAtPath:changePath] != nil) {
        // we have a shadowing write, ignore changes.
        return viewCache;
    } else {
        FIndexedNode *newEventCache;
        if (changePath.isEmpty) {
            // TODO: figure out how this plays with "sliding ack windows"
            NSAssert(viewCache.cachedServerSnap.isFullyInitialized, @"If change path is empty, we must have complete server data");
            id<FNode> nodeWithLocalWrites;
            if (viewCache.cachedServerSnap.isFiltered) {
                // We need to special case this, because we need to only apply writes to complete children, or
                // we might end up raising events for incomplete children. If the server data is filtered deep
                // writes cannot be guaranteed to be complete
                id<FNode> serverCache = viewCache.completeServerSnap;
                FChildrenNode *completeChildren = ([serverCache isKindOfClass:[FChildrenNode class]]) ? serverCache : [FEmptyNode emptyNode];
                nodeWithLocalWrites = [writesCache calculateCompleteEventChildrenWithCompleteServerChildren:completeChildren];
            } else {
                nodeWithLocalWrites = [writesCache calculateCompleteEventCacheWithCompleteServerCache:viewCache.completeServerSnap];
            }
            FIndexedNode *indexedNode = [FIndexedNode indexedNodeWithNode:nodeWithLocalWrites index:self.filter.index];
            newEventCache = [self.filter updateFullNode:viewCache.cachedEventSnap.indexedNode
                                            withNewNode:indexedNode
                                            accumulator:accumulator];
        } else {
            NSString *childKey = [changePath getFront];
            if ([childKey isEqualToString:@".priority"]) {
                NSAssert(changePath.length == 1, @"Can't have a priority with additional path components");
                id<FNode> oldEventNode = oldEventSnap.node;
                id<FNode> serverNode = viewCache.cachedServerSnap.node;
                // we might have overwrites for this priority
                id<FNode> updatedPriority = [writesCache calculateEventCacheAfterServerOverwriteWithChildPath:changePath
                                                                                            existingEventSnap:oldEventNode
                                                                                           existingServerSnap:serverNode];
                if (updatedPriority != nil) {
                    newEventCache = [self.filter updatePriority:updatedPriority forNode:oldEventSnap.indexedNode];
                } else {
                    // priority didn't change, keep old node
                    newEventCache = oldEventSnap.indexedNode;
                }
            } else {
                FPath *childChangePath = [changePath popFront];
                id<FNode> newEventChild;
                if ([oldEventSnap isCompleteForChild:childKey]) {
                    id<FNode> serverNode = viewCache.cachedServerSnap.node;
                    id<FNode> eventChildUpdate = [writesCache calculateEventCacheAfterServerOverwriteWithChildPath:changePath existingEventSnap:oldEventSnap.node existingServerSnap:serverNode];
                    if (eventChildUpdate != nil) {
                        newEventChild = [[oldEventSnap.node getImmediateChild:childKey] updateChild:childChangePath withNewChild:eventChildUpdate];
                    } else {
                        // Nothing changed, just keep the old child
                        newEventChild = [oldEventSnap.node getImmediateChild:childKey];
                    }
                } else {
                    newEventChild = [writesCache calculateCompleteChild:childKey cache:viewCache.cachedServerSnap];
                }
                if (newEventChild != nil) {
                    newEventCache = [self.filter updateChildIn:oldEventSnap.indexedNode
                                                   forChildKey:childKey
                                                      newChild:newEventChild
                                                  affectedPath:childChangePath
                                                    fromSource:source
                                                   accumulator:accumulator];
                } else {
                    // No complete children available or no change
                    newEventCache = oldEventSnap.indexedNode;
                }
            }
        }
        return [viewCache updateEventSnap:newEventCache
                               isComplete:(oldEventSnap.isFullyInitialized || changePath.isEmpty)
                               isFiltered:self.filter.filtersNodes];
    }
}

- (FViewCache *) applyServerOverwriteTo:(FViewCache *)oldViewCache changePath:(FPath *)changePath snap:(id<FNode>)changedSnap
                        writesCache:(FWriteTreeRef *)writesCache completeCache:(id<FNode>)optCompleteCache
                       filterServerNode:(BOOL)filterServerNode accumulator:(FChildChangeAccumulator *)accumulator {
    FCacheNode *oldServerSnap = oldViewCache.cachedServerSnap;
    FIndexedNode *newServerCache;
    id<FNodeFilter> serverFilter = filterServerNode ? self.filter : self.filter.indexedFilter;

    if (changePath.isEmpty) {
        FIndexedNode *indexed = [FIndexedNode indexedNodeWithNode:changedSnap index:serverFilter.index];
        newServerCache = [serverFilter updateFullNode:oldServerSnap.indexedNode withNewNode:indexed accumulator:nil];
    } else if (serverFilter.filtersNodes && !oldServerSnap.isFiltered) {
        // We want to filter the server node, but we didn't filter the server node yet, so simulate a full update
        NSAssert(![changePath isEmpty], @"An empty path should been caught in the other branch");
        NSString *childKey = [changePath getFront];
        FPath *updatePath = [changePath popFront];
        id<FNode> newChild = [[oldServerSnap.node getImmediateChild:childKey] updateChild:updatePath
                                                                             withNewChild:changedSnap];
        FIndexedNode *indexed = [oldServerSnap.indexedNode updateChild:childKey withNewChild:newChild];
        newServerCache = [serverFilter updateFullNode:oldServerSnap.indexedNode withNewNode:indexed accumulator:nil];
    } else {
        NSString *childKey = [changePath getFront];
        if (![oldServerSnap isCompleteForPath:changePath] && changePath.length > 1) {
            // We don't update incomplete nodes with updates intended for other listeners.
            return oldViewCache;
        }
        FPath *childChangePath = [changePath popFront];
        id<FNode> childNode = [oldServerSnap.node getImmediateChild:childKey];
        id<FNode> newChildNode = [childNode updateChild:childChangePath withNewChild:changedSnap];
        if ([childKey isEqualToString:@".priority"]) {
            newServerCache = [serverFilter updatePriority:newChildNode forNode:oldServerSnap.indexedNode];
        } else {
            newServerCache = [serverFilter updateChildIn:oldServerSnap.indexedNode
                                             forChildKey:childKey
                                                newChild:newChildNode
                                            affectedPath:childChangePath
                                              fromSource:[FNoCompleteChildSource instance]
                                             accumulator:nil];
        }
    }
    FViewCache *newViewCache = [oldViewCache updateServerSnap:newServerCache
                                                   isComplete:(oldServerSnap.isFullyInitialized || changePath.isEmpty)
                                                   isFiltered:serverFilter.filtersNodes];
    id<FCompleteChildSource> source = [[FWriteTreeCompleteChildSource alloc] initWithWrites:writesCache
                                                                                  viewCache:newViewCache
                                                                                serverCache:optCompleteCache];
    return [self generateEventCacheAfterServerEvent:newViewCache
                                               path:changePath
                                        writesCache:writesCache
                                             source:source
                                        accumulator:accumulator];
}

- (FViewCache *) applyUserOverwriteTo:(FViewCache *)oldViewCache
                           changePath:(FPath *)changePath
                          changedSnap:(id<FNode>)changedSnap
                          writesCache:(FWriteTreeRef *)writesCache
                        completeCache:(id<FNode>)optCompleteCache
                          accumulator:(FChildChangeAccumulator *)accumulator {
    FCacheNode *oldEventSnap = oldViewCache.cachedEventSnap;
    FViewCache *newViewCache;
    id<FCompleteChildSource> source = [[FWriteTreeCompleteChildSource alloc] initWithWrites:writesCache
                                                                                  viewCache:oldViewCache
                                                                                serverCache:optCompleteCache];
    if (changePath.isEmpty) {
        FIndexedNode *newIndexed = [FIndexedNode indexedNodeWithNode:changedSnap index:self.filter.index];
        FIndexedNode *newEventCache = [self.filter updateFullNode:oldEventSnap.indexedNode
                                                      withNewNode:newIndexed
                                                      accumulator:accumulator];
        newViewCache = [oldViewCache updateEventSnap:newEventCache isComplete:YES isFiltered:self.filter.filtersNodes];
    } else {
        NSString *childKey = [changePath getFront];
        if ([childKey isEqualToString:@".priority"]) {
            FIndexedNode *newEventCache = [self.filter updatePriority:changedSnap
                                                              forNode:oldViewCache.cachedEventSnap.indexedNode];
            newViewCache = [oldViewCache updateEventSnap:newEventCache
                                              isComplete:oldEventSnap.isFullyInitialized
                                              isFiltered:oldEventSnap.isFiltered];
        } else {
            FPath *childChangePath = [changePath popFront];
            id<FNode> oldChild = [oldEventSnap.node getImmediateChild:childKey];
            id<FNode> newChild;
            if (childChangePath.isEmpty) {
                // Child overwrite, we can replace the child
                newChild = changedSnap;
            } else {
                id<FNode> childNode = [source completeChild:childKey];
                if (childNode != nil) {
                    if ([[childChangePath getBack] isEqualToString:@".priority"] && [childNode getChild:[childChangePath parent]].isEmpty) {
                        // This is a priority update on an empty node. If this node exists on the server, the server
                        // will send down the priority in the update, so ignore for now
                        newChild = childNode;
                    } else {
                        newChild = [childNode updateChild:childChangePath withNewChild:changedSnap];
                    }
                } else {
                    newChild = [FEmptyNode emptyNode];
                }
            }
            if (![oldChild isEqual:newChild]) {
                FIndexedNode *newEventSnap = [self.filter updateChildIn:oldEventSnap.indexedNode
                                                            forChildKey:childKey
                                                               newChild:newChild
                                                           affectedPath:childChangePath
                                                             fromSource:source
                                                            accumulator:accumulator];
                newViewCache = [oldViewCache updateEventSnap:newEventSnap isComplete:oldEventSnap.isFullyInitialized isFiltered:self.filter.filtersNodes];
            } else {
                newViewCache = oldViewCache;
            }
        }
    }
    return newViewCache;
}

+ (BOOL) cache:(FViewCache *)viewCache hasChild:(NSString *)childKey {
    return [viewCache.cachedEventSnap isCompleteForChild:childKey];
}

/**
* @param changedChildren NSDictionary of child name (NSString*) to child value (id<FNode>)
*/
- (FViewCache *) applyUserMergeTo:(FViewCache *)viewCache
                             path:(FPath *)path
                  changedChildren:(FCompoundWrite *)changedChildren
                      writesCache:(FWriteTreeRef *)writesCache
                    completeCache:(id<FNode>)serverCache
                      accumulator:(FChildChangeAccumulator *)accumulator {
    // HACK: In the case of a limit query, there may be some changes that bump things out of the
    // window leaving room for new items.  It's important we process these changes first, so we
    // iterate the changes twice, first processing any that affect items currently in view.
    // TODO: I consider an item "in view" if cacheHasChild is true, which checks both the server
    // and event snap.  I'm not sure if this will result in edge cases when a child is in one but
    // not the other.
    __block FViewCache *curViewCache = viewCache;

    [changedChildren enumerateWrites:^(FPath *relativePath, id<FNode> childNode, BOOL *stop) {
        FPath *writePath = [path child:relativePath];
        if ([FViewProcessor cache:viewCache hasChild:[writePath getFront]]) {
            curViewCache = [self applyUserOverwriteTo:curViewCache
                                           changePath:writePath
                                          changedSnap:childNode
                                          writesCache:writesCache
                                        completeCache:serverCache
                                          accumulator:accumulator];
        }
    }];

    [changedChildren enumerateWrites:^(FPath *relativePath, id<FNode> childNode, BOOL *stop) {
        FPath *writePath = [path child:relativePath];
        if (![FViewProcessor cache:viewCache hasChild:[writePath getFront]]) {
            curViewCache = [self applyUserOverwriteTo:curViewCache
                                             changePath:writePath
                                            changedSnap:childNode
                                            writesCache:writesCache
                                          completeCache:serverCache
                                            accumulator:accumulator];
        }
    }];

    return curViewCache;
}

- (FViewCache *) applyServerMergeTo:(FViewCache *)viewCache
                               path:(FPath *)path
                    changedChildren:(FCompoundWrite *)changedChildren
                        writesCache:(FWriteTreeRef *)writesCache
                      completeCache:(id<FNode>)serverCache
                   filterServerNode:(BOOL)filterServerNode
                        accumulator:(FChildChangeAccumulator *)accumulator {
    // If we don't have a cache yet, this merge was intended for a previously listen in the same location. Ignore it and
    // wait for the complete data update coming soon.
    if (viewCache.cachedServerSnap.node.isEmpty && !viewCache.cachedServerSnap.isFullyInitialized) {
        return viewCache;
    }

    // HACK: In the case of a limit query, there may be some changes that bump things out of the
    // window leaving room for new items.  It's important we process these changes first, so we
    // iterate the changes twice, first processing any that affect items currently in view.
    // TODO: I consider an item "in view" if cacheHasChild is true, which checks both the server
    // and event snap.  I'm not sure if this will result in edge cases when a child is in one but
    // not the other.
    __block FViewCache *curViewCache = viewCache;
    FCompoundWrite *actualMerge;
    if (path.isEmpty) {
        actualMerge = changedChildren;
    } else {
        actualMerge = [[FCompoundWrite emptyWrite] addCompoundWrite:changedChildren atPath:path];
    }
    id<FNode> serverNode = viewCache.cachedServerSnap.node;

    NSDictionary *childCompoundWrites = actualMerge.childCompoundWrites;
    [childCompoundWrites enumerateKeysAndObjectsUsingBlock:^(NSString *childKey, FCompoundWrite *childMerge, BOOL *stop) {
        if ([serverNode hasChild:childKey]) {
            id<FNode> serverChild = [viewCache.cachedServerSnap.node getImmediateChild:childKey];
            id<FNode> newChild = [childMerge applyToNode:serverChild];
            curViewCache = [self applyServerOverwriteTo:curViewCache
                                             changePath:[[FPath alloc] initWith:childKey]
                                                   snap:newChild
                                            writesCache:writesCache
                                          completeCache:serverCache
                                       filterServerNode:filterServerNode
                                            accumulator:accumulator];
        }
    }];

    [childCompoundWrites enumerateKeysAndObjectsUsingBlock:^(NSString *childKey, FCompoundWrite *childMerge, BOOL *stop) {
        bool isUnknownDeepMerge = ![viewCache.cachedServerSnap isCompleteForChild:childKey] && childMerge.rootWrite == nil;
        if (![serverNode hasChild:childKey] && !isUnknownDeepMerge) {
            id<FNode> serverChild = [viewCache.cachedServerSnap.node getImmediateChild:childKey];
            id<FNode> newChild = [childMerge applyToNode:serverChild];
            curViewCache = [self applyServerOverwriteTo:curViewCache
                                             changePath:[[FPath alloc] initWith:childKey]
                                                   snap:newChild
                                            writesCache:writesCache
                                          completeCache:serverCache
                                       filterServerNode:filterServerNode
                                            accumulator:accumulator];
        }
    }];

    return curViewCache;
}

- (FViewCache *) ackUserWriteOn:(FViewCache *)viewCache
                        ackPath:(FPath *)ackPath
                   affectedTree:(FImmutableTree *)affectedTree
                    writesCache:(FWriteTreeRef *)writesCache
                  completeCache:(id <FNode>)optCompleteCache
                    accumulator:(FChildChangeAccumulator *)accumulator {

    if ([writesCache shadowingWriteAtPath:ackPath] != nil) {
        return viewCache;
    }

    // Only filter server node if it is currently filtered
    BOOL filterServerNode = viewCache.cachedServerSnap.isFiltered;

    // Essentially we'll just get our existing server cache for the affected paths and re-apply it as a server update
    // now that it won't be shadowed.
    FCacheNode *serverCache = viewCache.cachedServerSnap;
    if (affectedTree.value != nil) {
        // This is an overwrite.
        if ((ackPath.isEmpty && serverCache.isFullyInitialized) || [serverCache isCompleteForPath:ackPath]) {
            return [self applyServerOverwriteTo:viewCache changePath:ackPath snap:[serverCache.node getChild:ackPath]
                                    writesCache:writesCache completeCache:optCompleteCache
                               filterServerNode:filterServerNode accumulator:accumulator];
        } else if (ackPath.isEmpty) {
            // This is a goofy edge case where we are acking data at this location but don't have full data.  We
            // should just re-apply whatever we have in our cache as a merge.
            FCompoundWrite *changedChildren = [FCompoundWrite emptyWrite];
            for(FNamedNode *child in serverCache.node.childEnumerator) {
                changedChildren = [changedChildren addWrite:child.node atKey:child.name];
            }
            return [self applyServerMergeTo:viewCache path:ackPath changedChildren:changedChildren
                                writesCache:writesCache completeCache:optCompleteCache
                           filterServerNode:filterServerNode accumulator:accumulator];
        } else {
            return viewCache;
        }
    } else {
        // This is a merge.
        __block FCompoundWrite *changedChildren = [FCompoundWrite emptyWrite];
        [affectedTree forEach:^(FPath *mergePath, id value) {
            FPath *serverCachePath = [ackPath child:mergePath];
            if ([serverCache isCompleteForPath:serverCachePath]) {
                changedChildren = [changedChildren addWrite:[serverCache.node getChild:serverCachePath] atPath:mergePath];
            }
        }];
        return [self applyServerMergeTo:viewCache path:ackPath changedChildren:changedChildren
                            writesCache:writesCache completeCache:optCompleteCache
                       filterServerNode:filterServerNode accumulator:accumulator];
    }
}

- (FViewCache *) revertUserWriteOn:(FViewCache *)viewCache
                              path:(FPath *)path
                       writesCache:(FWriteTreeRef *)writesCache
                     completeCache:(id<FNode>)optCompleteCache
                       accumulator:(FChildChangeAccumulator *)accumulator {
    if ([writesCache shadowingWriteAtPath:path] != nil) {
        return viewCache;
    } else {
        id<FCompleteChildSource> source = [[FWriteTreeCompleteChildSource alloc] initWithWrites:writesCache
                                                                                      viewCache:viewCache
                                                                                    serverCache:optCompleteCache];
        FIndexedNode *oldEventCache = viewCache.cachedEventSnap.indexedNode;
        FIndexedNode *newEventCache;
        if (path.isEmpty || [[path getFront] isEqualToString:@".priority"]) {
            id<FNode> newNode;
            if (viewCache.cachedServerSnap.isFullyInitialized) {
                newNode = [writesCache calculateCompleteEventCacheWithCompleteServerCache:viewCache.completeServerSnap];
            } else {
                newNode = [writesCache calculateCompleteEventChildrenWithCompleteServerChildren:viewCache.cachedServerSnap.node];
            }
            FIndexedNode *indexedNode = [FIndexedNode indexedNodeWithNode:newNode index:self.filter.index];
            newEventCache = [self.filter updateFullNode:oldEventCache withNewNode:indexedNode accumulator:accumulator];
        } else {
            NSString *childKey = [path getFront];
            id<FNode> newChild = [writesCache calculateCompleteChild:childKey cache:viewCache.cachedServerSnap];
            if (newChild == nil && [viewCache.cachedServerSnap isCompleteForChild:childKey]) {
                newChild = [oldEventCache.node getImmediateChild:childKey];
            }
            if (newChild != nil) {
                newEventCache = [self.filter updateChildIn:oldEventCache
                                               forChildKey:childKey
                                                  newChild:newChild
                                              affectedPath:[path popFront]
                                                fromSource:source
                                               accumulator:accumulator];
            } else if (newChild == nil && [viewCache.cachedEventSnap.node hasChild:childKey]) {
                // No complete child available, delete the existing one, if any
                newEventCache = [self.filter updateChildIn:oldEventCache
                                               forChildKey:childKey
                                                  newChild:[FEmptyNode emptyNode]
                                              affectedPath:[path popFront]
                                                fromSource:source
                                               accumulator:accumulator];
            } else {
                newEventCache = oldEventCache;
            }
            if (newEventCache.node.isEmpty && viewCache.cachedServerSnap.isFullyInitialized) {
                // We might have reverted all child writes. Maybe the old event was a leaf node.
                id<FNode> complete = [writesCache calculateCompleteEventCacheWithCompleteServerCache:viewCache.completeServerSnap];
                if (complete.isLeafNode) {
                    FIndexedNode *indexed = [FIndexedNode indexedNodeWithNode:complete];
                    newEventCache = [self.filter updateFullNode:newEventCache
                                                    withNewNode:indexed
                                                    accumulator:accumulator];
                }
            }
        }
        BOOL complete = viewCache.cachedServerSnap.isFullyInitialized || [writesCache shadowingWriteAtPath:[FPath empty]] != nil;
        return [viewCache updateEventSnap:newEventCache isComplete:complete isFiltered:self.filter.filtersNodes];
    }
}

- (FViewCache *) listenCompleteOldCache:(FViewCache *)viewCache
                                   path:(FPath *)path
                            writesCache:(FWriteTreeRef *)writesCache
                            serverCache:(id<FNode>)servercache
                            accumulator:(FChildChangeAccumulator *)accumulator {
    FCacheNode *oldServerNode = viewCache.cachedServerSnap;
    FViewCache *newViewCache = [viewCache updateServerSnap:oldServerNode.indexedNode
                                                isComplete:(oldServerNode.isFullyInitialized || path.isEmpty)
                                                isFiltered:oldServerNode.isFiltered];
    return [self generateEventCacheAfterServerEvent:newViewCache path:path writesCache:writesCache source:[FNoCompleteChildSource instance] accumulator:accumulator];
}

@end
