/*
 *  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 "testsupport/fileutils.h"
#include "voice_engine/test/auto_test/fixtures/after_streaming_fixture.h"
#include "voice_engine/test/auto_test/voe_standard_test.h"

class RxCallback : public webrtc::VoERxVadCallback {
 public:
  RxCallback() :
    vad_decision(-1) {
  }

  virtual void OnRxVad(int, int vadDecision) {
    char msg[128];
    sprintf(msg, "RX VAD detected decision %d \n", vadDecision);
    TEST_LOG("%s", msg);
    vad_decision = vadDecision;
  }

  int vad_decision;
};

class AudioProcessingTest : public AfterStreamingFixture {
 protected:
  // Note: Be careful with this one, it is used in the
  // Android / iPhone part too.
  void TryEnablingAgcWithMode(webrtc::AgcModes agc_mode_to_set) {
    EXPECT_EQ(0, voe_apm_->SetAgcStatus(true, agc_mode_to_set));

    bool agc_enabled = false;
    webrtc::AgcModes agc_mode = webrtc::kAgcDefault;

    EXPECT_EQ(0, voe_apm_->GetAgcStatus(agc_enabled, agc_mode));
    EXPECT_TRUE(agc_enabled);
    EXPECT_EQ(agc_mode_to_set, agc_mode);
  }

  void TryEnablingRxAgcWithMode(webrtc::AgcModes agc_mode_to_set) {
    EXPECT_EQ(0, voe_apm_->SetRxAgcStatus(channel_, true, agc_mode_to_set));

    bool rx_agc_enabled = false;
    webrtc::AgcModes agc_mode = webrtc::kAgcDefault;

    EXPECT_EQ(0, voe_apm_->GetRxAgcStatus(channel_, rx_agc_enabled, agc_mode));
    EXPECT_TRUE(rx_agc_enabled);
    EXPECT_EQ(agc_mode_to_set, agc_mode);
  }

  // EC modes can map to other EC modes, so we have a separate parameter
  // for what we expect the EC mode to be set to.
  void TryEnablingEcWithMode(webrtc::EcModes ec_mode_to_set,
                             webrtc::EcModes expected_mode) {
    EXPECT_EQ(0, voe_apm_->SetEcStatus(true, ec_mode_to_set));

    bool ec_enabled = true;
    webrtc::EcModes ec_mode = webrtc::kEcDefault;

    EXPECT_EQ(0, voe_apm_->GetEcStatus(ec_enabled, ec_mode));

    EXPECT_EQ(expected_mode, ec_mode);
  }

  // Here, the CNG mode will be expected to be on or off depending on the mode.
  void TryEnablingAecmWithMode(webrtc::AecmModes aecm_mode_to_set,
                               bool cng_enabled_to_set) {
    EXPECT_EQ(0, voe_apm_->SetAecmMode(aecm_mode_to_set, cng_enabled_to_set));

    bool cng_enabled = false;
    webrtc::AecmModes aecm_mode = webrtc::kAecmEarpiece;

    voe_apm_->GetAecmMode(aecm_mode, cng_enabled);

    EXPECT_EQ(cng_enabled_to_set, cng_enabled);
    EXPECT_EQ(aecm_mode_to_set, aecm_mode);
  }

  void TryEnablingNsWithMode(webrtc::NsModes ns_mode_to_set,
                             webrtc::NsModes expected_ns_mode) {
    EXPECT_EQ(0, voe_apm_->SetNsStatus(true, ns_mode_to_set));

    bool ns_status = true;
    webrtc::NsModes ns_mode = webrtc::kNsDefault;
    EXPECT_EQ(0, voe_apm_->GetNsStatus(ns_status, ns_mode));

    EXPECT_TRUE(ns_status);
    EXPECT_EQ(expected_ns_mode, ns_mode);
  }

  void TryEnablingRxNsWithMode(webrtc::NsModes ns_mode_to_set,
                               webrtc::NsModes expected_ns_mode) {
    EXPECT_EQ(0, voe_apm_->SetRxNsStatus(channel_, true, ns_mode_to_set));

    bool ns_status = true;
    webrtc::NsModes ns_mode = webrtc::kNsDefault;
    EXPECT_EQ(0, voe_apm_->GetRxNsStatus(channel_, ns_status, ns_mode));

    EXPECT_TRUE(ns_status);
    EXPECT_EQ(expected_ns_mode, ns_mode);
  }

  void TryDetectingSilence() {
    // Here, speech is running. Shut down speech.
    EXPECT_EQ(0, voe_codec_->SetVADStatus(channel_, true));
    EXPECT_EQ(0, voe_volume_control_->SetInputMute(channel_, true));
    EXPECT_EQ(0, voe_file_->StopPlayingFileAsMicrophone(channel_));

    // We should detect the silence after a short time.
    Sleep(50);
    for (int i = 0; i < 25; i++) {
      EXPECT_EQ(0, voe_apm_->VoiceActivityIndicator(channel_));
      Sleep(10);
    }
  }

  void TryDetectingSpeechAfterSilence() {
    // Re-enable speech.
    RestartFakeMicrophone();
    EXPECT_EQ(0, voe_codec_->SetVADStatus(channel_, false));
    EXPECT_EQ(0, voe_volume_control_->SetInputMute(channel_, false));

    // We should detect the speech after a short time.
    for (int i = 0; i < 50; i++) {
      if (voe_apm_->VoiceActivityIndicator(channel_) == 1) {
        return;
      }
      Sleep(10);
    }

    ADD_FAILURE() << "Failed to detect speech within 500 ms.";
  }
};

#if !defined(WEBRTC_IOS) && !defined(WEBRTC_ANDROID)

TEST_F(AudioProcessingTest, AgcIsOnByDefault) {
  bool agc_enabled = false;
  webrtc::AgcModes agc_mode = webrtc::kAgcAdaptiveAnalog;

  EXPECT_EQ(0, voe_apm_->GetAgcStatus(agc_enabled, agc_mode));
  EXPECT_TRUE(agc_enabled);
  EXPECT_EQ(webrtc::kAgcAdaptiveAnalog, agc_mode);
}

TEST_F(AudioProcessingTest, CanEnableAgcWithAllModes) {
  TryEnablingAgcWithMode(webrtc::kAgcAdaptiveDigital);
  TryEnablingAgcWithMode(webrtc::kAgcAdaptiveAnalog);
  TryEnablingAgcWithMode(webrtc::kAgcFixedDigital);
}

TEST_F(AudioProcessingTest, EcIsDisabledAndAecIsDefaultEcMode) {
  bool ec_enabled = true;
  webrtc::EcModes ec_mode = webrtc::kEcDefault;

  EXPECT_EQ(0, voe_apm_->GetEcStatus(ec_enabled, ec_mode));
  EXPECT_FALSE(ec_enabled);
  EXPECT_EQ(webrtc::kEcAec, ec_mode);
}

TEST_F(AudioProcessingTest, EnablingEcAecShouldEnableEcAec) {
  TryEnablingEcWithMode(webrtc::kEcAec, webrtc::kEcAec);
}

TEST_F(AudioProcessingTest, EnablingEcConferenceShouldEnableEcAec) {
  TryEnablingEcWithMode(webrtc::kEcConference, webrtc::kEcAec);
}

TEST_F(AudioProcessingTest, EcModeIsPreservedWhenEcIsTurnedOff) {
  TryEnablingEcWithMode(webrtc::kEcConference, webrtc::kEcAec);

  EXPECT_EQ(0, voe_apm_->SetEcStatus(false));

  bool ec_enabled = true;
  webrtc::EcModes ec_mode = webrtc::kEcDefault;
  EXPECT_EQ(0, voe_apm_->GetEcStatus(ec_enabled, ec_mode));

  EXPECT_FALSE(ec_enabled);
  EXPECT_EQ(webrtc::kEcAec, ec_mode);
}

TEST_F(AudioProcessingTest, CanEnableAndDisableEcModeSeveralTimesInARow) {
  for (int i = 0; i < 10; i++) {
    EXPECT_EQ(0, voe_apm_->SetEcStatus(true));
    EXPECT_EQ(0, voe_apm_->SetEcStatus(false));
  }

  bool ec_enabled = true;
  webrtc::EcModes ec_mode = webrtc::kEcDefault;
  EXPECT_EQ(0, voe_apm_->GetEcStatus(ec_enabled, ec_mode));

  EXPECT_FALSE(ec_enabled);
  EXPECT_EQ(webrtc::kEcAec, ec_mode);
}

// TODO(phoglund): Reenable below test when it's no longer flaky.
TEST_F(AudioProcessingTest, DISABLED_TestVoiceActivityDetectionWithObserver) {
  RxCallback rx_callback;
  EXPECT_EQ(0, voe_apm_->RegisterRxVadObserver(channel_, rx_callback));

  // The extra sleeps are to allow decisions some time to propagate to the
  // observer.
  TryDetectingSilence();
  Sleep(100);

  EXPECT_EQ(0, rx_callback.vad_decision);

  TryDetectingSpeechAfterSilence();
  Sleep(100);

  EXPECT_EQ(1, rx_callback.vad_decision);

  EXPECT_EQ(0, voe_apm_->DeRegisterRxVadObserver(channel_));
}

#endif   // !WEBRTC_IOS && !WEBRTC_ANDROID

TEST_F(AudioProcessingTest, EnablingEcAecmShouldEnableEcAecm) {
  // This one apparently applies to Android and iPhone as well.
  TryEnablingEcWithMode(webrtc::kEcAecm, webrtc::kEcAecm);
}

TEST_F(AudioProcessingTest, EcAecmModeIsEnabledAndSpeakerphoneByDefault) {
  bool cng_enabled = false;
  webrtc::AecmModes aecm_mode = webrtc::kAecmEarpiece;

  voe_apm_->GetAecmMode(aecm_mode, cng_enabled);

  EXPECT_TRUE(cng_enabled);
  EXPECT_EQ(webrtc::kAecmSpeakerphone, aecm_mode);
}

TEST_F(AudioProcessingTest, CanSetAecmMode) {
  EXPECT_EQ(0, voe_apm_->SetEcStatus(true, webrtc::kEcAecm));

  // Try some AECM mode - CNG enabled combinations.
  TryEnablingAecmWithMode(webrtc::kAecmEarpiece, true);
  TryEnablingAecmWithMode(webrtc::kAecmEarpiece, false);
  TryEnablingAecmWithMode(webrtc::kAecmLoudEarpiece, true);
  TryEnablingAecmWithMode(webrtc::kAecmLoudSpeakerphone, false);
  TryEnablingAecmWithMode(webrtc::kAecmQuietEarpieceOrHeadset, true);
  TryEnablingAecmWithMode(webrtc::kAecmSpeakerphone, false);
}

TEST_F(AudioProcessingTest, RxAgcShouldBeOffByDefault) {
  bool rx_agc_enabled = true;
  webrtc::AgcModes agc_mode = webrtc::kAgcDefault;

  EXPECT_EQ(0, voe_apm_->GetRxAgcStatus(channel_, rx_agc_enabled, agc_mode));
  EXPECT_FALSE(rx_agc_enabled);
  EXPECT_EQ(webrtc::kAgcAdaptiveDigital, agc_mode);
}

TEST_F(AudioProcessingTest, CanTurnOnDigitalRxAcg) {
  TryEnablingRxAgcWithMode(webrtc::kAgcAdaptiveDigital);
  TryEnablingRxAgcWithMode(webrtc::kAgcFixedDigital);
}

TEST_F(AudioProcessingTest, CannotTurnOnAdaptiveAnalogRxAgc) {
  EXPECT_EQ(-1, voe_apm_->SetRxAgcStatus(
      channel_, true, webrtc::kAgcAdaptiveAnalog));
}

TEST_F(AudioProcessingTest, NsIsOffWithModerateSuppressionByDefault) {
  bool ns_status = true;
  webrtc::NsModes ns_mode = webrtc::kNsDefault;
  EXPECT_EQ(0, voe_apm_->GetNsStatus(ns_status, ns_mode));

  EXPECT_FALSE(ns_status);
  EXPECT_EQ(webrtc::kNsModerateSuppression, ns_mode);
}

TEST_F(AudioProcessingTest, CanSetNsMode) {
  // Concrete suppression values map to themselves.
  TryEnablingNsWithMode(webrtc::kNsHighSuppression,
                        webrtc::kNsHighSuppression);
  TryEnablingNsWithMode(webrtc::kNsLowSuppression,
                        webrtc::kNsLowSuppression);
  TryEnablingNsWithMode(webrtc::kNsModerateSuppression,
                        webrtc::kNsModerateSuppression);
  TryEnablingNsWithMode(webrtc::kNsVeryHighSuppression,
                        webrtc::kNsVeryHighSuppression);

  // Conference and Default map to concrete values.
  TryEnablingNsWithMode(webrtc::kNsConference,
                        webrtc::kNsHighSuppression);
  TryEnablingNsWithMode(webrtc::kNsDefault,
                        webrtc::kNsModerateSuppression);
}

TEST_F(AudioProcessingTest, RxNsIsOffWithModerateSuppressionByDefault) {
  bool ns_status = true;
  webrtc::NsModes ns_mode = webrtc::kNsDefault;
  EXPECT_EQ(0, voe_apm_->GetRxNsStatus(channel_, ns_status, ns_mode));

  EXPECT_FALSE(ns_status);
  EXPECT_EQ(webrtc::kNsModerateSuppression, ns_mode);
}

TEST_F(AudioProcessingTest, CanSetRxNsMode) {
  EXPECT_EQ(0, voe_apm_->SetRxNsStatus(channel_, true));

  // See comments on the regular NS test above.
  TryEnablingRxNsWithMode(webrtc::kNsHighSuppression,
                          webrtc::kNsHighSuppression);
  TryEnablingRxNsWithMode(webrtc::kNsLowSuppression,
                          webrtc::kNsLowSuppression);
  TryEnablingRxNsWithMode(webrtc::kNsModerateSuppression,
                          webrtc::kNsModerateSuppression);
  TryEnablingRxNsWithMode(webrtc::kNsVeryHighSuppression,
                          webrtc::kNsVeryHighSuppression);
  TryEnablingRxNsWithMode(webrtc::kNsConference,
                          webrtc::kNsHighSuppression);
  TryEnablingRxNsWithMode(webrtc::kNsDefault,
                          webrtc::kNsModerateSuppression);
}

TEST_F(AudioProcessingTest, VadIsDisabledByDefault) {
  bool vad_enabled;
  bool disabled_dtx;
  webrtc::VadModes vad_mode;

  EXPECT_EQ(0, voe_codec_->GetVADStatus(
      channel_, vad_enabled, vad_mode, disabled_dtx));

  EXPECT_FALSE(vad_enabled);
}

TEST_F(AudioProcessingTest, VoiceActivityIndicatorReturns1WithSpeechOn) {
  // This sleep is necessary since the voice detection algorithm needs some
  // time to detect the speech from the fake microphone.
  Sleep(500);
  EXPECT_EQ(1, voe_apm_->VoiceActivityIndicator(channel_));
}

TEST_F(AudioProcessingTest, CanSetDelayOffset) {
  voe_apm_->SetDelayOffsetMs(50);
  EXPECT_EQ(50, voe_apm_->DelayOffsetMs());
  voe_apm_->SetDelayOffsetMs(-50);
  EXPECT_EQ(-50, voe_apm_->DelayOffsetMs());
}

TEST_F(AudioProcessingTest, HighPassFilterIsOnByDefault) {
  EXPECT_TRUE(voe_apm_->IsHighPassFilterEnabled());
}

TEST_F(AudioProcessingTest, CanSetHighPassFilter) {
  EXPECT_EQ(0, voe_apm_->EnableHighPassFilter(true));
  EXPECT_TRUE(voe_apm_->IsHighPassFilterEnabled());
  EXPECT_EQ(0, voe_apm_->EnableHighPassFilter(false));
  EXPECT_FALSE(voe_apm_->IsHighPassFilterEnabled());
}

TEST_F(AudioProcessingTest, StereoChannelSwappingIsOffByDefault) {
  EXPECT_FALSE(voe_apm_->IsStereoChannelSwappingEnabled());
}

TEST_F(AudioProcessingTest, CanSetStereoChannelSwapping) {
  voe_apm_->EnableStereoChannelSwapping(true);
  EXPECT_TRUE(voe_apm_->IsStereoChannelSwappingEnabled());
  voe_apm_->EnableStereoChannelSwapping(false);
  EXPECT_FALSE(voe_apm_->IsStereoChannelSwappingEnabled());
}

TEST_F(AudioProcessingTest, CanStartAndStopDebugRecording) {
  std::string output_path = webrtc::test::OutputPath();
  std::string output_file = output_path + "apm_debug.txt";

  EXPECT_EQ(0, voe_apm_->StartDebugRecording(output_file.c_str()));
  Sleep(1000);
  EXPECT_EQ(0, voe_apm_->StopDebugRecording());
}

#if defined(WEBRTC_IOS) || defined(WEBRTC_ANDROID)

TEST_F(AudioProcessingTest, AgcIsOffByDefaultAndDigital) {
  bool agc_enabled = true;
  webrtc::AgcModes agc_mode = webrtc::kAgcAdaptiveAnalog;

  EXPECT_EQ(0, voe_apm_->GetAgcStatus(agc_enabled, agc_mode));
  EXPECT_FALSE(agc_enabled);
  EXPECT_EQ(webrtc::kAgcAdaptiveDigital, agc_mode);
}

TEST_F(AudioProcessingTest, CanEnableAgcInAdaptiveDigitalMode) {
  TryEnablingAgcWithMode(webrtc::kAgcAdaptiveDigital);
}

TEST_F(AudioProcessingTest, AgcIsPossibleExceptInAdaptiveAnalogMode) {
  EXPECT_EQ(-1, voe_apm_->SetAgcStatus(true, webrtc::kAgcAdaptiveAnalog));
  EXPECT_EQ(0, voe_apm_->SetAgcStatus(true, webrtc::kAgcFixedDigital));
  EXPECT_EQ(0, voe_apm_->SetAgcStatus(true, webrtc::kAgcAdaptiveDigital));
}

TEST_F(AudioProcessingTest, EcIsDisabledAndAecmIsDefaultEcMode) {
  bool ec_enabled = true;
  webrtc::EcModes ec_mode = webrtc::kEcDefault;

  EXPECT_EQ(0, voe_apm_->GetEcStatus(ec_enabled, ec_mode));
  EXPECT_FALSE(ec_enabled);
  EXPECT_EQ(webrtc::kEcAecm, ec_mode);
}

TEST_F(AudioProcessingTest, TestVoiceActivityDetection) {
  TryDetectingSilence();
  TryDetectingSpeechAfterSilence();
}

#endif  // WEBRTC_IOS || WEBRTC_ANDROID
