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

#import "Protos/GtalkCore.pbobjc.h"

#import "FIRMessagingDefines.h"
#import "FIRMessagingRmqManager.h"
#import "FIRMessagingUtilities.h"

static const int kMaxQueuedMessageCount = 10;

@interface FIRMessagingDelayedMessageQueue ()

@property(nonatomic, readonly, weak) id<FIRMessagingRmqScanner> rmqScanner;
@property(nonatomic, readonly, copy) FIRMessagingSendDelayedMessagesHandler sendDelayedMessagesHandler;

@property(nonatomic, readwrite, assign) int persistedMessageCount;
// the scheduled timeout or -1 if not set
@property(nonatomic, readwrite, assign) int64_t scheduledTimeoutMilliseconds;
// The time  of the last scan of the message DB,
// used to avoid retrieving messages more than once.
@property(nonatomic, readwrite, assign) int64_t lastDBScanTimestampSeconds;

@property(nonatomic, readwrite, strong) NSMutableArray *messages;
@property(nonatomic, readwrite, strong) NSTimer *sendTimer;

@end

@implementation FIRMessagingDelayedMessageQueue

- (instancetype)init {
  FIRMessagingInvalidateInitializer();
}

- (instancetype)initWithRmqScanner:(id<FIRMessagingRmqScanner>)rmqScanner
        sendDelayedMessagesHandler:(FIRMessagingSendDelayedMessagesHandler)sendDelayedMessagesHandler {
  _FIRMessagingDevAssert(sendDelayedMessagesHandler, @"Invalid nil callback for delayed messages");
  self = [super init];
  if (self) {
    _rmqScanner = rmqScanner;
    _sendDelayedMessagesHandler = sendDelayedMessagesHandler;
    _messages = [NSMutableArray arrayWithCapacity:10];
    _scheduledTimeoutMilliseconds = -1;
  }
  return self;
}

- (BOOL)queueMessage:(GtalkDataMessageStanza *)message {
  if (self.messages.count >= kMaxQueuedMessageCount) {
    return NO;
  }
  if (message.ttl == 0) {
    // ttl=0 messages aren't persisted, add it to memory
    [self.messages addObject:message];
  } else {
    self.persistedMessageCount++;
  }
  int64_t timeoutMillis = [self calculateTimeoutInMillisWithDelayInSeconds:message.maxDelay];
  if (![self isTimeoutScheduled] || timeoutMillis < self.scheduledTimeoutMilliseconds) {
    [self scheduleTimeoutInMillis:timeoutMillis];
  }
  return YES;
}

- (NSArray *)removeDelayedMessages {
  [self cancelTimeout];
  if ([self messageCount] == 0) {
    return @[];
  }

  NSMutableArray *delayedMessages = [NSMutableArray array];
  // add the ttl=0 messages
  if (self.messages.count) {
    [delayedMessages addObjectsFromArray:delayedMessages];
    [self.messages removeAllObjects];
  }

  // add persistent messages
  if (self.persistedMessageCount > 0) {
    FIRMessaging_WEAKIFY(self);
    [self.rmqScanner scanWithRmqMessageHandler:nil
                            dataMessageHandler:^(int64_t rmqId, GtalkDataMessageStanza *stanza) {
                              FIRMessaging_STRONGIFY(self);
                              if ([stanza hasMaxDelay] &&
                                  [stanza sent] >= self.lastDBScanTimestampSeconds) {
                                [delayedMessages addObject:stanza];
                              }
                            }];
    self.lastDBScanTimestampSeconds = FIRMessagingCurrentTimestampInSeconds();
    self.persistedMessageCount = 0;
  }
  return delayedMessages;
}

- (void)sendMessages {
  if (self.sendDelayedMessagesHandler) {
    self.sendDelayedMessagesHandler([self removeDelayedMessages]);
  }
}

#pragma mark - Private

- (NSInteger)messageCount {
  return self.messages.count + self.persistedMessageCount;
}

- (BOOL)isTimeoutScheduled {
  return self.scheduledTimeoutMilliseconds > 0;
}

- (int64_t)calculateTimeoutInMillisWithDelayInSeconds:(int)delay {
  return FIRMessagingCurrentTimestampInMilliseconds() + delay * 1000.0;
}

- (void)scheduleTimeoutInMillis:(int64_t)time {
  [self cancelTimeout];
  self.scheduledTimeoutMilliseconds = time;
  double delay = (time - FIRMessagingCurrentTimestampInMilliseconds()) / 1000.0;
  [self performSelector:@selector(sendMessages) withObject:self afterDelay:delay];
}

- (void)cancelTimeout {
  if ([self isTimeoutScheduled]) {
    [NSObject cancelPreviousPerformRequestsWithTarget:self
                                             selector:@selector(sendMessages)
                                               object:nil];
    self.scheduledTimeoutMilliseconds = -1;
  }
}

@end
