#import "Downloader.h"

@implementation RNFSDownloadParams

@end

@interface RNFSDownloader()

@property (copy) RNFSDownloadParams* params;

@property (retain) NSURLSession* session;
@property (retain) NSURLSessionDownloadTask* task;
@property (retain) NSNumber* statusCode;
@property (assign) NSTimeInterval lastProgressEmitTimestamp;
@property (retain) NSNumber* lastProgressValue;
@property (retain) NSNumber* contentLength;
@property (retain) NSNumber* bytesWritten;
@property (retain) NSData* resumeData;

@property (retain) NSFileHandle* fileHandle;

@end

@implementation RNFSDownloader

- (NSString *)downloadFile:(RNFSDownloadParams*)params
{
    NSString *uuid = nil;

    _params = params;

  _lastProgressEmitTimestamp = 0;
  _bytesWritten = 0;

  NSURL* url = [NSURL URLWithString:_params.fromUrl];

  if ([[NSFileManager defaultManager] fileExistsAtPath:_params.toFile]) {
    _fileHandle = [NSFileHandle fileHandleForWritingAtPath:_params.toFile];

    if (!_fileHandle) {
      NSError* error = [NSError errorWithDomain:@"Downloader" code:NSURLErrorFileDoesNotExist
                                userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat: @"Failed to write target file at path: %@", _params.toFile]}];

      _params.errorCallback(error);
      return nil;
    } else {
      [_fileHandle closeFile];
    }
  }

  NSURLSessionConfiguration *config;
  if (_params.background) {
    uuid = [[NSUUID UUID] UUIDString];
    config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:uuid];
    config.discretionary = _params.discretionary;
  } else {
    config = [NSURLSessionConfiguration defaultSessionConfiguration];
  }

  if (!_params.cacheable) {
    config.URLCache = nil;
  }

  config.HTTPAdditionalHeaders = _params.headers;
  config.timeoutIntervalForRequest = [_params.readTimeout intValue] / 1000.0;
  config.timeoutIntervalForResource = [_params.backgroundTimeout intValue] / 1000.0;

  _session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
  _task = [_session downloadTaskWithURL:url];
  [_task resume];

    return uuid;
}

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
  NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)downloadTask.response;
  if (_params.beginCallback && !_statusCode) {
    _statusCode = [NSNumber numberWithLong:httpResponse.statusCode];
    _contentLength = [NSNumber numberWithLong:httpResponse.expectedContentLength];
    return _params.beginCallback(_statusCode, _contentLength, httpResponse.allHeaderFields);
  }

  if (_params.progressCallback && [_statusCode isEqualToNumber:[NSNumber numberWithInt:200]]) {
    _bytesWritten = @(totalBytesWritten);

    if(_params.progressInterval.integerValue > 0){
      NSTimeInterval timestamp = [[NSDate date] timeIntervalSince1970];
      if(timestamp - _lastProgressEmitTimestamp > _params.progressInterval.integerValue / 1000.0){
        _lastProgressEmitTimestamp = timestamp;
        return _params.progressCallback(_contentLength, _bytesWritten);
      }
    }else if (_params.progressDivider.integerValue <= 0) {
      return _params.progressCallback(_contentLength, _bytesWritten);
    } else {
      double doubleBytesWritten = (double)[_bytesWritten longValue];
      double doubleContentLength = (double)[_contentLength longValue];
      double doublePercents = doubleBytesWritten / doubleContentLength * 100;
      NSNumber* progress = [NSNumber numberWithUnsignedInt: floor(doublePercents)];
      if ([progress unsignedIntValue] % [_params.progressDivider integerValue] == 0) {
        if (([progress unsignedIntValue] != [_lastProgressValue unsignedIntValue]) || ([_bytesWritten unsignedIntegerValue] == [_contentLength longValue])) {
            NSLog(@"---Progress callback EMIT--- %u", [progress unsignedIntValue]);
          _lastProgressValue = [NSNumber numberWithUnsignedInt:[progress unsignedIntValue]];
          return _params.progressCallback(_contentLength, _bytesWritten);
        }
      }
    }
  }
}

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
  NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)downloadTask.response;
  if (!_statusCode) {
    _statusCode = [NSNumber numberWithLong:httpResponse.statusCode];
  }
  NSURL *destURL = [NSURL fileURLWithPath:_params.toFile];
  NSFileManager *fm = [NSFileManager defaultManager];
  NSError *error = nil;
  if([_statusCode integerValue] >= 200 && [_statusCode integerValue] < 300) {
    [fm removeItemAtURL:destURL error:nil];       // Remove file at destination path, if it exists
    [fm moveItemAtURL:location toURL:destURL error:&error];
    // There are no guarantees about how often URLSession:downloadTask:didWriteData: will fire,
    // so we read an authoritative number of bytes written here.
    _bytesWritten = @([fm attributesOfItemAtPath:_params.toFile error:nil].fileSize);
  }
  if (error) {
    NSLog(@"RNFS download: unable to move tempfile to destination. %@, %@", error, error.userInfo);
  }

  // When numerous downloads are called the sessions are not always invalidated and cleared by iOS14. 
  // This leads to error 28 – no space left on device so we manually flush and invalidate to free up space
  if(session != nil){
    [session flushWithCompletionHandler:^{
      [session finishTasksAndInvalidate];
    }];
  }

  return _params.completeCallback(_statusCode, _bytesWritten);
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
  if (error) {
    NSLog(@"RNFS download: didCompleteWithError %@, %@", error, error.userInfo);
    if (error.code != NSURLErrorCancelled) {
      _resumeData = error.userInfo[NSURLSessionDownloadTaskResumeData];
      if (_resumeData != nil) {
        if (_params.resumableCallback) {
            _params.resumableCallback();
        }
      } else {
          _params.errorCallback(error);
      }
    }
  }
}

- (void)stopDownload
{
  if (_task.state == NSURLSessionTaskStateRunning) {
    [_task cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
        if (resumeData != nil) {
            self.resumeData = resumeData;
            if (self->_params.resumableCallback) {
                self->_params.resumableCallback();
            }
        } else {
            NSError *error = [NSError errorWithDomain:@"RNFS"
                                                 code:0 //used to pass an NSString @"Aborted" here, but it needs an NSInteger
                                             userInfo:@{
                                                        NSLocalizedDescriptionKey: @"Download has been aborted"
                                                        }];

            self->_params.errorCallback(error);
        }
    }];

  }
}

- (void)resumeDownload
{
    if (_resumeData != nil) {
        _task = [_session downloadTaskWithResumeData:_resumeData];
        [_task resume];
        _resumeData = nil;
    }
}

- (BOOL)isResumable
{
    return _resumeData != nil;
}

@end
