/*
 *  Copyright 2015 The WebRTC Project Authors. All rights reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#import "ARDStatsBuilder.h"

#import "WebRTC/RTCLegacyStatsReport.h"

#import "ARDBitrateTracker.h"
#import "ARDUtilities.h"

@implementation ARDStatsBuilder {
  // Connection stats.
  NSString *_connRecvBitrate;
  NSString *_connRtt;
  NSString *_connSendBitrate;
  NSString *_localCandType;
  NSString *_remoteCandType;
  NSString *_transportType;

  // BWE stats.
  NSString *_actualEncBitrate;
  NSString *_availableRecvBw;
  NSString *_availableSendBw;
  NSString *_targetEncBitrate;

  // Video send stats.
  NSString *_videoEncodeMs;
  NSString *_videoInputFps;
  NSString *_videoInputHeight;
  NSString *_videoInputWidth;
  NSString *_videoSendCodec;
  NSString *_videoSendBitrate;
  NSString *_videoSendFps;
  NSString *_videoSendHeight;
  NSString *_videoSendWidth;

  // QP stats.
  int _videoQPSum;
  int _framesEncoded;
  int _oldVideoQPSum;
  int _oldFramesEncoded;

  // Video receive stats.
  NSString *_videoDecodeMs;
  NSString *_videoDecodedFps;
  NSString *_videoOutputFps;
  NSString *_videoRecvBitrate;
  NSString *_videoRecvFps;
  NSString *_videoRecvHeight;
  NSString *_videoRecvWidth;

  // Audio send stats.
  NSString *_audioSendBitrate;
  NSString *_audioSendCodec;

  // Audio receive stats.
  NSString *_audioCurrentDelay;
  NSString *_audioExpandRate;
  NSString *_audioRecvBitrate;
  NSString *_audioRecvCodec;

  // Bitrate trackers.
  ARDBitrateTracker *_audioRecvBitrateTracker;
  ARDBitrateTracker *_audioSendBitrateTracker;
  ARDBitrateTracker *_connRecvBitrateTracker;
  ARDBitrateTracker *_connSendBitrateTracker;
  ARDBitrateTracker *_videoRecvBitrateTracker;
  ARDBitrateTracker *_videoSendBitrateTracker;
}

- (instancetype)init {
  if (self = [super init]) {
    _audioSendBitrateTracker = [[ARDBitrateTracker alloc] init];
    _audioRecvBitrateTracker = [[ARDBitrateTracker alloc] init];
    _connSendBitrateTracker = [[ARDBitrateTracker alloc] init];
    _connRecvBitrateTracker = [[ARDBitrateTracker alloc] init];
    _videoSendBitrateTracker = [[ARDBitrateTracker alloc] init];
    _videoRecvBitrateTracker = [[ARDBitrateTracker alloc] init];
    _videoQPSum = 0;
    _framesEncoded = 0;
  }
  return self;
}

- (NSString *)statsString {
  NSMutableString *result = [NSMutableString string];
  NSString *systemStatsFormat = @"(cpu)%ld%%\n";
  [result appendString:[NSString stringWithFormat:systemStatsFormat,
      (long)ARDGetCpuUsagePercentage()]];

  // Connection stats.
  NSString *connStatsFormat = @"CN %@ms | %@->%@/%@ | (s)%@ | (r)%@\n";
  [result appendString:[NSString stringWithFormat:connStatsFormat,
      _connRtt,
      _localCandType, _remoteCandType, _transportType,
      _connSendBitrate, _connRecvBitrate]];

  // Video send stats.
  NSString *videoSendFormat = @"VS (input) %@x%@@%@fps | (sent) %@x%@@%@fps\n"
                               "VS (enc) %@/%@ | (sent) %@/%@ | %@ms | %@\n"
                               "AvgQP (past %d encoded frames) = %d\n ";
  int avgqp = [self calculateAvgQP];

  [result appendString:[NSString stringWithFormat:videoSendFormat,
      _videoInputWidth, _videoInputHeight, _videoInputFps,
      _videoSendWidth, _videoSendHeight, _videoSendFps,
      _actualEncBitrate, _targetEncBitrate,
      _videoSendBitrate, _availableSendBw,
      _videoEncodeMs,
      _videoSendCodec,
      _framesEncoded - _oldFramesEncoded, avgqp]];

  // Video receive stats.
  NSString *videoReceiveFormat =
      @"VR (recv) %@x%@@%@fps | (decoded)%@ | (output)%@fps | %@/%@ | %@ms\n";
  [result appendString:[NSString stringWithFormat:videoReceiveFormat,
      _videoRecvWidth, _videoRecvHeight, _videoRecvFps,
      _videoDecodedFps,
      _videoOutputFps,
      _videoRecvBitrate, _availableRecvBw,
      _videoDecodeMs]];

  // Audio send stats.
  NSString *audioSendFormat = @"AS %@ | %@\n";
  [result appendString:[NSString stringWithFormat:audioSendFormat,
      _audioSendBitrate, _audioSendCodec]];

  // Audio receive stats.
  NSString *audioReceiveFormat = @"AR %@ | %@ | %@ms | (expandrate)%@";
  [result appendString:[NSString stringWithFormat:audioReceiveFormat,
      _audioRecvBitrate, _audioRecvCodec, _audioCurrentDelay,
      _audioExpandRate]];

  return result;
}

- (void)parseStatsReport:(RTCLegacyStatsReport *)statsReport {
  NSString *reportType = statsReport.type;
  if ([reportType isEqualToString:@"ssrc"] &&
      [statsReport.reportId rangeOfString:@"ssrc"].location != NSNotFound) {
    if ([statsReport.reportId rangeOfString:@"send"].location != NSNotFound) {
      [self parseSendSsrcStatsReport:statsReport];
    }
    if ([statsReport.reportId rangeOfString:@"recv"].location != NSNotFound) {
      [self parseRecvSsrcStatsReport:statsReport];
    }
  } else if ([reportType isEqualToString:@"VideoBwe"]) {
    [self parseBweStatsReport:statsReport];
  } else if ([reportType isEqualToString:@"googCandidatePair"]) {
    [self parseConnectionStatsReport:statsReport];
  }
}

#pragma mark - Private

- (int)calculateAvgQP {
  int deltaFramesEncoded = _framesEncoded - _oldFramesEncoded;
  int deltaQPSum = _videoQPSum - _oldVideoQPSum;

  return deltaFramesEncoded != 0 ? deltaQPSum / deltaFramesEncoded : 0;
}

- (void)parseBweStatsReport:(RTCLegacyStatsReport *)statsReport {
  [statsReport.values enumerateKeysAndObjectsUsingBlock:^(
      NSString *key, NSString *value, BOOL *stop) {
    if ([key isEqualToString:@"googAvailableSendBandwidth"]) {
      _availableSendBw =
          [ARDBitrateTracker bitrateStringForBitrate:value.doubleValue];
    } else if ([key isEqualToString:@"googAvailableReceiveBandwidth"]) {
      _availableRecvBw =
          [ARDBitrateTracker bitrateStringForBitrate:value.doubleValue];
    } else if ([key isEqualToString:@"googActualEncBitrate"]) {
      _actualEncBitrate =
          [ARDBitrateTracker bitrateStringForBitrate:value.doubleValue];
    } else if ([key isEqualToString:@"googTargetEncBitrate"]) {
      _targetEncBitrate =
          [ARDBitrateTracker bitrateStringForBitrate:value.doubleValue];
    }
  }];
}

- (void)parseConnectionStatsReport:(RTCLegacyStatsReport *)statsReport {
  NSString *activeConnection = statsReport.values[@"googActiveConnection"];
  if (![activeConnection isEqualToString:@"true"]) {
    return;
  }
  [statsReport.values enumerateKeysAndObjectsUsingBlock:^(
      NSString *key, NSString *value, BOOL *stop) {
    if ([key isEqualToString:@"googRtt"]) {
      _connRtt = value;
    } else if ([key isEqualToString:@"googLocalCandidateType"]) {
      _localCandType = value;
    } else if ([key isEqualToString:@"googRemoteCandidateType"]) {
      _remoteCandType = value;
    } else if ([key isEqualToString:@"googTransportType"]) {
      _transportType = value;
    } else if ([key isEqualToString:@"bytesReceived"]) {
      NSInteger byteCount = value.integerValue;
      [_connRecvBitrateTracker updateBitrateWithCurrentByteCount:byteCount];
      _connRecvBitrate = _connRecvBitrateTracker.bitrateString;
    } else if ([key isEqualToString:@"bytesSent"]) {
      NSInteger byteCount = value.integerValue;
      [_connSendBitrateTracker updateBitrateWithCurrentByteCount:byteCount];
      _connSendBitrate = _connSendBitrateTracker.bitrateString;
    }
  }];
}

- (void)parseSendSsrcStatsReport:(RTCLegacyStatsReport *)statsReport {
  NSDictionary *values = statsReport.values;
  if ([values objectForKey:@"googFrameRateSent"]) {
    // Video track.
    [self parseVideoSendStatsReport:statsReport];
  } else if ([values objectForKey:@"audioInputLevel"]) {
    // Audio track.
    [self parseAudioSendStatsReport:statsReport];
  }
}

- (void)parseAudioSendStatsReport:(RTCLegacyStatsReport *)statsReport {
  [statsReport.values enumerateKeysAndObjectsUsingBlock:^(
      NSString *key, NSString *value, BOOL *stop) {
    if ([key isEqualToString:@"googCodecName"]) {
      _audioSendCodec = value;
    } else if ([key isEqualToString:@"bytesSent"]) {
      NSInteger byteCount = value.integerValue;
      [_audioSendBitrateTracker updateBitrateWithCurrentByteCount:byteCount];
      _audioSendBitrate = _audioSendBitrateTracker.bitrateString;
    }
  }];
}

- (void)parseVideoSendStatsReport:(RTCLegacyStatsReport *)statsReport {
  [statsReport.values enumerateKeysAndObjectsUsingBlock:^(
      NSString *key, NSString *value, BOOL *stop) {
    if ([key isEqualToString:@"googCodecName"]) {
      _videoSendCodec = value;
    } else if ([key isEqualToString:@"googFrameHeightInput"]) {
      _videoInputHeight = value;
    } else if ([key isEqualToString:@"googFrameWidthInput"]) {
      _videoInputWidth = value;
    } else if ([key isEqualToString:@"googFrameRateInput"]) {
      _videoInputFps = value;
    } else if ([key isEqualToString:@"googFrameHeightSent"]) {
      _videoSendHeight = value;
    } else if ([key isEqualToString:@"googFrameWidthSent"]) {
      _videoSendWidth = value;
    } else if ([key isEqualToString:@"googFrameRateSent"]) {
      _videoSendFps = value;
    } else if ([key isEqualToString:@"googAvgEncodeMs"]) {
      _videoEncodeMs = value;
    } else if ([key isEqualToString:@"bytesSent"]) {
      NSInteger byteCount = value.integerValue;
      [_videoSendBitrateTracker updateBitrateWithCurrentByteCount:byteCount];
      _videoSendBitrate = _videoSendBitrateTracker.bitrateString;
    } else if ([key isEqualToString:@"qpSum"]) {
      _oldVideoQPSum = _videoQPSum;
      _videoQPSum = value.integerValue;
    } else if ([key isEqualToString:@"framesEncoded"]) {
      _oldFramesEncoded = _framesEncoded;
      _framesEncoded = value.integerValue;
    }
  }];
}

- (void)parseRecvSsrcStatsReport:(RTCLegacyStatsReport *)statsReport {
  NSDictionary *values = statsReport.values;
  if ([values objectForKey:@"googFrameWidthReceived"]) {
    // Video track.
    [self parseVideoRecvStatsReport:statsReport];
  } else if ([values objectForKey:@"audioOutputLevel"]) {
    // Audio track.
    [self parseAudioRecvStatsReport:statsReport];
  }
}

- (void)parseAudioRecvStatsReport:(RTCLegacyStatsReport *)statsReport {
  [statsReport.values enumerateKeysAndObjectsUsingBlock:^(
      NSString *key, NSString *value, BOOL *stop) {
    if ([key isEqualToString:@"googCodecName"]) {
      _audioRecvCodec = value;
    } else if ([key isEqualToString:@"bytesReceived"]) {
      NSInteger byteCount = value.integerValue;
      [_audioRecvBitrateTracker updateBitrateWithCurrentByteCount:byteCount];
      _audioRecvBitrate = _audioRecvBitrateTracker.bitrateString;
    } else if ([key isEqualToString:@"googSpeechExpandRate"]) {
      _audioExpandRate = value;
    } else if ([key isEqualToString:@"googCurrentDelayMs"]) {
      _audioCurrentDelay = value;
    }
  }];
}

- (void)parseVideoRecvStatsReport:(RTCLegacyStatsReport *)statsReport {
  [statsReport.values enumerateKeysAndObjectsUsingBlock:^(
      NSString *key, NSString *value, BOOL *stop) {
    if ([key isEqualToString:@"googFrameHeightReceived"]) {
      _videoRecvHeight = value;
    } else if ([key isEqualToString:@"googFrameWidthReceived"]) {
      _videoRecvWidth = value;
    } else if ([key isEqualToString:@"googFrameRateReceived"]) {
      _videoRecvFps = value;
    } else if ([key isEqualToString:@"googFrameRateDecoded"]) {
      _videoDecodedFps = value;
    } else if ([key isEqualToString:@"googFrameRateOutput"]) {
      _videoOutputFps = value;
    } else if ([key isEqualToString:@"googDecodeMs"]) {
      _videoDecodeMs = value;
    } else if ([key isEqualToString:@"bytesReceived"]) {
      NSInteger byteCount = value.integerValue;
      [_videoRecvBitrateTracker updateBitrateWithCurrentByteCount:byteCount];
      _videoRecvBitrate = _videoRecvBitrateTracker.bitrateString;
    }
  }];
}

@end

