/*
 *  Copyright (c) 2012 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.
 */

#include "webrtc/modules/audio_coding/main/test/TestVADDTX.h"

#include <iostream>

#include "webrtc/common.h"
#include "webrtc/common_types.h"
#include "webrtc/engine_configurations.h"
#include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h"
#include "webrtc/modules/audio_coding/main/interface/audio_coding_module_typedefs.h"
#include "webrtc/modules/audio_coding/main/test/utility.h"
#include "webrtc/system_wrappers/interface/trace.h"
#include "webrtc/test/testsupport/fileutils.h"

namespace webrtc {

TestVADDTX::TestVADDTX(const Config& config)
    : _acmA(config.Get<AudioCodingModuleFactory>().Create(0)),
      _acmB(config.Get<AudioCodingModuleFactory>().Create(1)),
      _channelA2B(NULL) {
}

TestVADDTX::~TestVADDTX() {
  if (_channelA2B != NULL) {
    delete _channelA2B;
    _channelA2B = NULL;
  }
}

void TestVADDTX::Perform() {
  const std::string file_name = webrtc::test::ResourcePath(
      "audio_coding/testfile32kHz", "pcm");
  _inFileA.Open(file_name, 32000, "rb");

  EXPECT_EQ(0, _acmA->InitializeReceiver());
  EXPECT_EQ(0, _acmB->InitializeReceiver());

  uint8_t numEncoders = _acmA->NumberOfCodecs();
  CodecInst myCodecParam;
  for (uint8_t n = 0; n < numEncoders; n++) {
    EXPECT_EQ(0, _acmB->Codec(n, &myCodecParam));
    if (!strcmp(myCodecParam.plname, "opus")) {
      // Register Opus as mono.
      myCodecParam.channels = 1;
    }
    EXPECT_EQ(0, _acmB->RegisterReceiveCodec(myCodecParam));
  }

  // Create and connect the channel
  _channelA2B = new Channel;
  _acmA->RegisterTransportCallback(_channelA2B);
  _channelA2B->RegisterReceiverACM(_acmB.get());

  _acmA->RegisterVADCallback(&_monitor);

  int16_t testCntr = 1;

#ifdef WEBRTC_CODEC_ISAC
  // Open outputfile
  OpenOutFile(testCntr++);

  // Register iSAC WB as send codec
  char nameISAC[] = "ISAC";
  RegisterSendCodec('A', nameISAC, 16000);

  // Run the five test cased
  runTestCases();

  // Close file
  _outFileB.Close();

  // Open outputfile
  OpenOutFile(testCntr++);

  // Register iSAC SWB as send codec
  RegisterSendCodec('A', nameISAC, 32000);

  // Run the five test cased
  runTestCases();

  // Close file
  _outFileB.Close();
#endif
#ifdef WEBRTC_CODEC_ILBC
  // Open outputfile
  OpenOutFile(testCntr++);

  // Register iLBC as send codec
  char nameILBC[] = "ilbc";
  RegisterSendCodec('A', nameILBC);

  // Run the five test cased
  runTestCases();

  // Close file
  _outFileB.Close();

#endif
#ifdef WEBRTC_CODEC_OPUS
  // Open outputfile
  OpenOutFile(testCntr++);

  // Register Opus as send codec
  char nameOPUS[] = "opus";
  RegisterSendCodec('A', nameOPUS);

  // Run the five test cased
  runTestCases();

  // Close file
  _outFileB.Close();

#endif
}

void TestVADDTX::runTestCases() {
  // #1 DTX = OFF, VAD = ON, VADNormal
  SetVAD(false, true, VADNormal);
  Run();
  VerifyTest();

  // #2 DTX = OFF, VAD = ON, VADAggr
  SetVAD(false, true, VADAggr);
  Run();
  VerifyTest();

  // #3 DTX = ON, VAD = ON, VADLowBitrate
  SetVAD(true, true, VADLowBitrate);
  Run();
  VerifyTest();

  // #4 DTX = ON, VAD = ON, VADVeryAggr
  SetVAD(true, true, VADVeryAggr);
  Run();
  VerifyTest();

  // #5 DTX = ON, VAD = OFF, VADNormal
  SetVAD(true, false, VADNormal);
  Run();
  VerifyTest();
}

void TestVADDTX::runTestInternalDTX(int expected_result) {
  // #6 DTX = ON, VAD = ON, VADNormal
  SetVAD(true, true, VADNormal);
  EXPECT_EQ(expected_result, _acmA->ReplaceInternalDTXWithWebRtc(true));
  if (expected_result == 0) {
    Run();
    VerifyTest();
  }
}

void TestVADDTX::SetVAD(bool statusDTX, bool statusVAD, int16_t vadMode) {
  bool dtxEnabled, vadEnabled;
  ACMVADMode vadModeSet;

  EXPECT_EQ(0, _acmA->SetVAD(statusDTX, statusVAD, (ACMVADMode) vadMode));
  EXPECT_EQ(0, _acmA->VAD(&dtxEnabled, &vadEnabled, &vadModeSet));

  // Requested VAD/DTX settings
  _setStruct.statusDTX = statusDTX;
  _setStruct.statusVAD = statusVAD;
  _setStruct.vadMode = (ACMVADMode) vadMode;

  // VAD settings after setting VAD in ACM
  _getStruct.statusDTX = dtxEnabled;
  _getStruct.statusVAD = vadEnabled;
  _getStruct.vadMode = vadModeSet;
}

VADDTXstruct TestVADDTX::GetVAD() {
  VADDTXstruct retStruct;
  bool dtxEnabled, vadEnabled;
  ACMVADMode vadModeSet;

  EXPECT_EQ(0, _acmA->VAD(&dtxEnabled, &vadEnabled, &vadModeSet));

  retStruct.statusDTX = dtxEnabled;
  retStruct.statusVAD = vadEnabled;
  retStruct.vadMode = vadModeSet;
  return retStruct;
}

int16_t TestVADDTX::RegisterSendCodec(char side, char* codecName,
                                      int32_t samplingFreqHz,
                                      int32_t rateKbps) {
  std::cout << std::flush;
  AudioCodingModule* myACM;
  switch (side) {
    case 'A': {
      myACM = _acmA.get();
      break;
    }
    case 'B': {
      myACM = _acmB.get();
      break;
    }
    default:
      return -1;
  }

  if (myACM == NULL) {
    return -1;
  }

  CodecInst myCodecParam;
  for (int16_t codecCntr = 0; codecCntr < myACM->NumberOfCodecs();
      codecCntr++) {
    EXPECT_EQ(0, myACM->Codec((uint8_t) codecCntr, &myCodecParam));
    if (!STR_CASE_CMP(myCodecParam.plname, codecName)) {
      if ((samplingFreqHz == -1) || (myCodecParam.plfreq == samplingFreqHz)) {
        if ((rateKbps == -1) || (myCodecParam.rate == rateKbps)) {
          break;
        }
      }
    }
  }

  // We only allow VAD/DTX when sending mono.
  myCodecParam.channels = 1;
  EXPECT_EQ(0, myACM->RegisterSendCodec(myCodecParam));

  // initialization was succesful
  return 0;
}

void TestVADDTX::Run() {
  AudioFrame audioFrame;

  uint16_t SamplesIn10MsecA = _inFileA.PayloadLength10Ms();
  uint32_t timestampA = 1;
  int32_t outFreqHzB = _outFileB.SamplingFrequency();

  while (!_inFileA.EndOfFile()) {
    _inFileA.Read10MsData(audioFrame);
    audioFrame.timestamp_ = timestampA;
    timestampA += SamplesIn10MsecA;
    EXPECT_EQ(0, _acmA->Add10MsData(audioFrame));
    EXPECT_GT(_acmA->Process(), -1);
    EXPECT_EQ(0, _acmB->PlayoutData10Ms(outFreqHzB, &audioFrame));
    _outFileB.Write10MsData(audioFrame.data_, audioFrame.samples_per_channel_);
  }
#ifdef PRINT_STAT
  _monitor.PrintStatistics();
#endif
  _inFileA.Rewind();
  _monitor.GetStatistics(_statCounter);
  _monitor.ResetStatistics();
}

void TestVADDTX::OpenOutFile(int16_t test_number) {
  std::string file_name;
  std::stringstream file_stream;
  file_stream << webrtc::test::OutputPath();
  file_stream << "testVADDTX_outFile_";
  file_stream << test_number << ".pcm";
  file_name = file_stream.str();
  _outFileB.Open(file_name, 16000, "wb");
}

int16_t TestVADDTX::VerifyTest() {
  // Verify empty frame result
  uint8_t statusEF = 0;
  uint8_t vadPattern = 0;
  uint8_t emptyFramePattern[6];
  CodecInst myCodecParam;
  _acmA->SendCodec(&myCodecParam);
  bool dtxInUse = true;
  bool isReplaced = false;
  if ((STR_CASE_CMP(myCodecParam.plname, "G729") == 0)
      || (STR_CASE_CMP(myCodecParam.plname, "G723") == 0)
      || (STR_CASE_CMP(myCodecParam.plname, "AMR") == 0)
      || (STR_CASE_CMP(myCodecParam.plname, "AMR-wb") == 0)
      || (STR_CASE_CMP(myCodecParam.plname, "speex") == 0)) {
    _acmA->IsInternalDTXReplacedWithWebRtc(&isReplaced);
    if (!isReplaced) {
      dtxInUse = false;
    }
  } else if (STR_CASE_CMP(myCodecParam.plname, "opus") == 0) {
    if (_getStruct.statusDTX != false) {
      // DTX status doesn't match expected.
      vadPattern |= 4;
    } else if (_getStruct.statusVAD != false) {
      // Mismatch in VAD setting.
      vadPattern |= 2;
    } else {
      _setStruct.statusDTX = false;
      _setStruct.statusVAD = false;
    }
  }

  // Check for error in VAD/DTX settings
  if (_getStruct.statusDTX != _setStruct.statusDTX) {
    // DTX status doesn't match expected
    vadPattern |= 4;
  }
  if (_getStruct.statusDTX) {
    if ((!_getStruct.statusVAD && dtxInUse)
        || (!dtxInUse && (_getStruct.statusVAD != _setStruct.statusVAD))) {
      // Missmatch in VAD setting
      vadPattern |= 2;
    }
  } else {
    if (_getStruct.statusVAD != _setStruct.statusVAD) {
      // VAD status doesn't match expected
      vadPattern |= 2;
    }
  }
  if (_getStruct.vadMode != _setStruct.vadMode) {
    // VAD Mode doesn't match expected
    vadPattern |= 1;
  }

  // Set expected empty frame pattern
  int ii;
  for (ii = 0; ii < 6; ii++) {
    emptyFramePattern[ii] = 0;
  }
  // 0 - "kNoEncoding", not important to check.
  //      Codecs with packetsize != 80 samples will get this output.
  // 1 - "kActiveNormalEncoded", expect to receive some frames with this label .
  // 2 - "kPassiveNormalEncoded".
  // 3 - "kPassiveDTXNB".
  // 4 - "kPassiveDTXWB".
  // 5 - "kPassiveDTXSWB".
  emptyFramePattern[0] = 1;
  emptyFramePattern[1] = 1;
  emptyFramePattern[2] = (((!_getStruct.statusDTX && _getStruct.statusVAD)
      || (!dtxInUse && _getStruct.statusDTX)));
  emptyFramePattern[3] = ((_getStruct.statusDTX && dtxInUse
      && (_acmA->SendFrequency() == 8000)));
  emptyFramePattern[4] = ((_getStruct.statusDTX && dtxInUse
      && (_acmA->SendFrequency() == 16000)));
  emptyFramePattern[5] = ((_getStruct.statusDTX && dtxInUse
      && (_acmA->SendFrequency() == 32000)));

  // Check pattern 1-5 (skip 0)
  for (int ii = 1; ii < 6; ii++) {
    if (emptyFramePattern[ii]) {
      statusEF |= (_statCounter[ii] == 0);
    } else {
      statusEF |= (_statCounter[ii] > 0);
    }
  }
  EXPECT_EQ(0, statusEF);
  EXPECT_EQ(0, vadPattern);

  return 0;
}

ActivityMonitor::ActivityMonitor() {
  _counter[0] = _counter[1] = _counter[2] = _counter[3] = _counter[4] =
      _counter[5] = 0;
}

ActivityMonitor::~ActivityMonitor() {
}

int32_t ActivityMonitor::InFrameType(int16_t frameType) {
  _counter[frameType]++;
  return 0;
}

void ActivityMonitor::PrintStatistics() {
  printf("\n");
  printf("kActiveNormalEncoded  kPassiveNormalEncoded  kPassiveDTXWB  ");
  printf("kPassiveDTXNB kPassiveDTXSWB kFrameEmpty\n");
  printf("%19u", _counter[1]);
  printf("%22u", _counter[2]);
  printf("%14u", _counter[3]);
  printf("%14u", _counter[4]);
  printf("%14u", _counter[5]);
  printf("%11u", _counter[0]);
  printf("\n\n");
}

void ActivityMonitor::ResetStatistics() {
  _counter[0] = _counter[1] = _counter[2] = _counter[3] = _counter[4] =
      _counter[5] = 0;
}

void ActivityMonitor::GetStatistics(uint32_t* getCounter) {
  for (int ii = 0; ii < 6; ii++) {
    getCounter[ii] = _counter[ii];
  }
}

}  // namespace webrtc
