// Copyright 2021 Google LLC
//
// 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 "Crashlytics/Crashlytics/Public/FirebaseCrashlytics/FIRCrashlyticsReport.h"

#import "Crashlytics/Crashlytics/Components/FIRCLSContext.h"
#import "Crashlytics/Crashlytics/Components/FIRCLSGlobals.h"
#import "Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h"

@interface FIRCrashlyticsReport () {
  NSString *_reportID;
  NSDate *_dateCreated;
  BOOL _hasCrash;

  FIRCLSUserLoggingABStorage _logStorage;
  const char *_activeLogPath;

  uint32_t _internalKVCounter;
  FIRCLSUserLoggingKVStorage _internalKVStorage;

  uint32_t _userKVCounter;
  FIRCLSUserLoggingKVStorage _userKVStorage;
}

@property(nonatomic, strong) FIRCLSInternalReport *internalReport;

@end

@implementation FIRCrashlyticsReport

- (instancetype)initWithInternalReport:(FIRCLSInternalReport *)internalReport {
  self = [super init];
  if (!self) {
    return nil;
  }

  _internalReport = internalReport;
  _reportID = [[internalReport identifier] copy];
  _dateCreated = [[internalReport dateCreated] copy];
  _hasCrash = [internalReport isCrash];

  _logStorage.maxSize = _firclsContext.readonly->logging.logStorage.maxSize;
  _logStorage.maxEntries = _firclsContext.readonly->logging.logStorage.maxEntries;
  _logStorage.restrictBySize = _firclsContext.readonly->logging.logStorage.restrictBySize;
  _logStorage.entryCount = _firclsContext.readonly->logging.logStorage.entryCount;
  _logStorage.aPath = [FIRCrashlyticsReport filesystemPathForContentFile:FIRCLSReportLogAFile
                                                        inInternalReport:internalReport];
  _logStorage.bPath = [FIRCrashlyticsReport filesystemPathForContentFile:FIRCLSReportLogBFile
                                                        inInternalReport:internalReport];

  _activeLogPath = _logStorage.aPath;

  // TODO: correct kv accounting
  // The internal report will have non-zero compacted and incremental keys. The right thing to do
  // is count them, so we can kick off compactions/pruning at the right times. By
  // setting this value to zero, we're allowing more entries to be made than there really
  // should be. Not the end of the world, but we should do better eventually.
  _internalKVCounter = 0;
  _userKVCounter = 0;

  _userKVStorage.maxCount = _firclsContext.readonly->logging.userKVStorage.maxCount;
  _userKVStorage.maxIncrementalCount =
      _firclsContext.readonly->logging.userKVStorage.maxIncrementalCount;
  _userKVStorage.compactedPath =
      [FIRCrashlyticsReport filesystemPathForContentFile:FIRCLSReportUserCompactedKVFile
                                        inInternalReport:internalReport];
  _userKVStorage.incrementalPath =
      [FIRCrashlyticsReport filesystemPathForContentFile:FIRCLSReportUserIncrementalKVFile
                                        inInternalReport:internalReport];

  _internalKVStorage.maxCount = _firclsContext.readonly->logging.internalKVStorage.maxCount;
  _internalKVStorage.maxIncrementalCount =
      _firclsContext.readonly->logging.internalKVStorage.maxIncrementalCount;
  _internalKVStorage.compactedPath =
      [FIRCrashlyticsReport filesystemPathForContentFile:FIRCLSReportInternalCompactedKVFile
                                        inInternalReport:internalReport];
  _internalKVStorage.incrementalPath =
      [FIRCrashlyticsReport filesystemPathForContentFile:FIRCLSReportInternalIncrementalKVFile
                                        inInternalReport:internalReport];

  return self;
}

+ (const char *)filesystemPathForContentFile:(NSString *)contentFile
                            inInternalReport:(FIRCLSInternalReport *)internalReport {
  if (!internalReport) {
    return nil;
  }

  // We need to be defensive because strdup will crash
  // if given a nil.
  NSString *objCString = [internalReport pathForContentFile:contentFile];
  const char *fileSystemString = [objCString fileSystemRepresentation];
  if (!objCString || !fileSystemString) {
    return nil;
  }

  // Paths need to be duplicated because fileSystemRepresentation returns C strings
  // that are freed outside of this context.
  return strdup(fileSystemString);
}

- (BOOL)checkContextForMethod:(NSString *)methodName {
  if (!FIRCLSContextIsInitialized()) {
    FIRCLSErrorLog(@"%@ failed for FIRCrashlyticsReport because Crashlytics context isn't "
                   @"initialized.",
                   methodName);
    return false;
  }
  return true;
}

#pragma mark - API: Getters

- (NSString *)reportID {
  return _reportID;
}

- (NSDate *)dateCreated {
  return _dateCreated;
}

- (BOOL)hasCrash {
  return _hasCrash;
}

#pragma mark - API: Logging

- (void)log:(NSString *)msg {
  if (![self checkContextForMethod:@"log:"]) {
    return;
  }

  FIRCLSLogToStorage(&_logStorage, &_activeLogPath, @"%@", msg);
}

- (void)logWithFormat:(NSString *)format, ... {
  if (![self checkContextForMethod:@"logWithFormat:"]) {
    return;
  }

  va_list args;
  va_start(args, format);
  [self logWithFormat:format arguments:args];
  va_end(args);
}

- (void)logWithFormat:(NSString *)format arguments:(va_list)args {
  if (![self checkContextForMethod:@"logWithFormat:arguments:"]) {
    return;
  }

  [self log:[[NSString alloc] initWithFormat:format arguments:args]];
}

#pragma mark - API: setUserID

- (void)setUserID:(nullable NSString *)userID {
  if (![self checkContextForMethod:@"setUserID:"]) {
    return;
  }

  FIRCLSUserLoggingRecordKeyValue(FIRCLSUserIdentifierKey, userID, &_internalKVStorage,
                                  &_internalKVCounter);
}

#pragma mark - API: setCustomValue

- (void)setCustomValue:(nullable id)value forKey:(NSString *)key {
  if (![self checkContextForMethod:@"setCustomValue:forKey:"]) {
    return;
  }

  FIRCLSUserLoggingRecordKeyValue(key, value, &_userKVStorage, &_userKVCounter);
}

- (void)setCustomKeysAndValues:(NSDictionary *)keysAndValues {
  if (![self checkContextForMethod:@"setCustomKeysAndValues:"]) {
    return;
  }

  FIRCLSUserLoggingRecordKeysAndValues(keysAndValues, &_userKVStorage, &_userKVCounter);
}

@end
