/** filename: {{&sdknameLower}}/{{&sdkname}}API.m **/
#import "{{&sdkname}}API.h"

@implementation {{&sdkname}}API

+ ({{&sdkname}}API *) getAPI:(NSString *)path withMethod:(NSString *)method {
    NSString *url = [{{&sdkname}} getURL:path];
    return [[{{&sdkname}}API alloc] init:url withMethod:method];
}

-(id)init:(NSString *)url withMethod:(NSString *)method {
    if (self = [super init]) {
        self.url = url;
        self.method = method;
    }
    return self;
}

- (CachePolicy)cachePolicyType {
    if (!_cachePolicyType) {
        return [{{&sdkname}} cachePolicyType];
    }
    return _cachePolicyType;
}
- (void)setCachePolicyType:(CachePolicy)value {
    _cachePolicyType = value;
}

- (NSNumber *)cachePolicyDuration {
    if (_cachePolicyDuration == nil) {
        return [{{&sdkname}} cachePolicyDuration];
    }
    return _cachePolicyDuration;
}

- (void)setCachePolicyDuration:(NSNumber *)value {
    _cachePolicyDuration = value;
}

- (NSString *) getCacheHash:(NSString *)method with:(NSURL *)url with:(NSDictionary *)headers and:(NSDictionary *)defaultHeaders {
    return [NSString stringWithFormat:@"%@:%@\n%@\n%@", method, url, headers, defaultHeaders];
}

- (NSDictionary *) execute {
    return [self execute:[NSDictionary dictionary] with:nil];
}
- (NSDictionary *) execute:(NSDictionary *)options {
    return [self execute:options with:nil];
}
- (NSDictionary *) executeWith:(void (^)(NSError *error, NSDictionary *result, NSHTTPURLResponse *response))callback {
    return [self execute:[NSDictionary dictionary] with:callback];
}
- (NSDictionary *) execute:(NSDictionary *)options with:(void (^)(NSError *error, NSDictionary *result, NSHTTPURLResponse *response))callback {
    NSString *url = self.url;
    NSDictionary *query = [options valueForKey:@"query"];
    NSDictionary *body = [options valueForKey:@"body"];
    NSDictionary *headers = [options valueForKey:@"headers"];
    NSDictionary *path = [options valueForKey:@"path"];
    NSNumber *timeout = [options valueForKey:@"timeout"];
    NSDictionary *defaultHeaders = [{{&sdkname}} getDefaultHeaders];
    if (query == nil && body == nil && headers == nil && path == nil && timeout == nil) {
        if ([self.method isEqualToString:@"GET"]) {
            query = options;
        }
        else {
            body = options;
        }
    }
    if (timeout == nil) {
        timeout = {{&sdkname}}.timeout;
    }

    // How about PATH COMPONENTS?! Oh goodness, the glee bubbles over!
    if (path != nil) {
        for (NSString *pathKey in path) {
            url = [url stringByReplacingOccurrencesOfString:[NSString stringWithFormat:@":%@", pathKey] withString:path[pathKey]];
        }
    }
    
    // Do we have query components? HOW EXCITING!
    NSURLComponents *components = [NSURLComponents componentsWithString:self.url];
    if (query != nil) {
        NSMutableArray *queryItems = [NSMutableArray array];
        for (NSString *queryKey in query) {
            [queryItems addObject:[NSURLQueryItem queryItemWithName:queryKey value:query[queryKey]]];
        }
        components.queryItems = queryItems;
    }
    NSURL *builtURL = components.URL;
    CachePolicy cacheType = [self cachePolicyType];
    NSNumber *cacheDuration = [self cachePolicyDuration];
    NSString *cacheHash = [self getCacheHash:self.method with:builtURL with:headers and:defaultHeaders];
    bool isGET = [self.method isEqualToString:@"GET"];

    if (isGET && (bool)(cacheType & (CachePolicyCacheElseNetwork | CachePolicyCacheThenNetwork | CachePolicyCacheOnly))) {
        NSDictionary *cached = [{{&sdkname}} readCache:cacheHash];
        if (cached != nil) {
            if (callback) {
                callback(nil, cached, nil);
            }
            if (cacheType != CachePolicyCacheThenNetwork) {
                return cached;
            }
        }
        if (cacheType == CachePolicyCacheOnly) {
            if (callback) {
                callback([NSError errorWithDomain:@"error" code:1 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"No results have been cached yet.", @"message", nil]], nil, nil);
            }
            return nil;
        }
    }

    // Create our request.
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:builtURL
                                                           cachePolicy:NSURLRequestUseProtocolCachePolicy
                                                       timeoutInterval:[timeout doubleValue] / 1000];
    
    [request setHTTPMethod:self.method];
    [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
    
    // Do we have a body? Turn it to JSON so we can send it!
    if (body != nil) {
        [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
        NSError *e = nil;
        NSData *data = [NSJSONSerialization dataWithJSONObject:body options:0 error:&e];
        if (e != nil) {
            if (callback) {
                callback(e, nil, nil);
            }
            return nil;
        }
        [request setValue:[NSString stringWithFormat:@"%lu", (unsigned long)[data length]] forHTTPHeaderField:@"Content-Length"];
        [request setHTTPBody:data];
    }

    // What about headers? Let's SET 'EM!
    if (defaultHeaders != nil) {
        for (NSString *headerKey in defaultHeaders) {
            [request setValue:defaultHeaders[headerKey] forHTTPHeaderField:headerKey];
        }
    }
    if (headers != nil) {
        for (NSString *headerKey in headers) {
            [request setValue:headers[headerKey] forHTTPHeaderField:headerKey];
        }
    }

    // Asynchronomagically dispatch the request, and then...
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {

        NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;

        // Did we get back data?
        if (data != nil && [data length] > 0) {
            NSError *parseError = nil;
            NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];
            if (parseError != nil) {
                if (callback) {
                    callback(error, nil, httpResponse);
                }
                return;
            }

            if (httpResponse.statusCode >= 200 && httpResponse.statusCode <= 299) {
                NSString *key = [dictionary valueForKey:@"key"];
                if (key != nil) {
                    dictionary = [dictionary valueForKey:key];
                }
                if (isGET && (bool)(cacheType & (CachePolicyCacheElseNetwork | CachePolicyNetworkElseCache | CachePolicyCacheThenNetwork | CachePolicyCacheThenNetwork))) {
                    [{{&sdkname}} writeCache:cacheHash with:dictionary and:cacheDuration];
                    // TODO: When we do a findAll, cache findByID, too.
                    // TODO: Non-GETs should bust the cache for find, right?
                }
                if (callback) {
                    callback(nil, dictionary, httpResponse);
                }
                return;
            }
            else {
                if (isGET && cacheType == CachePolicyNetworkElseCache) {
                    NSDictionary *cachedResult = [{{&sdkname}} readCache:cacheHash];
                    if (cachedResult != nil) {
                        if (callback) {
                            callback(nil, cachedResult, nil);
                        }
                        return;
                    }
                }
                callback([NSError errorWithDomain:@"error" code:httpResponse.statusCode userInfo:dictionary], nil, httpResponse);
            }
        }

        // An error? Oh tragedy! Let's tell the developer (our best friend) about it.
        else if (error != nil) {
            if (isGET && cacheType == CachePolicyNetworkElseCache) {
                NSDictionary *cachedResult = [{{&sdkname}} readCache:cacheHash];
                if (cachedResult != nil) {
                    if (callback) {
                        callback(nil, cachedResult, nil);
                    }
                    return;
                }
            }
            callback(error, nil, httpResponse);
            return;
        }

        // Nothing? How odd. Oh well.
        else {
            if (callback) {
                callback(nil, nil, httpResponse);
            }
            return;
        }
    }];

    // We don't have a synchronous response ready, so return nil.
    return nil;
}

@end

