/*
 *  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 "modules/audio_coding/test/EncodeDecodeTest.h"

#include <memory>
#include <sstream>
#include <stdio.h>
#include <stdlib.h>

#include "common_types.h"  // NOLINT(build/include)
#include "modules/audio_coding/codecs/audio_format_conversion.h"
#include "modules/audio_coding/include/audio_coding_module.h"
#include "modules/audio_coding/test/utility.h"
#include "system_wrappers/include/trace.h"
#include "test/gtest.h"
#include "test/testsupport/fileutils.h"

namespace webrtc {

TestPacketization::TestPacketization(RTPStream *rtpStream, uint16_t frequency)
    : _rtpStream(rtpStream),
      _frequency(frequency),
      _seqNo(0) {
}

TestPacketization::~TestPacketization() {
}

int32_t TestPacketization::SendData(
    const FrameType /* frameType */, const uint8_t payloadType,
    const uint32_t timeStamp, const uint8_t* payloadData,
    const size_t payloadSize,
    const RTPFragmentationHeader* /* fragmentation */) {
  _rtpStream->Write(payloadType, timeStamp, _seqNo++, payloadData, payloadSize,
                    _frequency);
  return 1;
}

Sender::Sender()
    : _acm(NULL),
      _pcmFile(),
      _audioFrame(),
      _packetization(NULL) {
}

void Sender::Setup(AudioCodingModule *acm, RTPStream *rtpStream,
                   std::string in_file_name, int sample_rate, size_t channels) {
  struct CodecInst sendCodec;
  int noOfCodecs = acm->NumberOfCodecs();
  int codecNo;

  // Open input file
  const std::string file_name = webrtc::test::ResourcePath(in_file_name, "pcm");
  _pcmFile.Open(file_name, sample_rate, "rb");
  if (channels == 2) {
    _pcmFile.ReadStereo(true);
  }
  // Set test length to 500 ms (50 blocks of 10 ms each).
  _pcmFile.SetNum10MsBlocksToRead(50);
  // Fast-forward 1 second (100 blocks) since the file starts with silence.
  _pcmFile.FastForward(100);

  // Set the codec for the current test.
  if ((testMode == 0) || (testMode == 1)) {
    // Set the codec id.
    codecNo = codeId;
  } else {
    // Choose codec on command line.
    printf("List of supported codec.\n");
    for (int n = 0; n < noOfCodecs; n++) {
      EXPECT_EQ(0, acm->Codec(n, &sendCodec));
      printf("%d %s\n", n, sendCodec.plname);
    }
    printf("Choose your codec:");
    ASSERT_GT(scanf("%d", &codecNo), 0);
  }

  EXPECT_EQ(0, acm->Codec(codecNo, &sendCodec));

  sendCodec.channels = channels;

  EXPECT_EQ(0, acm->RegisterSendCodec(sendCodec));
  _packetization = new TestPacketization(rtpStream, sendCodec.plfreq);
  EXPECT_EQ(0, acm->RegisterTransportCallback(_packetization));

  _acm = acm;
}

void Sender::Teardown() {
  _pcmFile.Close();
  delete _packetization;
}

bool Sender::Add10MsData() {
  if (!_pcmFile.EndOfFile()) {
    EXPECT_GT(_pcmFile.Read10MsData(_audioFrame), 0);
    int32_t ok = _acm->Add10MsData(_audioFrame);
    EXPECT_GE(ok, 0);
    return ok >= 0 ? true : false;
  }
  return false;
}

void Sender::Run() {
  while (true) {
    if (!Add10MsData()) {
      break;
    }
  }
}

Receiver::Receiver()
    : _playoutLengthSmpls(WEBRTC_10MS_PCM_AUDIO),
      _payloadSizeBytes(MAX_INCOMING_PAYLOAD) {
}

void Receiver::Setup(AudioCodingModule *acm, RTPStream *rtpStream,
                     std::string out_file_name, size_t channels) {
  struct CodecInst recvCodec = CodecInst();
  int noOfCodecs;
  EXPECT_EQ(0, acm->InitializeReceiver());

  noOfCodecs = acm->NumberOfCodecs();
  for (int i = 0; i < noOfCodecs; i++) {
    EXPECT_EQ(0, acm->Codec(i, &recvCodec));
    if (recvCodec.channels == channels)
      EXPECT_EQ(true, acm->RegisterReceiveCodec(recvCodec.pltype,
                                                CodecInstToSdp(recvCodec)));
    // Forces mono/stereo for Opus.
    if (!strcmp(recvCodec.plname, "opus")) {
      recvCodec.channels = channels;
      EXPECT_EQ(true, acm->RegisterReceiveCodec(recvCodec.pltype,
                                                CodecInstToSdp(recvCodec)));
    }
  }

  int playSampFreq;
  std::string file_name;
  std::stringstream file_stream;
  file_stream << webrtc::test::OutputPath() << out_file_name
      << static_cast<int>(codeId) << ".pcm";
  file_name = file_stream.str();
  _rtpStream = rtpStream;

  if (testMode == 1) {
    playSampFreq = recvCodec.plfreq;
    _pcmFile.Open(file_name, recvCodec.plfreq, "wb+");
  } else if (testMode == 0) {
    playSampFreq = 32000;
    _pcmFile.Open(file_name, 32000, "wb+");
  } else {
    printf("\nValid output frequencies:\n");
    printf("8000\n16000\n32000\n-1,");
    printf("which means output frequency equal to received signal frequency");
    printf("\n\nChoose output sampling frequency: ");
    ASSERT_GT(scanf("%d", &playSampFreq), 0);
    file_name = webrtc::test::OutputPath() + out_file_name + ".pcm";
    _pcmFile.Open(file_name, playSampFreq, "wb+");
  }

  _realPayloadSizeBytes = 0;
  _playoutBuffer = new int16_t[WEBRTC_10MS_PCM_AUDIO];
  _frequency = playSampFreq;
  _acm = acm;
  _firstTime = true;
}

void Receiver::Teardown() {
  delete[] _playoutBuffer;
  _pcmFile.Close();
  if (testMode > 1) {
    Trace::ReturnTrace();
  }
}

bool Receiver::IncomingPacket() {
  if (!_rtpStream->EndOfFile()) {
    if (_firstTime) {
      _firstTime = false;
      _realPayloadSizeBytes = _rtpStream->Read(&_rtpInfo, _incomingPayload,
                                               _payloadSizeBytes, &_nextTime);
      if (_realPayloadSizeBytes == 0) {
        if (_rtpStream->EndOfFile()) {
          _firstTime = true;
          return true;
        } else {
          return false;
        }
      }
    }

    EXPECT_EQ(0, _acm->IncomingPacket(_incomingPayload, _realPayloadSizeBytes,
                                      _rtpInfo));
    _realPayloadSizeBytes = _rtpStream->Read(&_rtpInfo, _incomingPayload,
                                             _payloadSizeBytes, &_nextTime);
    if (_realPayloadSizeBytes == 0 && _rtpStream->EndOfFile()) {
      _firstTime = true;
    }
  }
  return true;
}

bool Receiver::PlayoutData() {
  AudioFrame audioFrame;
  bool muted;
  int32_t ok = _acm->PlayoutData10Ms(_frequency, &audioFrame, &muted);
  if (muted) {
    ADD_FAILURE();
    return false;
  }
  EXPECT_EQ(0, ok);
  if (ok < 0){
    return false;
  }
  if (_playoutLengthSmpls == 0) {
    return false;
  }
  _pcmFile.Write10MsData(audioFrame.data(),
      audioFrame.samples_per_channel_ * audioFrame.num_channels_);
  return true;
}

void Receiver::Run() {
  uint8_t counter500Ms = 50;
  uint32_t clock = 0;

  while (counter500Ms > 0) {
    if (clock == 0 || clock >= _nextTime) {
      EXPECT_TRUE(IncomingPacket());
      if (clock == 0) {
        clock = _nextTime;
      }
    }
    if ((clock % 10) == 0) {
      if (!PlayoutData()) {
        clock++;
        continue;
      }
    }
    if (_rtpStream->EndOfFile()) {
      counter500Ms--;
    }
    clock++;
  }
}

EncodeDecodeTest::EncodeDecodeTest() {
  _testMode = 2;
  Trace::CreateTrace();
  Trace::SetTraceFile(
      (webrtc::test::OutputPath() + "acm_encdec_trace.txt").c_str());
}

EncodeDecodeTest::EncodeDecodeTest(int testMode) {
  //testMode == 0 for autotest
  //testMode == 1 for testing all codecs/parameters
  //testMode > 1 for specific user-input test (as it was used before)
  _testMode = testMode;
  if (_testMode != 0) {
    Trace::CreateTrace();
    Trace::SetTraceFile(
        (webrtc::test::OutputPath() + "acm_encdec_trace.txt").c_str());
  }
}

void EncodeDecodeTest::Perform() {
  int numCodecs = 1;
  int codePars[3];  // Frequency, packet size, rate.
  int numPars[52];  // Number of codec parameters sets (freq, pacsize, rate)
                    // to test, for a given codec.

  codePars[0] = 0;
  codePars[1] = 0;
  codePars[2] = 0;

  std::unique_ptr<AudioCodingModule> acm(AudioCodingModule::Create());
  struct CodecInst sendCodecTmp;
  numCodecs = acm->NumberOfCodecs();

  if (_testMode != 2) {
    for (int n = 0; n < numCodecs; n++) {
      EXPECT_EQ(0, acm->Codec(n, &sendCodecTmp));
      if (STR_CASE_CMP(sendCodecTmp.plname, "telephone-event") == 0) {
        numPars[n] = 0;
      } else if (STR_CASE_CMP(sendCodecTmp.plname, "cn") == 0) {
        numPars[n] = 0;
      } else if (STR_CASE_CMP(sendCodecTmp.plname, "red") == 0) {
        numPars[n] = 0;
      } else if (sendCodecTmp.channels == 2) {
        numPars[n] = 0;
      } else {
        numPars[n] = 1;
      }
    }
  } else {
    numCodecs = 1;
    numPars[0] = 1;
  }

  _receiver.testMode = _testMode;

  // Loop over all mono codecs:
  for (int codeId = 0; codeId < numCodecs; codeId++) {
    // Only encode using real mono encoders, not telephone-event and cng.
    for (int loopPars = 1; loopPars <= numPars[codeId]; loopPars++) {
      // Encode all data to file.
      std::string fileName = EncodeToFile(1, codeId, codePars, _testMode);

      RTPFile rtpFile;
      rtpFile.Open(fileName.c_str(), "rb");

      _receiver.codeId = codeId;

      rtpFile.ReadHeader();
      _receiver.Setup(acm.get(), &rtpFile, "encodeDecode_out", 1);
      _receiver.Run();
      _receiver.Teardown();
      rtpFile.Close();
    }
  }

  // End tracing.
  if (_testMode == 1) {
    Trace::ReturnTrace();
  }
}

std::string EncodeDecodeTest::EncodeToFile(int fileType,
                                           int codeId,
                                           int* codePars,
                                           int testMode) {
  std::unique_ptr<AudioCodingModule> acm(AudioCodingModule::Create());
  RTPFile rtpFile;
  std::string fileName = webrtc::test::TempFilename(webrtc::test::OutputPath(),
                                                    "encode_decode_rtp");
  rtpFile.Open(fileName.c_str(), "wb+");
  rtpFile.WriteHeader();

  // Store for auto_test and logging.
  _sender.testMode = testMode;
  _sender.codeId = codeId;

  _sender.Setup(acm.get(), &rtpFile, "audio_coding/testfile32kHz", 32000, 1);
  if (acm->SendCodec()) {
    _sender.Run();
  }
  _sender.Teardown();
  rtpFile.Close();

  return fileName;
}

}  // namespace webrtc