/*
 * 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 "FQueryParams.h"
#import "FValidation.h"
#import "FConstants.h"
#import "FIndex.h"
#import "FPriorityIndex.h"
#import "FUtilities.h"
#import "FNodeFilter.h"
#import "FIndexedFilter.h"
#import "FLimitedFilter.h"
#import "FRangedFilter.h"
#import "FNode.h"
#import "FSnapshotUtilities.h"

@interface FQueryParams ()

@property (nonatomic, readwrite) BOOL limitSet;
@property (nonatomic, readwrite) NSInteger limit;

@property (nonatomic, strong, readwrite) NSString *viewFrom;
/**
* indexStartValue is anything you can store as a priority / value.
*/
@property (nonatomic, strong, readwrite) id<FNode> indexStartValue;
@property (nonatomic, strong, readwrite) NSString *indexStartKey;
/**
* indexStartValue is anything you can store as a priority / value.
*/
@property (nonatomic, strong, readwrite) id<FNode> indexEndValue;
@property (nonatomic, strong, readwrite) NSString *indexEndKey;

@property (nonatomic, strong, readwrite) id<FIndex> index;

@end

@implementation FQueryParams

+ (FQueryParams *) defaultInstance {
    static FQueryParams *defaultParams = nil;
    static dispatch_once_t defaultParamsToken;
    dispatch_once(&defaultParamsToken, ^{
        defaultParams = [[FQueryParams alloc] init];
    });
    return defaultParams;
}


- (id)init {
    self = [super init];
    if (self) {
        self->_limitSet = NO;
        self->_limit = 0;

        self->_viewFrom = nil;
        self->_indexStartValue = nil;
        self->_indexStartKey = nil;
        self->_indexEndValue = nil;
        self->_indexEndKey = nil;

        self->_index = [FPriorityIndex priorityIndex];
    }
    return self;
}

/**
* Only valid if hasStart is true
*/
- (id) indexStartValue {
    NSAssert([self hasStart], @"Only valid if start has been set");
    return _indexStartValue;
}

/**
* Only valid if hasStart is true.
* @return The starting key name for the range defined by these query parameters
*/
- (NSString *) indexStartKey {
    NSAssert([self hasStart], @"Only valid if start has been set");
    if (_indexStartKey == nil) {
        return [FUtilities minName];
    } else {
        return _indexStartKey;
    }
}

/**
* Only valid if hasEnd is true.
*/
- (id) indexEndValue {
    NSAssert([self hasEnd], @"Only valid if end has been set");
    return _indexEndValue;
}

/**
* Only valid if hasEnd is true.
* @return The end key name for the range defined by these query parameters
*/
- (NSString *) indexEndKey {
    NSAssert([self hasEnd], @"Only valid if end has been set");
    if (_indexEndKey == nil) {
        return [FUtilities maxName];
    } else {
        return _indexEndKey;
    }
}

/**
* @return true if a limit has been set and has been explicitly anchored
*/
- (BOOL) hasAnchoredLimit {
    return self.limitSet && self.viewFrom != nil;
}

/**
* Only valid to call if limitSet returns true
*/
- (NSInteger) limit {
    NSAssert(self.limitSet, @"Only valid if limit has been set");
    return _limit;
}

- (BOOL)hasStart {
    return self->_indexStartValue != nil;
}

- (BOOL)hasEnd {
    return self->_indexEndValue != nil;
}

- (id) copyWithZone:(NSZone *)zone {
    // Immutable
    return self;
}

- (id) mutableCopy {
    FQueryParams* other = [[[self class] alloc] init];
    // Maybe need to do extra copying here
    other->_limitSet = _limitSet;
    other->_limit = _limit;
    other->_indexStartValue = _indexStartValue;
    other->_indexStartKey = _indexStartKey;
    other->_indexEndValue = _indexEndValue;
    other->_indexEndKey = _indexEndKey;
    other->_viewFrom = _viewFrom;
    other->_index = _index;
    return other;
}

- (FQueryParams *) limitTo:(NSInteger)newLimit {
    FQueryParams *newParams = [self mutableCopy];
    newParams->_limitSet = YES;
    newParams->_limit = newLimit;
    newParams->_viewFrom = nil;
    return newParams;
}

- (FQueryParams *) limitToFirst:(NSInteger)newLimit {
    FQueryParams *newParams = [self mutableCopy];
    newParams->_limitSet = YES;
    newParams->_limit = newLimit;
    newParams->_viewFrom = kFQPViewFromLeft;
    return newParams;
}

- (FQueryParams *) limitToLast:(NSInteger)newLimit {
    FQueryParams *newParams = [self mutableCopy];
    newParams->_limitSet = YES;
    newParams->_limit = newLimit;
    newParams->_viewFrom = kFQPViewFromRight;
    return newParams;
}

- (FQueryParams *) startAt:(id<FNode>)indexValue childKey:(NSString *)key {
    NSAssert([indexValue isLeafNode] || [indexValue isEmpty], nil);
    FQueryParams *newParams = [self mutableCopy];
    newParams->_indexStartValue = indexValue;
    newParams->_indexStartKey = key;
    return newParams;
}

- (FQueryParams *) startAt:(id<FNode>)indexValue {
    return [self startAt:indexValue childKey:nil];
}

- (FQueryParams *) endAt:(id<FNode>)indexValue childKey:(NSString *)key {
    NSAssert([indexValue isLeafNode] || [indexValue isEmpty], nil);
    FQueryParams *newParams = [self mutableCopy];
    newParams->_indexEndValue = indexValue;
    newParams->_indexEndKey = key;
    return newParams;
}

- (FQueryParams *) endAt:(id<FNode>)indexValue {
    return [self endAt:indexValue childKey:nil];
}

- (FQueryParams *) orderBy:(id)newIndex {
    FQueryParams *newParams = [self mutableCopy];
    newParams->_index = newIndex;
    return newParams;
}

- (NSDictionary *) wireProtocolParams {
    NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];
    if ([self hasStart]) {
        [dict setObject:[self.indexStartValue valForExport:YES] forKey:kFQPIndexStartValue];

        // Don't use property as it will be [MIN-NAME]
        if (self->_indexStartKey != nil) {
            [dict setObject:self->_indexStartKey forKey:kFQPIndexStartName];
        }
    }

    if ([self hasEnd]) {
        [dict setObject:[self.indexEndValue valForExport:YES] forKey:kFQPIndexEndValue];

        // Don't use property as it will be [MAX-NAME]
        if (self->_indexEndKey != nil) {
            [dict setObject:self->_indexEndKey forKey:kFQPIndexEndName];
        }
    }

    if (self.limitSet) {
        [dict setObject:[NSNumber numberWithInteger:self.limit] forKey:kFQPLimit];
        NSString *vf = self.viewFrom;
        if (vf == nil) {
            // limit() rather than limitToFirst or limitToLast was called.
            // This means that only one of startSet or endSet is true. Use them
            // to calculate which side of the view to anchor to. If neither is set,
            // Anchor to end
            if ([self hasStart]) {
                vf = kFQPViewFromLeft;
            } else {
                vf = kFQPViewFromRight;
            }
        }
        [dict setObject:vf forKey:kFQPViewFrom];
    }

    // For now, priority index is the default, so we only specify if it's some other index.
    if (![self.index isEqual:[FPriorityIndex priorityIndex]]) {
        [dict setObject:[self.index queryDefinition] forKey:kFQPIndex];
    }

    return dict;
}

+ (FQueryParams *)fromQueryObject:(NSDictionary *)dict {
    if (dict.count == 0) {
        return [FQueryParams defaultInstance];
    }

    FQueryParams *params = [[FQueryParams alloc] init];
    if (dict[kFQPLimit] != nil) {
        params->_limitSet = YES;
        params->_limit = [dict[kFQPLimit] integerValue];
    }

    if (dict[kFQPIndexStartValue] != nil) {
        params->_indexStartValue = [FSnapshotUtilities nodeFrom:dict[kFQPIndexStartValue]];
        if (dict[kFQPIndexStartName] != nil) {
            params->_indexStartKey = dict[kFQPIndexStartName];
        }
    }

    if (dict[kFQPIndexEndValue] != nil) {
        params->_indexEndValue = [FSnapshotUtilities nodeFrom:dict[kFQPIndexEndValue]];
        if (dict[kFQPIndexEndName] != nil) {
            params->_indexEndKey = dict[kFQPIndexEndName];
        }
    }

    if (dict[kFQPViewFrom] != nil) {
        NSString *viewFrom = dict[kFQPViewFrom];
        if (![viewFrom isEqualToString:kFQPViewFromLeft] && ![viewFrom isEqualToString:kFQPViewFromRight]) {
            [NSException raise:NSInvalidArgumentException format:@"Unknown view from paramter: %@", viewFrom];
        }
        params->_viewFrom = viewFrom;
    }

    NSString *index = dict[kFQPIndex];
    if (index != nil) {
        params->_index = [FIndex indexFromQueryDefinition:index];
    }

    return params;
}

- (BOOL) isViewFromLeft {
    if (self.viewFrom != nil) {
        // Not null, we can just check
        return [self.viewFrom isEqualToString:kFQPViewFromLeft];
    } else {
        // If start is set, it's view from left. Otherwise not.
        return self.hasStart;
    }
}

- (id<FNodeFilter>) nodeFilter {
    if (self.loadsAllData) {
        return [[FIndexedFilter alloc] initWithIndex:self.index];
    } else if (self.limitSet) {
        return [[FLimitedFilter alloc] initWithQueryParams:self];
    } else {
        return [[FRangedFilter alloc] initWithQueryParams:self];
    }
}


- (BOOL) isValid {
    return !(self.hasStart && self.hasEnd && self.limitSet && !self.hasAnchoredLimit);
}

- (BOOL) loadsAllData {
    return !(self.hasStart || self.hasEnd || self.limitSet);
}

- (BOOL) isDefault {
    return [self loadsAllData] && [self.index isEqual:[FPriorityIndex priorityIndex]];
}

- (NSString *) description {
    return [[self wireProtocolParams] description];
}

- (BOOL) isEqual:(id)obj {
    if (self == obj) {
        return YES;
    }
    if (![obj isKindOfClass:[self class]]) {
        return NO;
    }
    FQueryParams *other = (FQueryParams *)obj;
    if (self->_limitSet != other->_limitSet) return NO;
    if (self->_limit != other->_limit) return NO;
    if ((self->_index != other->_index) && ![self->_index isEqual:other->_index]) return NO;
    if ((self->_indexStartKey != other->_indexStartKey) && ![self->_indexStartKey isEqualToString:other->_indexStartKey]) return NO;
    if ((self->_indexStartValue != other->_indexStartValue) && ![self->_indexStartValue isEqual:other->_indexStartValue]) return NO;
    if ((self->_indexEndKey != other->_indexEndKey) && ![self->_indexEndKey isEqualToString:other->_indexEndKey]) return NO;
    if ((self->_indexEndValue != other->_indexEndValue) && ![self->_indexEndValue isEqual:other->_indexEndValue]) return NO;
    if ([self isViewFromLeft] != [other isViewFromLeft]) return NO;

    return YES;
}

- (NSUInteger) hash {
    NSUInteger result = _limitSet ? _limit : 0;
    result = 31 * result + ([self isViewFromLeft] ? 1231 : 1237);
    result = 31 * result + [_indexStartKey hash];
    result = 31 * result + [_indexStartValue hash];
    result = 31 * result + [_indexEndKey hash];
    result = 31 * result + [_indexEndValue hash];
    result = 31 * result + [_index hash];
    return result;
}

@end
