/*
 * 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 <TargetConditionals.h>
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_VISION

#import <UIKit/UIKit.h>

#import "FirebaseCore/Extension/FirebaseCoreInternal.h"

#import "FirebaseInAppMessaging/Sources/FIRCore+InAppMessaging.h"
#import "FirebaseInAppMessaging/Sources/Private/Flows/FIRIAMActivityLogger.h"
@implementation FIRIAMActivityRecord

static NSString *const kActiveTypeArchiveKey = @"type";
static NSString *const kIsSuccessArchiveKey = @"is_success";
static NSString *const kTimeStampArchiveKey = @"timestamp";
static NSString *const kDetailArchiveKey = @"detail";

+ (BOOL)supportsSecureCoding {
  return YES;
}

- (id)initWithCoder:(NSCoder *)decoder {
  self = [super init];
  if (self != nil) {
    _activityType = [decoder decodeIntegerForKey:kActiveTypeArchiveKey];
    _timestamp = [decoder decodeObjectOfClass:[NSDate class] forKey:kTimeStampArchiveKey];
    _success = [decoder decodeBoolForKey:kIsSuccessArchiveKey];
    _detail = [decoder decodeObjectOfClass:[NSString class] forKey:kDetailArchiveKey];
  }
  return self;
}

- (void)encodeWithCoder:(NSCoder *)encoder {
  [encoder encodeInteger:self.activityType forKey:kActiveTypeArchiveKey];
  [encoder encodeObject:self.timestamp forKey:kTimeStampArchiveKey];
  [encoder encodeBool:self.success forKey:kIsSuccessArchiveKey];
  [encoder encodeObject:self.detail forKey:kDetailArchiveKey];
}

- (instancetype)initWithActivityType:(FIRIAMActivityType)type
                        isSuccessful:(BOOL)isSuccessful
                          withDetail:(NSString *)detail
                           timestamp:(nullable NSDate *)timestamp {
  if (self = [super init]) {
    _activityType = type;
    _success = isSuccessful;
    _detail = detail;
    _timestamp = timestamp ? timestamp : [[NSDate alloc] init];
  }
  return self;
}

- (NSString *)displayStringForActivityType {
  switch (self.activityType) {
    case FIRIAMActivityTypeFetchMessage:
      return @"Message Fetching";
    case FIRIAMActivityTypeRenderMessage:
      return @"Message Rendering";
    case FIRIAMActivityTypeDismissMessage:
      return @"Message Dismiss";
    case FIRIAMActivityTypeCheckForOnOpenMessage:
      return @"OnOpen Msg Check";
    case FIRIAMActivityTypeCheckForAnalyticsEventMessage:
      return @"Analytic Msg Check";
    case FIRIAMActivityTypeCheckForFetch:
      return @"Fetch Check";
  }
}
@end

@interface FIRIAMActivityLogger ()
@property(nonatomic) BOOL isDirty;

// always insert at the head of this array so that they are always in anti-chronological order
@property(nonatomic, nonnull) NSMutableArray<FIRIAMActivityRecord *> *activityRecords;

// When we see the number of log records goes beyond maxRecordCountBeforeReduce, we would trigger
// a reduction action which would bring the array length to be the size as defined by
// newSizeAfterReduce
@property(nonatomic, readonly) NSInteger maxRecordCountBeforeReduce;
@property(nonatomic, readonly) NSInteger newSizeAfterReduce;

@end

@implementation FIRIAMActivityLogger
- (instancetype)initWithMaxCountBeforeReduce:(NSInteger)maxBeforeReduce
                         withSizeAfterReduce:(NSInteger)sizeAfterReduce
                                 verboseMode:(BOOL)verboseMode
                               loadFromCache:(BOOL)loadFromCache {
  if (self = [super init]) {
    _maxRecordCountBeforeReduce = maxBeforeReduce;
    _newSizeAfterReduce = sizeAfterReduce;
    _activityRecords = [[NSMutableArray alloc] init];
    _verboseMode = verboseMode;
    _isDirty = NO;

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(appWillBecomeInactive:)
                                                 name:UIApplicationWillResignActiveNotification
                                               object:nil];
    if (@available(iOS 13.0, tvOS 13.0, *)) {
      [[NSNotificationCenter defaultCenter] addObserver:self
                                               selector:@selector(appWillBecomeInactive:)
                                                   name:UISceneWillDeactivateNotification
                                                 object:nil];
    }
    if (loadFromCache) {
      @try {
        [self loadFromCachePath:nil];
      } @catch (NSException *exception) {
        FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM310003",
                      @"Non-fatal exception in loading persisted activity log records: %@.",
                      exception);
      }
    }
  }
  return self;
}

- (void)dealloc {
  [[NSNotificationCenter defaultCenter] removeObserver:self];
}

+ (NSString *)determineCacheFilePath {
  static NSString *logCachePath;
  static dispatch_once_t onceToken;

  dispatch_once(&onceToken, ^{
    NSString *cacheDirPath =
        NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
    logCachePath = [NSString stringWithFormat:@"%@/firebase-iam-activity-log-cache", cacheDirPath];
    FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM310001",
                @"Persistent file path for activity log data is %@", logCachePath);
  });
  return logCachePath;
}

- (void)loadFromCachePath:(NSString *)cacheFilePath {
  NSString *filePath = cacheFilePath == nil ? [self.class determineCacheFilePath] : cacheFilePath;
  id fetchedActivityRecords;
  NSData *data = [NSData dataWithContentsOfFile:filePath];
  if (data) {
    fetchedActivityRecords = [NSKeyedUnarchiver
        unarchivedObjectOfClasses:[NSSet setWithObjects:[FIRIAMActivityRecord class],
                                                        [NSMutableArray class], nil]
                         fromData:data
                            error:nil];
  }
  if (fetchedActivityRecords) {
    @synchronized(self) {
      self.activityRecords = (NSMutableArray<FIRIAMActivityRecord *> *)fetchedActivityRecords;
      self.isDirty = NO;
    }
  }
}

- (BOOL)saveIntoCacheWithPath:(NSString *)cacheFilePath {
  NSString *filePath = cacheFilePath == nil ? [self.class determineCacheFilePath] : cacheFilePath;
  @synchronized(self) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
    BOOL result = [NSKeyedArchiver archiveRootObject:self.activityRecords toFile:filePath];
#pragma clang diagnostic pop
    if (result) {
      self.isDirty = NO;
    }
    return result;
  }
}

- (void)appWillBecomeInactive:(NSNotification *)notification {
  FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM310004",
              @"App will become inactive, save"
               " activity logs");

  if (self.isDirty) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul), ^{
      if ([self saveIntoCacheWithPath:nil]) {
        FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM310002",
                    @"Persisting activity log data is was successful");
      } else {
        FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM310005",
                      @"Persisting activity log data has failed");
      }
    });
  }
}

// Helper function to determine if a given activity type should be recorded under
// non verbose type.
+ (BOOL)isMandatoryType:(FIRIAMActivityType)type {
  switch (type) {
    case FIRIAMActivityTypeFetchMessage:
    case FIRIAMActivityTypeRenderMessage:
    case FIRIAMActivityTypeDismissMessage:
      return YES;
    default:
      return NO;
  }
}

- (void)addLogRecord:(FIRIAMActivityRecord *)newRecord {
  if (self.verboseMode || [FIRIAMActivityLogger isMandatoryType:newRecord.activityType]) {
    @synchronized(self) {
      [self.activityRecords insertObject:newRecord atIndex:0];

      if (self.activityRecords.count >= self.maxRecordCountBeforeReduce) {
        NSRange removeRange;
        removeRange.location = self.newSizeAfterReduce;
        removeRange.length = self.maxRecordCountBeforeReduce - self.newSizeAfterReduce;
        [self.activityRecords removeObjectsInRange:removeRange];
      }
      self.isDirty = YES;
    }
  }
}

- (NSArray<FIRIAMActivityRecord *> *)readRecords {
  return [self.activityRecords copy];
}
@end

#endif  // TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_VISION
