// 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 "FIRStorageReference.h"

#import "FIRStorageConstants_Private.h"
#import "FIRStorageDeleteTask.h"
#import "FIRStorageDownloadTask_Private.h"
#import "FIRStorageGetDownloadURLTask.h"
#import "FIRStorageGetMetadataTask.h"
#import "FIRStorageMetadata_Private.h"
#import "FIRStorageReference_Private.h"
#import "FIRStorageTaskSnapshot.h"
#import "FIRStorageTaskSnapshot_Private.h"
#import "FIRStorageTask_Private.h"
#import "FIRStorageUpdateMetadataTask.h"
#import "FIRStorageUploadTask_Private.h"
#import "FIRStorageUtils.h"
#import "FIRStorage_Private.h"

#import <FirebaseCore/FIRApp.h>
#import <FirebaseCore/FIROptions.h>

#import <GTMSessionFetcher/GTMSessionFetcher.h>
#import "GTMSessionFetcherService.h"

@implementation FIRStorageReference

- (instancetype)init {
  FIRStorage *storage = [FIRStorage storage];
  NSString *storageBucket = storage.app.options.storageBucket;
  FIRStoragePath *path = [[FIRStoragePath alloc] initWithBucket:storageBucket object:nil];
  FIRStorageReference *reference = [self initWithStorage:storage path:path];
  return reference;
}

- (instancetype)initWithStorage:(FIRStorage *)storage path:(FIRStoragePath *)path {
  self = [super init];
  if (self) {
    _storage = storage;
    _path = path;
  }
  return self;
}

#pragma mark - NSObject overrides

- (instancetype)copyWithZone:(NSZone *)zone {
  FIRStorageReference *copiedReference =
      [[[self class] allocWithZone:zone] initWithStorage:_storage path:_path];
  return copiedReference;
}

- (BOOL)isEqual:(id)object {
  if (self == object) {
    return YES;
  }

  if (![object isKindOfClass:[FIRStorageReference class]]) {
    return NO;
  }

  BOOL isObjectEqual = [self isEqualToFIRStorageReference:(FIRStorageReference *)object];
  return isObjectEqual;
}

- (BOOL)isEqualToFIRStorageReference:(FIRStorageReference *)reference {
  BOOL isEqual = [_storage isEqual:reference.storage] && [_path isEqual:reference.path];
  return isEqual;
}

- (NSUInteger)hash {
  NSUInteger hash = [_storage hash] ^ [_path hash];
  return hash;
}

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

- (NSString *)stringValue {
  NSString *value = [NSString stringWithFormat:@"gs://%@/%@", _path.bucket, _path.object ?: @""];
  return value;
}

#pragma mark - Property Getters

- (NSString *)bucket {
  NSString *bucket = _path.bucket;
  return bucket;
}

- (NSString *)fullPath {
  NSString *path = _path.object;
  if (!path) {
    path = @"";
  }
  return path;
}

- (NSString *)name {
  NSString *name = [_path.object lastPathComponent];
  if (!name) {
    name = @"";
  }
  return name;
}

#pragma mark - Path Operations

- (FIRStorageReference *)root {
  FIRStoragePath *rootPath = [_path root];
  FIRStorageReference *rootReference =
      [[FIRStorageReference alloc] initWithStorage:_storage path:rootPath];
  return rootReference;
}

- (nullable FIRStorageReference *)parent {
  FIRStoragePath *parentPath = [_path parent];
  if (!parentPath) {
    return nil;
  }

  FIRStorageReference *parentReference =
      [[FIRStorageReference alloc] initWithStorage:_storage path:parentPath];
  return parentReference;
}

- (FIRStorageReference *)child:(NSString *)path {
  FIRStoragePath *childPath = [_path child:path];
  FIRStorageReference *childReference =
      [[FIRStorageReference alloc] initWithStorage:_storage path:childPath];
  return childReference;
}

#pragma mark - Uploads

- (FIRStorageUploadTask *)putData:(NSData *)uploadData {
  return [self putData:uploadData metadata:nil completion:nil];
}

- (FIRStorageUploadTask *)putData:(NSData *)uploadData
                         metadata:(nullable FIRStorageMetadata *)metadata {
  return [self putData:uploadData metadata:metadata completion:nil];
}

- (FIRStorageUploadTask *)putData:(NSData *)uploadData
                         metadata:(nullable FIRStorageMetadata *)metadata
                       completion:(nullable FIRStorageVoidMetadataError)completion {
  if (!metadata) {
    metadata = [[FIRStorageMetadata alloc] init];
  }

  metadata.path = _path.object;
  metadata.name = [_path.object lastPathComponent];
  FIRStorageUploadTask *task =
      [[FIRStorageUploadTask alloc] initWithReference:self
                                       fetcherService:_storage.fetcherServiceForApp
                                        dispatchQueue:_storage.dispatchQueue
                                                 data:uploadData
                                             metadata:metadata];

  if (completion) {
    dispatch_queue_t callbackQueue = _storage.fetcherServiceForApp.callbackQueue;
    if (!callbackQueue) {
      callbackQueue = dispatch_get_main_queue();
    }

    [task observeStatus:FIRStorageTaskStatusSuccess
                handler:^(FIRStorageTaskSnapshot *_Nonnull snapshot) {
                  dispatch_async(callbackQueue, ^{
                    completion(snapshot.metadata, nil);
                  });
                }];
    [task observeStatus:FIRStorageTaskStatusFailure
                handler:^(FIRStorageTaskSnapshot *_Nonnull snapshot) {
                  dispatch_async(callbackQueue, ^{
                    completion(nil, snapshot.error);
                  });
                }];
  }
  [task enqueue];
  return task;
}

- (FIRStorageUploadTask *)putFile:(NSURL *)fileURL {
  return [self putFile:fileURL metadata:nil completion:nil];
}

- (FIRStorageUploadTask *)putFile:(NSURL *)fileURL
                         metadata:(nullable FIRStorageMetadata *)metadata {
  return [self putFile:fileURL metadata:metadata completion:nil];
}

- (FIRStorageUploadTask *)putFile:(NSURL *)fileURL
                         metadata:(nullable FIRStorageMetadata *)metadata
                       completion:(nullable FIRStorageVoidMetadataError)completion {
  if (!metadata) {
    metadata = [[FIRStorageMetadata alloc] init];
  }

  metadata.path = _path.object;
  metadata.name = [_path.object lastPathComponent];
  FIRStorageUploadTask *task =
      [[FIRStorageUploadTask alloc] initWithReference:self
                                       fetcherService:_storage.fetcherServiceForApp
                                        dispatchQueue:_storage.dispatchQueue
                                                 file:fileURL
                                             metadata:metadata];

  if (completion) {
    dispatch_queue_t callbackQueue = _storage.fetcherServiceForApp.callbackQueue;
    if (!callbackQueue) {
      callbackQueue = dispatch_get_main_queue();
    }

    [task observeStatus:FIRStorageTaskStatusSuccess
                handler:^(FIRStorageTaskSnapshot *_Nonnull snapshot) {
                  dispatch_async(callbackQueue, ^{
                    completion(snapshot.metadata, nil);
                  });
                }];
    [task observeStatus:FIRStorageTaskStatusFailure
                handler:^(FIRStorageTaskSnapshot *_Nonnull snapshot) {
                  dispatch_async(callbackQueue, ^{
                    completion(nil, snapshot.error);
                  });
                }];
  }
  [task enqueue];
  return task;
}

#pragma mark - Downloads

- (FIRStorageDownloadTask *)dataWithMaxSize:(int64_t)size
                                 completion:(FIRStorageVoidDataError)completion {
  FIRStorageDownloadTask *task =
      [[FIRStorageDownloadTask alloc] initWithReference:self
                                         fetcherService:_storage.fetcherServiceForApp
                                          dispatchQueue:_storage.dispatchQueue
                                                   file:nil];

  dispatch_queue_t callbackQueue = _storage.fetcherServiceForApp.callbackQueue;
  if (!callbackQueue) {
    callbackQueue = dispatch_get_main_queue();
  }

  [task observeStatus:FIRStorageTaskStatusSuccess
              handler:^(FIRStorageTaskSnapshot *_Nonnull snapshot) {
                FIRStorageDownloadTask *task = snapshot.task;
                dispatch_async(callbackQueue, ^{
                  completion(task.downloadData, nil);
                });
              }];
  [task observeStatus:FIRStorageTaskStatusFailure
              handler:^(FIRStorageTaskSnapshot *_Nonnull snapshot) {
                dispatch_async(callbackQueue, ^{
                  completion(nil, snapshot.error);
                });
              }];
  [task
      observeStatus:FIRStorageTaskStatusProgress
            handler:^(FIRStorageTaskSnapshot *_Nonnull snapshot) {
              FIRStorageDownloadTask *task = snapshot.task;
              if (task.progress.totalUnitCount > size || task.progress.completedUnitCount > size) {
                NSDictionary *infoDictionary =
                    @{@"totalSize" : @(task.progress.totalUnitCount),
                      @"maxAllowedSize" : @(size)};
                NSError *error =
                    [FIRStorageErrors errorWithCode:FIRStorageErrorCodeDownloadSizeExceeded
                                     infoDictionary:infoDictionary];
                [task cancelWithError:error];
              }
            }];
  [task enqueue];
  return task;
}

- (FIRStorageDownloadTask *)writeToFile:(NSURL *)fileURL {
  return [self writeToFile:fileURL completion:nil];
}

- (FIRStorageDownloadTask *)writeToFile:(NSURL *)fileURL
                             completion:(FIRStorageVoidURLError)completion {
  FIRStorageDownloadTask *task =
      [[FIRStorageDownloadTask alloc] initWithReference:self
                                         fetcherService:_storage.fetcherServiceForApp
                                          dispatchQueue:_storage.dispatchQueue
                                                   file:fileURL];
  if (completion) {
    dispatch_queue_t callbackQueue = _storage.fetcherServiceForApp.callbackQueue;
    if (!callbackQueue) {
      callbackQueue = dispatch_get_main_queue();
    }

    [task observeStatus:FIRStorageTaskStatusSuccess
                handler:^(FIRStorageTaskSnapshot *_Nonnull snapshot) {
                  dispatch_async(callbackQueue, ^{
                    completion(fileURL, nil);
                  });
                }];
    [task observeStatus:FIRStorageTaskStatusFailure
                handler:^(FIRStorageTaskSnapshot *_Nonnull snapshot) {
                  dispatch_async(callbackQueue, ^{
                    completion(nil, snapshot.error);
                  });
                }];
  }
  [task enqueue];
  return task;
}

- (void)downloadURLWithCompletion:(FIRStorageVoidURLError)completion {
  FIRStorageGetDownloadURLTask *task =
      [[FIRStorageGetDownloadURLTask alloc] initWithReference:self
                                               fetcherService:_storage.fetcherServiceForApp
                                                dispatchQueue:_storage.dispatchQueue
                                                   completion:completion];
  [task enqueue];
}

#pragma mark - Metadata Operations

- (void)metadataWithCompletion:(FIRStorageVoidMetadataError)completion {
  FIRStorageGetMetadataTask *task =
      [[FIRStorageGetMetadataTask alloc] initWithReference:self
                                            fetcherService:_storage.fetcherServiceForApp
                                             dispatchQueue:_storage.dispatchQueue
                                                completion:completion];
  [task enqueue];
}

- (void)updateMetadata:(FIRStorageMetadata *)metadata
            completion:(nullable FIRStorageVoidMetadataError)completion {
  FIRStorageUpdateMetadataTask *task =
      [[FIRStorageUpdateMetadataTask alloc] initWithReference:self
                                               fetcherService:_storage.fetcherServiceForApp
                                                dispatchQueue:_storage.dispatchQueue
                                                     metadata:metadata
                                                   completion:completion];
  [task enqueue];
}

#pragma mark - Delete

- (void)deleteWithCompletion:(nullable FIRStorageVoidError)completion {
  FIRStorageDeleteTask *task =
      [[FIRStorageDeleteTask alloc] initWithReference:self
                                       fetcherService:_storage.fetcherServiceForApp
                                        dispatchQueue:_storage.dispatchQueue
                                           completion:completion];
  [task enqueue];
}

@end
