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

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <iostream>
#include <ostream>
#include <string>

#include "testing/gtest/include/gtest/gtest.h"
#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/test/utility.h"
#include "webrtc/system_wrappers/interface/event_wrapper.h"
#include "webrtc/system_wrappers/interface/thread_wrapper.h"
#include "webrtc/system_wrappers/interface/tick_util.h"
#include "webrtc/system_wrappers/interface/trace.h"
#include "webrtc/test/testsupport/fileutils.h"

namespace webrtc {

#define TEST_DURATION_SEC 600
#define NUMBER_OF_SENDER_TESTS 6
#define MAX_FILE_NAME_LENGTH_BYTE 500
#define CHECK_THREAD_NULLITY(myThread, S)                                      \
  if(myThread != NULL) {                                                       \
    (myThread)->Start();                                                       \
  } else {                                                                     \
    ADD_FAILURE() << S;                                                        \
  }

void APITest::Wait(uint32_t waitLengthMs) {
  if (_randomTest) {
    return;
  } else {
    EventWrapper* myEvent = EventWrapper::Create();
    myEvent->Wait(waitLengthMs);
    delete myEvent;
    return;
  }
}

APITest::APITest(const Config& config)
    : _acmA(AudioCodingModule::Create(1)),
      _acmB(AudioCodingModule::Create(2)),
      _channel_A2B(NULL),
      _channel_B2A(NULL),
      _writeToFile(true),
      _pullEventA(NULL),
      _pushEventA(NULL),
      _processEventA(NULL),
      _apiEventA(NULL),
      _pullEventB(NULL),
      _pushEventB(NULL),
      _processEventB(NULL),
      _apiEventB(NULL),
      _codecCntrA(0),
      _codecCntrB(0),
      _thereIsEncoderA(false),
      _thereIsEncoderB(false),
      _thereIsDecoderA(false),
      _thereIsDecoderB(false),
      _sendVADA(false),
      _sendDTXA(false),
      _sendVADModeA(VADNormal),
      _sendVADB(false),
      _sendDTXB(false),
      _sendVADModeB(VADNormal),
      _minDelayA(0),
      _minDelayB(0),
      _dotPositionA(0),
      _dotMoveDirectionA(1),
      _dotPositionB(39),
      _dotMoveDirectionB(-1),
      _dtmfCallback(NULL),
      _vadCallbackA(NULL),
      _vadCallbackB(NULL),
      _apiTestRWLock(*RWLockWrapper::CreateRWLock()),
      _randomTest(false),
      _testNumA(0),
      _testNumB(1) {
  int n;
  for (n = 0; n < 32; n++) {
    _payloadUsed[n] = false;
  }

  _movingDot[40] = '\0';

  for (int n = 0; n < 40; n++) {
    _movingDot[n] = ' ';
  }
}

APITest::~APITest() {
  DELETE_POINTER(_channel_A2B);
  DELETE_POINTER(_channel_B2A);

  DELETE_POINTER(_pushEventA);
  DELETE_POINTER(_pullEventA);
  DELETE_POINTER(_processEventA);
  DELETE_POINTER(_apiEventA);

  DELETE_POINTER(_pushEventB);
  DELETE_POINTER(_pullEventB);
  DELETE_POINTER(_processEventB);
  DELETE_POINTER(_apiEventB);

  _inFileA.Close();
  _outFileA.Close();

  _inFileB.Close();
  _outFileB.Close();

  DELETE_POINTER(_dtmfCallback);
  DELETE_POINTER(_vadCallbackA);
  DELETE_POINTER(_vadCallbackB);

  delete &_apiTestRWLock;
}

int16_t APITest::SetUp() {
  CodecInst dummyCodec;
  int lastPayloadType = 0;

  int16_t numCodecs = _acmA->NumberOfCodecs();
  for (uint8_t n = 0; n < numCodecs; n++) {
    AudioCodingModule::Codec(n, &dummyCodec);
    if ((STR_CASE_CMP(dummyCodec.plname, "CN") == 0)
        && (dummyCodec.plfreq == 32000)) {
      continue;
    }

    printf("Register Receive Codec %s  ", dummyCodec.plname);

    if ((n != 0) && !FixedPayloadTypeCodec(dummyCodec.plname)) {
      // Check registration with an already occupied payload type
      int currentPayloadType = dummyCodec.pltype;
      dummyCodec.pltype = 97;  //lastPayloadType;
      CHECK_ERROR(_acmB->RegisterReceiveCodec(dummyCodec));
      dummyCodec.pltype = currentPayloadType;
    }

    if ((n < numCodecs - 1) && !FixedPayloadTypeCodec(dummyCodec.plname)) {
      // test if re-registration works;
      CodecInst nextCodec;
      int currentPayloadType = dummyCodec.pltype;
      AudioCodingModule::Codec(n + 1, &nextCodec);
      dummyCodec.pltype = nextCodec.pltype;
      if (!FixedPayloadTypeCodec(nextCodec.plname)) {
        _acmB->RegisterReceiveCodec(dummyCodec);
      }
      dummyCodec.pltype = currentPayloadType;
    }

    if ((n < numCodecs - 1) && !FixedPayloadTypeCodec(dummyCodec.plname)) {
      // test if un-registration works;
      CodecInst nextCodec;
      AudioCodingModule::Codec(n + 1, &nextCodec);
      nextCodec.pltype = dummyCodec.pltype;
      if (!FixedPayloadTypeCodec(nextCodec.plname)) {
        CHECK_ERROR_MT(_acmA->RegisterReceiveCodec(nextCodec));
        CHECK_ERROR_MT(_acmA->UnregisterReceiveCodec(nextCodec.pltype));
      }
    }

    CHECK_ERROR_MT(_acmA->RegisterReceiveCodec(dummyCodec));
    printf("   side A done!");
    CHECK_ERROR_MT(_acmB->RegisterReceiveCodec(dummyCodec));
    printf("   side B done!\n");

    if (!strcmp(dummyCodec.plname, "CN")) {
      CHECK_ERROR_MT(_acmA->RegisterSendCodec(dummyCodec));
      CHECK_ERROR_MT(_acmB->RegisterSendCodec(dummyCodec));
    }
    lastPayloadType = dummyCodec.pltype;
    if ((lastPayloadType >= 96) && (lastPayloadType <= 127)) {
      _payloadUsed[lastPayloadType - 96] = true;
    }
  }
  _thereIsDecoderA = true;
  _thereIsDecoderB = true;

  // Register Send Codec
  AudioCodingModule::Codec((uint8_t) _codecCntrA, &dummyCodec);
  CHECK_ERROR_MT(_acmA->RegisterSendCodec(dummyCodec));
  _thereIsEncoderA = true;
  //
  AudioCodingModule::Codec((uint8_t) _codecCntrB, &dummyCodec);
  CHECK_ERROR_MT(_acmB->RegisterSendCodec(dummyCodec));
  _thereIsEncoderB = true;

  uint16_t frequencyHz;

  printf("\n\nAPI Test\n");
  printf("========\n");
  printf("Hit enter to accept the default values indicated in []\n\n");

  //--- Input A
  std::string file_name = webrtc::test::ResourcePath(
      "audio_coding/testfile32kHz", "pcm");
  frequencyHz = 32000;
  printf("Enter input file at side A [%s]: ", file_name.c_str());
  PCMFile::ChooseFile(&file_name, 499, &frequencyHz);
  _inFileA.Open(file_name, frequencyHz, "rb", true);

  //--- Output A
  std::string out_file_a = webrtc::test::OutputPath() + "outA.pcm";
  printf("Enter output file at side A [%s]: ", out_file_a.c_str());
  PCMFile::ChooseFile(&out_file_a, 499, &frequencyHz);
  _outFileA.Open(out_file_a, frequencyHz, "wb");

  //--- Input B
  file_name = webrtc::test::ResourcePath("audio_coding/testfile32kHz", "pcm");
  printf("\n\nEnter input file at side B [%s]: ", file_name.c_str());
  PCMFile::ChooseFile(&file_name, 499, &frequencyHz);
  _inFileB.Open(file_name, frequencyHz, "rb", true);

  //--- Output B
  std::string out_file_b = webrtc::test::OutputPath() + "outB.pcm";
  printf("Enter output file at side B [%s]: ", out_file_b.c_str());
  PCMFile::ChooseFile(&out_file_b, 499, &frequencyHz);
  _outFileB.Open(out_file_b, frequencyHz, "wb");

  //--- Set A-to-B channel
  _channel_A2B = new Channel(2);
  CHECK_ERROR_MT(_acmA->RegisterTransportCallback(_channel_A2B));
  _channel_A2B->RegisterReceiverACM(_acmB.get());

  //--- Set B-to-A channel
  _channel_B2A = new Channel(1);
  CHECK_ERROR_MT(_acmB->RegisterTransportCallback(_channel_B2A));
  _channel_B2A->RegisterReceiverACM(_acmA.get());

  //--- EVENT TIMERS
  // A
  _pullEventA = EventWrapper::Create();
  _pushEventA = EventWrapper::Create();
  _processEventA = EventWrapper::Create();
  _apiEventA = EventWrapper::Create();
  // B
  _pullEventB = EventWrapper::Create();
  _pushEventB = EventWrapper::Create();
  _processEventB = EventWrapper::Create();
  _apiEventB = EventWrapper::Create();

  //--- I/O params
  // A
  _outFreqHzA = _outFileA.SamplingFrequency();
  // B
  _outFreqHzB = _outFileB.SamplingFrequency();

  //Trace::SetEncryptedTraceFile("ACMAPITestEncrypted.txt");

  char print[11];

  // Create a trace file.
  Trace::CreateTrace();
  Trace::SetTraceFile(
      (webrtc::test::OutputPath() + "acm_api_trace.txt").c_str());

  printf("\nRandom Test (y/n)?");
  EXPECT_TRUE(fgets(print, 10, stdin) != NULL);
  print[10] = '\0';
  if (strstr(print, "y") != NULL) {
    _randomTest = true;
    _verbose = false;
    _writeToFile = false;
  } else {
    _randomTest = false;
    printf("\nPrint Tests (y/n)? ");
    EXPECT_TRUE(fgets(print, 10, stdin) != NULL);
    print[10] = '\0';
    if (strstr(print, "y") == NULL) {
      EXPECT_TRUE(freopen("APITest_log.txt", "w", stdout) != 0);
      _verbose = false;
    }
  }

#ifdef WEBRTC_DTMF_DETECTION
  _dtmfCallback = new DTMFDetector;
#endif
  _vadCallbackA = new VADCallback;
  _vadCallbackB = new VADCallback;

  return 0;
}

bool APITest::PushAudioThreadA(void* obj) {
  return static_cast<APITest*>(obj)->PushAudioRunA();
}

bool APITest::PushAudioThreadB(void* obj) {
  return static_cast<APITest*>(obj)->PushAudioRunB();
}

bool APITest::PullAudioThreadA(void* obj) {
  return static_cast<APITest*>(obj)->PullAudioRunA();
}

bool APITest::PullAudioThreadB(void* obj) {
  return static_cast<APITest*>(obj)->PullAudioRunB();
}

bool APITest::ProcessThreadA(void* obj) {
  return static_cast<APITest*>(obj)->ProcessRunA();
}

bool APITest::ProcessThreadB(void* obj) {
  return static_cast<APITest*>(obj)->ProcessRunB();
}

bool APITest::APIThreadA(void* obj) {
  return static_cast<APITest*>(obj)->APIRunA();
}

bool APITest::APIThreadB(void* obj) {
  return static_cast<APITest*>(obj)->APIRunB();
}

bool APITest::PullAudioRunA() {
  _pullEventA->Wait(100);
  AudioFrame audioFrame;
  if (_acmA->PlayoutData10Ms(_outFreqHzA, &audioFrame) < 0) {
    bool thereIsDecoder;
    {
      ReadLockScoped rl(_apiTestRWLock);
      thereIsDecoder = _thereIsDecoderA;
    }
    if (thereIsDecoder) {
      fprintf(stderr, "\n>>>>>>    cannot pull audio A       <<<<<<<< \n");
    }
  } else {
    if (_writeToFile) {
      _outFileA.Write10MsData(audioFrame);
    }
  }
  return true;
}

bool APITest::PullAudioRunB() {
  _pullEventB->Wait(100);
  AudioFrame audioFrame;
  if (_acmB->PlayoutData10Ms(_outFreqHzB, &audioFrame) < 0) {
    bool thereIsDecoder;
    {
      ReadLockScoped rl(_apiTestRWLock);
      thereIsDecoder = _thereIsDecoderB;
    }
    if (thereIsDecoder) {
      fprintf(stderr, "\n>>>>>>    cannot pull audio B       <<<<<<<< \n");
      fprintf(stderr, "%d %d\n", _testNumA, _testNumB);
    }
  } else {
    if (_writeToFile) {
      _outFileB.Write10MsData(audioFrame);
    }
  }
  return true;
}

bool APITest::PushAudioRunA() {
  _pushEventA->Wait(100);
  AudioFrame audioFrame;
  _inFileA.Read10MsData(audioFrame);
  if (_acmA->Add10MsData(audioFrame) < 0) {
    bool thereIsEncoder;
    {
      ReadLockScoped rl(_apiTestRWLock);
      thereIsEncoder = _thereIsEncoderA;
    }
    if (thereIsEncoder) {
      fprintf(stderr, "\n>>>>        add10MsData at A failed       <<<<\n");
    }
  }
  return true;
}

bool APITest::PushAudioRunB() {
  _pushEventB->Wait(100);
  AudioFrame audioFrame;
  _inFileB.Read10MsData(audioFrame);
  if (_acmB->Add10MsData(audioFrame) < 0) {
    bool thereIsEncoder;
    {
      ReadLockScoped rl(_apiTestRWLock);
      thereIsEncoder = _thereIsEncoderB;
    }

    if (thereIsEncoder) {
      fprintf(stderr, "\n>>>>   cannot add audio to B    <<<<");
    }
  }

  return true;
}

bool APITest::ProcessRunA() {
  _processEventA->Wait(100);
  return true;
}

bool APITest::ProcessRunB() {
  _processEventB->Wait(100);
  return true;
}

/*/
 *
 * In side A we test the APIs which are related to sender Side.
 *
/*/

void APITest::RunTest(char thread) {
  int testNum;
  {
    WriteLockScoped cs(_apiTestRWLock);
    if (thread == 'A') {
      _testNumA = (_testNumB + 1 + (rand() % 4)) % 5;
      testNum = _testNumA;

      _movingDot[_dotPositionA] = ' ';
      if (_dotPositionA == 0) {
        _dotMoveDirectionA = 1;
      }
      if (_dotPositionA == 19) {
        _dotMoveDirectionA = -1;
      }
      _dotPositionA += _dotMoveDirectionA;
      _movingDot[_dotPositionA] = (_dotMoveDirectionA > 0) ? '>' : '<';
    } else {
      _testNumB = (_testNumA + 1 + (rand() % 4)) % 5;
      testNum = _testNumB;

      _movingDot[_dotPositionB] = ' ';
      if (_dotPositionB == 20) {
        _dotMoveDirectionB = 1;
      }
      if (_dotPositionB == 39) {
        _dotMoveDirectionB = -1;
      }
      _dotPositionB += _dotMoveDirectionB;
      _movingDot[_dotPositionB] = (_dotMoveDirectionB > 0) ? '>' : '<';
    }
    //fprintf(stderr, "%c: %d \n", thread, testNum);
    //fflush(stderr);
  }
  switch (testNum) {
    case 0:
      CurrentCodec('A');
      ChangeCodec('A');
      break;
    case 1:
      TestPlayout('B');
      break;
    case 2:
      if (!_randomTest) {
        fprintf(stdout, "\nTesting Delay ...\n");
      }
      TestDelay('A');
      break;
    case 3:
      TestSendVAD('A');
      break;
    case 4:
      TestRegisteration('A');
      break;
    default:
      fprintf(stderr, "Wrong Test Number\n");
      getc(stdin);
      exit(1);
  }
}

bool APITest::APIRunA() {
  _apiEventA->Wait(50);

  bool randomTest;
  {
    ReadLockScoped rl(_apiTestRWLock);
    randomTest = _randomTest;
  }
  if (randomTest) {
    RunTest('A');
  } else {
    CurrentCodec('A');
    ChangeCodec('A');
    TestPlayout('B');
    if (_codecCntrA == 0) {
      fprintf(stdout, "\nTesting Delay ...\n");
      TestDelay('A');
    }
    // VAD TEST
    TestSendVAD('A');
    TestRegisteration('A');
  }
  return true;
}

bool APITest::APIRunB() {
  _apiEventB->Wait(50);
  bool randomTest;
  {
    ReadLockScoped rl(_apiTestRWLock);
    randomTest = _randomTest;
  }
  //_apiEventB->Wait(2000);
  if (randomTest) {
    RunTest('B');
  }

  return true;
}

void APITest::Perform() {
  SetUp();

  //--- THREADS
  // A
  // PUSH
  rtc::scoped_ptr<ThreadWrapper> myPushAudioThreadA =
      ThreadWrapper::CreateThread(PushAudioThreadA, this, "PushAudioThreadA");
  CHECK_THREAD_NULLITY(myPushAudioThreadA, "Unable to start A::PUSH thread");
  // PULL
  rtc::scoped_ptr<ThreadWrapper> myPullAudioThreadA =
      ThreadWrapper::CreateThread(PullAudioThreadA, this, "PullAudioThreadA");
  CHECK_THREAD_NULLITY(myPullAudioThreadA, "Unable to start A::PULL thread");
  // Process
  rtc::scoped_ptr<ThreadWrapper> myProcessThreadA = ThreadWrapper::CreateThread(
      ProcessThreadA, this, "ProcessThreadA");
  CHECK_THREAD_NULLITY(myProcessThreadA, "Unable to start A::Process thread");
  // API
  rtc::scoped_ptr<ThreadWrapper> myAPIThreadA = ThreadWrapper::CreateThread(
      APIThreadA, this, "APIThreadA");
  CHECK_THREAD_NULLITY(myAPIThreadA, "Unable to start A::API thread");
  // B
  // PUSH
  rtc::scoped_ptr<ThreadWrapper> myPushAudioThreadB =
      ThreadWrapper::CreateThread(PushAudioThreadB, this, "PushAudioThreadB");
  CHECK_THREAD_NULLITY(myPushAudioThreadB, "Unable to start B::PUSH thread");
  // PULL
  rtc::scoped_ptr<ThreadWrapper> myPullAudioThreadB =
      ThreadWrapper::CreateThread(PullAudioThreadB, this, "PullAudioThreadB");
  CHECK_THREAD_NULLITY(myPullAudioThreadB, "Unable to start B::PULL thread");
  // Process
  rtc::scoped_ptr<ThreadWrapper> myProcessThreadB = ThreadWrapper::CreateThread(
      ProcessThreadB, this, "ProcessThreadB");
  CHECK_THREAD_NULLITY(myProcessThreadB, "Unable to start B::Process thread");
  // API
  rtc::scoped_ptr<ThreadWrapper> myAPIThreadB = ThreadWrapper::CreateThread(
      APIThreadB, this, "APIThreadB");
  CHECK_THREAD_NULLITY(myAPIThreadB, "Unable to start B::API thread");

  //_apiEventA->StartTimer(true, 5000);
  //_apiEventB->StartTimer(true, 5000);

  _processEventA->StartTimer(true, 10);
  _processEventB->StartTimer(true, 10);

  _pullEventA->StartTimer(true, 10);
  _pullEventB->StartTimer(true, 10);

  _pushEventA->StartTimer(true, 10);
  _pushEventB->StartTimer(true, 10);

  // Keep main thread waiting for sender/receiver
  // threads to complete
  EventWrapper* completeEvent = EventWrapper::Create();
  uint64_t startTime = TickTime::MillisecondTimestamp();
  uint64_t currentTime;
  // Run test in 2 minutes (120000 ms).
  do {
    {
      //ReadLockScoped rl(_apiTestRWLock);
      //fprintf(stderr, "\r%s", _movingDot);
    }
    //fflush(stderr);
    completeEvent->Wait(50);
    currentTime = TickTime::MillisecondTimestamp();
  } while ((currentTime - startTime) < 120000);

  //completeEvent->Wait(0xFFFFFFFF);
  //(unsigned long)((unsigned long)TEST_DURATION_SEC * (unsigned long)1000));
  delete completeEvent;

  myPushAudioThreadA->Stop();
  myPullAudioThreadA->Stop();
  myProcessThreadA->Stop();
  myAPIThreadA->Stop();

  myPushAudioThreadB->Stop();
  myPullAudioThreadB->Stop();
  myProcessThreadB->Stop();
  myAPIThreadB->Stop();
}

void APITest::CheckVADStatus(char side) {

  bool dtxEnabled;
  bool vadEnabled;
  ACMVADMode vadMode;

  if (side == 'A') {
    _acmA->VAD(&dtxEnabled, &vadEnabled, &vadMode);
    _acmA->RegisterVADCallback(NULL);
    _vadCallbackA->Reset();
    _acmA->RegisterVADCallback(_vadCallbackA);

    if (!_randomTest) {
      if (_verbose) {
        fprintf(stdout, "DTX %3s, VAD %3s, Mode %d", dtxEnabled ? "ON" : "OFF",
                vadEnabled ? "ON" : "OFF", (int) vadMode);
        Wait(5000);
        fprintf(stdout, " => bit-rate %3.0f kbps\n", _channel_A2B->BitRate());
      } else {
        Wait(5000);
        fprintf(stdout, "DTX %3s, VAD %3s, Mode %d => bit-rate %3.0f kbps\n",
                dtxEnabled ? "ON" : "OFF", vadEnabled ? "ON" : "OFF",
                (int) vadMode, _channel_A2B->BitRate());
      }
      _vadCallbackA->PrintFrameTypes();
    }

    if (dtxEnabled != _sendDTXA) {
      fprintf(stderr, ">>>   Error Enabling DTX    <<<\n");
    }
    if ((vadEnabled != _sendVADA) && (!dtxEnabled)) {
      fprintf(stderr, ">>>   Error Enabling VAD    <<<\n");
    }
    if ((vadMode != _sendVADModeA) && vadEnabled) {
      fprintf(stderr, ">>>   Error setting VAD-mode    <<<\n");
    }
  } else {
    _acmB->VAD(&dtxEnabled, &vadEnabled, &vadMode);

    _acmB->RegisterVADCallback(NULL);
    _vadCallbackB->Reset();
    _acmB->RegisterVADCallback(_vadCallbackB);

    if (!_randomTest) {
      if (_verbose) {
        fprintf(stdout, "DTX %3s, VAD %3s, Mode %d", dtxEnabled ? "ON" : "OFF",
                vadEnabled ? "ON" : "OFF", (int) vadMode);
        Wait(5000);
        fprintf(stdout, " => bit-rate %3.0f kbps\n", _channel_B2A->BitRate());
      } else {
        Wait(5000);
        fprintf(stdout, "DTX %3s, VAD %3s, Mode %d => bit-rate %3.0f kbps\n",
                dtxEnabled ? "ON" : "OFF", vadEnabled ? "ON" : "OFF",
                (int) vadMode, _channel_B2A->BitRate());
      }
      _vadCallbackB->PrintFrameTypes();
    }

    if (dtxEnabled != _sendDTXB) {
      fprintf(stderr, ">>>   Error Enabling DTX    <<<\n");
    }
    if ((vadEnabled != _sendVADB) && (!dtxEnabled)) {
      fprintf(stderr, ">>>   Error Enabling VAD    <<<\n");
    }
    if ((vadMode != _sendVADModeB) && vadEnabled) {
      fprintf(stderr, ">>>   Error setting VAD-mode    <<<\n");
    }
  }
}

// Set Min delay, get delay, playout timestamp
void APITest::TestDelay(char side) {
  AudioCodingModule* myACM;
  Channel* myChannel;
  int32_t* myMinDelay;
  EventWrapper* myEvent = EventWrapper::Create();

  uint32_t inTimestamp = 0;
  uint32_t outTimestamp = 0;
  double estimDelay = 0;

  double averageEstimDelay = 0;
  double averageDelay = 0;

  CircularBuffer estimDelayCB(100);
  estimDelayCB.SetArithMean(true);

  if (side == 'A') {
    myACM = _acmA.get();
    myChannel = _channel_B2A;
    myMinDelay = &_minDelayA;
  } else {
    myACM = _acmB.get();
    myChannel = _channel_A2B;
    myMinDelay = &_minDelayB;
  }

  CHECK_ERROR_MT(myACM->SetMinimumPlayoutDelay(*myMinDelay));

  inTimestamp = myChannel->LastInTimestamp();
  CHECK_ERROR_MT(myACM->PlayoutTimestamp(&outTimestamp));

  if (!_randomTest) {
    myEvent->StartTimer(true, 30);
    int n = 0;
    int settlePoint = 5000;
    while (n < settlePoint + 400) {
      myEvent->Wait(1000);

      inTimestamp = myChannel->LastInTimestamp();
      CHECK_ERROR_MT(myACM->PlayoutTimestamp(&outTimestamp));

      //std::cout << outTimestamp << std::endl << std::flush;
      estimDelay = (double) ((uint32_t)(inTimestamp - outTimestamp))
          / ((double) myACM->ReceiveFrequency() / 1000.0);

      estimDelayCB.Update(estimDelay);

      estimDelayCB.ArithMean(averageEstimDelay);
      //printf("\n %6.1f \n", estimDelay);
      //std::cout << " " << std::flush;

      if (_verbose) {
        fprintf(stdout,
                "\rExpected: %4d,    retreived: %6.1f,   measured: %6.1f",
                *myMinDelay, averageDelay, averageEstimDelay);
        std::cout << " " << std::flush;
      }
      if ((averageDelay > *myMinDelay) && (n < settlePoint)) {
        settlePoint = n;
      }
      n++;
    }
    myEvent->StopTimer();
  }

  if ((!_verbose) && (!_randomTest)) {
    fprintf(stdout, "\nExpected: %4d,    retreived: %6.1f,   measured: %6.1f",
            *myMinDelay, averageDelay, averageEstimDelay);
  }

  *myMinDelay = (rand() % 1000) + 1;

  NetworkStatistics networkStat;
  CHECK_ERROR_MT(myACM->GetNetworkStatistics(&networkStat));

  if (!_randomTest) {
    fprintf(stdout, "\n\nJitter Statistics at Side %c\n", side);
    fprintf(stdout, "--------------------------------------\n");
    fprintf(stdout, "buffer-size............. %d\n",
            networkStat.currentBufferSize);
    fprintf(stdout, "Preferred buffer-size... %d\n",
            networkStat.preferredBufferSize);
    fprintf(stdout, "Peaky jitter mode........%d\n",
            networkStat.jitterPeaksFound);
    fprintf(stdout, "packet-size rate........ %d\n",
            networkStat.currentPacketLossRate);
    fprintf(stdout, "discard rate............ %d\n",
            networkStat.currentDiscardRate);
    fprintf(stdout, "expand rate............. %d\n",
            networkStat.currentExpandRate);
    fprintf(stdout, "speech expand rate...... %d\n",
            networkStat.currentSpeechExpandRate);
    fprintf(stdout, "Preemptive rate......... %d\n",
            networkStat.currentPreemptiveRate);
    fprintf(stdout, "Accelerate rate......... %d\n",
            networkStat.currentAccelerateRate);
    fprintf(stdout, "Secondary decoded rate.. %d\n",
            networkStat.currentSecondaryDecodedRate);
    fprintf(stdout, "Clock-drift............. %d\n", networkStat.clockDriftPPM);
    fprintf(stdout, "Mean waiting time....... %d\n",
            networkStat.meanWaitingTimeMs);
    fprintf(stdout, "Median waiting time..... %d\n",
            networkStat.medianWaitingTimeMs);
    fprintf(stdout, "Min waiting time........ %d\n",
            networkStat.minWaitingTimeMs);
    fprintf(stdout, "Max waiting time........ %d\n",
            networkStat.maxWaitingTimeMs);
  }

  CHECK_ERROR_MT(myACM->SetMinimumPlayoutDelay(*myMinDelay));

  if (!_randomTest) {
    myEvent->Wait(500);
    fprintf(stdout, "\n");
    fprintf(stdout, "\n");
  }
  delete myEvent;
}

// Unregister a codec & register again.
void APITest::TestRegisteration(char sendSide) {
  AudioCodingModule* sendACM;
  AudioCodingModule* receiveACM;
  bool* thereIsDecoder;
  EventWrapper* myEvent = EventWrapper::Create();

  if (!_randomTest) {
    fprintf(stdout, "\n\n");
    fprintf(stdout,
            "---------------------------------------------------------\n");
    fprintf(stdout, "           Unregister/register Receive Codec\n");
    fprintf(stdout,
            "---------------------------------------------------------\n");
  }

  switch (sendSide) {
    case 'A': {
      sendACM = _acmA.get();
      receiveACM = _acmB.get();
      thereIsDecoder = &_thereIsDecoderB;
      break;
    }
    case 'B': {
      sendACM = _acmB.get();
      receiveACM = _acmA.get();
      thereIsDecoder = &_thereIsDecoderA;
      break;
    }
    default:
      fprintf(stderr, "Invalid sender-side in TestRegistration(%c)\n",
              sendSide);
      exit(-1);
  }

  CodecInst myCodec;
  if (sendACM->SendCodec(&myCodec) < 0) {
    AudioCodingModule::Codec(_codecCntrA, &myCodec);
  }

  if (!_randomTest) {
    fprintf(stdout, "Unregistering reveive codec, NO AUDIO.\n");
    fflush (stdout);
  }
  {
    WriteLockScoped wl(_apiTestRWLock);
    *thereIsDecoder = false;
  }
  //myEvent->Wait(20);
  CHECK_ERROR_MT(receiveACM->UnregisterReceiveCodec(myCodec.pltype));
  Wait(1000);

  int currentPayload = myCodec.pltype;

  if (!FixedPayloadTypeCodec(myCodec.plname)) {
    int32_t i;
    for (i = 0; i < 32; i++) {
      if (!_payloadUsed[i]) {
        if (!_randomTest) {
          fprintf(stdout,
                  "Register receive codec with new Payload, AUDIO BACK.\n");
        }
        //myCodec.pltype = i + 96;
        //CHECK_ERROR_MT(receiveACM->RegisterReceiveCodec(myCodec));
        //CHECK_ERROR_MT(sendACM->RegisterSendCodec(myCodec));
        //myEvent->Wait(20);
        //{
        //    WriteLockScoped wl(_apiTestRWLock);
        //    *thereIsDecoder = true;
        //}
        Wait(1000);

        if (!_randomTest) {
          fprintf(stdout, "Unregistering reveive codec, NO AUDIO.\n");
        }
        //{
        //    WriteLockScoped wl(_apiTestRWLock);
        //    *thereIsDecoder = false;
        //}
        //myEvent->Wait(20);
        //CHECK_ERROR_MT(receiveACM->UnregisterReceiveCodec(myCodec.pltype));
        Wait(1000);

        myCodec.pltype = currentPayload;
        if (!_randomTest) {
          fprintf(stdout,
                  "Register receive codec with default Payload, AUDIO BACK.\n");
          fflush (stdout);
        }
        CHECK_ERROR_MT(receiveACM->RegisterReceiveCodec(myCodec));
        //CHECK_ERROR_MT(sendACM->RegisterSendCodec(myCodec));
        myEvent->Wait(20);
        {
          WriteLockScoped wl(_apiTestRWLock);
          *thereIsDecoder = true;
        }
        Wait(1000);

        break;
      }
    }
    if (i == 32) {
      CHECK_ERROR_MT(receiveACM->RegisterReceiveCodec(myCodec));
      {
        WriteLockScoped wl(_apiTestRWLock);
        *thereIsDecoder = true;
      }
    }
  } else {
    if (!_randomTest) {
      fprintf(stdout,
              "Register receive codec with fixed Payload, AUDIO BACK.\n");
      fflush (stdout);
    }
    CHECK_ERROR_MT(receiveACM->RegisterReceiveCodec(myCodec));
    //CHECK_ERROR_MT(receiveACM->UnregisterReceiveCodec(myCodec.pltype));
    //CHECK_ERROR_MT(receiveACM->RegisterReceiveCodec(myCodec));
    myEvent->Wait(20);
    {
      WriteLockScoped wl(_apiTestRWLock);
      *thereIsDecoder = true;
    }
  }
  delete myEvent;
  if (!_randomTest) {
    fprintf(stdout,
            "---------------------------------------------------------\n");
  }
}

// Playout Mode, background noise mode.
// Receiver Frequency, playout frequency.
void APITest::TestPlayout(char receiveSide) {
  AudioCodingModule* receiveACM;
  AudioPlayoutMode* playoutMode = NULL;
  switch (receiveSide) {
    case 'A': {
      receiveACM = _acmA.get();
      playoutMode = &_playoutModeA;
      break;
    }
    case 'B': {
      receiveACM = _acmB.get();
      playoutMode = &_playoutModeB;
      break;
    }
    default:
      receiveACM = _acmA.get();
  }

  int32_t receiveFreqHz = receiveACM->ReceiveFrequency();
  int32_t playoutFreqHz = receiveACM->PlayoutFrequency();

  CHECK_ERROR_MT(receiveFreqHz);
  CHECK_ERROR_MT(playoutFreqHz);


  char playoutString[25];
  switch (*playoutMode) {
    case voice: {
      *playoutMode = fax;
      strncpy(playoutString, "FAX", 25);
      break;
    }
    case fax: {
      *playoutMode = streaming;
      strncpy(playoutString, "Streaming", 25);
      break;
    }
    case streaming: {
      *playoutMode = voice;
      strncpy(playoutString, "Voice", 25);
      break;
    }
    default:
      *playoutMode = voice;
      strncpy(playoutString, "Voice", 25);
  }
  CHECK_ERROR_MT(receiveACM->SetPlayoutMode(*playoutMode));
  playoutString[24] = '\0';

  if (!_randomTest) {
    fprintf(stdout, "\n");
    fprintf(stdout, "In Side %c\n", receiveSide);
    fprintf(stdout, "---------------------------------\n");
    fprintf(stdout, "Receive Frequency....... %d Hz\n", receiveFreqHz);
    fprintf(stdout, "Playout Frequency....... %d Hz\n", playoutFreqHz);
    fprintf(stdout, "Audio Playout Mode...... %s\n", playoutString);
  }
}

void APITest::TestSendVAD(char side) {
  if (_randomTest) {
    return;
  }

  bool* vad;
  bool* dtx;
  ACMVADMode* mode;
  Channel* myChannel;
  AudioCodingModule* myACM;

  CodecInst myCodec;
  if (!_randomTest) {
    fprintf(stdout, "\n\n");
    fprintf(stdout, "-----------------------------------------------\n");
    fprintf(stdout, "                Test VAD API\n");
    fprintf(stdout, "-----------------------------------------------\n");
  }

  if (side == 'A') {
    AudioCodingModule::Codec(_codecCntrA, &myCodec);
    vad = &_sendVADA;
    dtx = &_sendDTXA;
    mode = &_sendVADModeA;
    myChannel = _channel_A2B;
    myACM = _acmA.get();
  } else {
    AudioCodingModule::Codec(_codecCntrB, &myCodec);
    vad = &_sendVADB;
    dtx = &_sendDTXB;
    mode = &_sendVADModeB;
    myChannel = _channel_B2A;
    myACM = _acmB.get();
  }

  CheckVADStatus(side);
  if (!_randomTest) {
    fprintf(stdout, "\n\n");
  }

  switch (*mode) {
    case VADNormal:
      *vad = true;
      *dtx = true;
      *mode = VADAggr;
      break;
    case VADLowBitrate:
      *vad = true;
      *dtx = true;
      *mode = VADVeryAggr;
      break;
    case VADAggr:
      *vad = true;
      *dtx = true;
      *mode = VADLowBitrate;
      break;
    case VADVeryAggr:
      *vad = false;
      *dtx = false;
      *mode = VADNormal;
      break;
    default:
      *mode = VADNormal;
  }

  *dtx = (myCodec.plfreq == 32000) ? false : *dtx;

  CHECK_ERROR_MT(myACM->SetVAD(*dtx, *vad, *mode));
  myChannel->ResetStats();

  CheckVADStatus(side);
  if (!_randomTest) {
    fprintf(stdout, "\n");
    fprintf(stdout, "-----------------------------------------------\n");
  }

  // Fault Test
  CHECK_PROTECTED_MT(myACM->SetVAD(false, true, (ACMVADMode) - 1));
  CHECK_PROTECTED_MT(myACM->SetVAD(false, true, (ACMVADMode) 4));

}

void APITest::CurrentCodec(char side) {
  CodecInst myCodec;
  if (side == 'A') {
    _acmA->SendCodec(&myCodec);
  } else {
    _acmB->SendCodec(&myCodec);
  }

  if (!_randomTest) {
    fprintf(stdout, "\n\n");
    fprintf(stdout, "Send codec in Side A\n");
    fprintf(stdout, "----------------------------\n");
    fprintf(stdout, "Name................. %s\n", myCodec.plname);
    fprintf(stdout, "Sampling Frequency... %d\n", myCodec.plfreq);
    fprintf(stdout, "Rate................. %d\n", myCodec.rate);
    fprintf(stdout, "Payload-type......... %d\n", myCodec.pltype);
    fprintf(stdout, "Packet-size.......... %d\n", myCodec.pacsize);
  }

  Wait(100);
}

void APITest::ChangeCodec(char side) {
  CodecInst myCodec;
  AudioCodingModule* myACM;
  uint8_t* codecCntr;
  bool* thereIsEncoder;
  bool* vad;
  bool* dtx;
  ACMVADMode* mode;
  Channel* myChannel;
  // Reset and Wait
  if (!_randomTest) {
    fprintf(stdout, "Reset Encoder Side A \n");
  }
  if (side == 'A') {
    myACM = _acmA.get();
    codecCntr = &_codecCntrA;
    {
      WriteLockScoped wl(_apiTestRWLock);
      thereIsEncoder = &_thereIsEncoderA;
    }
    vad = &_sendVADA;
    dtx = &_sendDTXA;
    mode = &_sendVADModeA;
    myChannel = _channel_A2B;
  } else {
    myACM = _acmB.get();
    codecCntr = &_codecCntrB;
    {
      WriteLockScoped wl(_apiTestRWLock);
      thereIsEncoder = &_thereIsEncoderB;
    }
    vad = &_sendVADB;
    dtx = &_sendDTXB;
    mode = &_sendVADModeB;
    myChannel = _channel_B2A;
  }

  myACM->ResetEncoder();
  Wait(100);

  // Register the next codec
  do {
    *codecCntr =
        (*codecCntr < AudioCodingModule::NumberOfCodecs() - 1) ?
            (*codecCntr + 1) : 0;

    if (*codecCntr == 0) {
      //printf("Initialize Sender Side A \n");
      {
        WriteLockScoped wl(_apiTestRWLock);
        *thereIsEncoder = false;
      }
      // After Initialization CN is lost, re-register them
      if (AudioCodingModule::Codec("CN", &myCodec, 8000, 1) >= 0) {
        CHECK_ERROR_MT(myACM->RegisterSendCodec(myCodec));
      }
      if (AudioCodingModule::Codec("CN", &myCodec, 16000, 1) >= 0) {
        CHECK_ERROR_MT(myACM->RegisterSendCodec(myCodec));
      }
      // VAD & DTX are disabled after initialization
      *vad = false;
      *dtx = false;
      _writeToFile = false;
    }

    AudioCodingModule::Codec(*codecCntr, &myCodec);
  } while (!STR_CASE_CMP(myCodec.plname, "CN")
      || !STR_CASE_CMP(myCodec.plname, "telephone-event")
      || !STR_CASE_CMP(myCodec.plname, "RED"));

  if (!_randomTest) {
    fprintf(stdout,"\n=====================================================\n");
    fprintf(stdout, "      Registering New Codec %s, %d kHz, %d kbps\n",
            myCodec.plname, myCodec.plfreq / 1000, myCodec.rate / 1000);
  }
  //std::cout<< std::flush;

  // NO DTX for supe-wideband codec at this point
  if (myCodec.plfreq == 32000) {
    *dtx = false;
    CHECK_ERROR_MT(myACM->SetVAD(*dtx, *vad, *mode));

  }

  CHECK_ERROR_MT(myACM->RegisterSendCodec(myCodec));
  myChannel->ResetStats();
  {
    WriteLockScoped wl(_apiTestRWLock);
    *thereIsEncoder = true;
  }
  Wait(500);
}

}  // namespace webrtc
