// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/audio/alsa/alsa_util.h"

#include <string>

#include "base/logging.h"
#include "media/audio/alsa/alsa_wrapper.h"

namespace alsa_util {

static snd_pcm_t* OpenDevice(media::AlsaWrapper* wrapper,
                             const char* device_name,
                             snd_pcm_stream_t type,
                             int channels,
                             int sample_rate,
                             snd_pcm_format_t pcm_format,
                             int latency_us) {
  snd_pcm_t* handle = NULL;
  int error = wrapper->PcmOpen(&handle, device_name, type, SND_PCM_NONBLOCK);
  if (error < 0) {
    LOG(WARNING) << "PcmOpen: " << device_name << ","
                 << wrapper->StrError(error);
    return NULL;
  }

  error = wrapper->PcmSetParams(handle, pcm_format,
                                SND_PCM_ACCESS_RW_INTERLEAVED, channels,
                                sample_rate, 1, latency_us);
  if (error < 0) {
    LOG(WARNING) << "PcmSetParams: " << device_name << ", "
                 << wrapper->StrError(error) << " - Format: " << pcm_format
                 << " Channels: " << channels << " Latency: " << latency_us;
    if (alsa_util::CloseDevice(wrapper, handle) < 0) {
      // TODO(ajwong): Retry on certain errors?
      LOG(WARNING) << "Unable to close audio device. Leaking handle.";
    }
    return NULL;
  }

  return handle;
}

static std::string DeviceNameToControlName(const std::string& device_name) {
  const char kMixerPrefix[] = "hw";
  std::string control_name;
  size_t pos1 = device_name.find(':');
  if (pos1 == std::string::npos) {
    control_name = device_name;
  } else {
    // Examples:
    // deviceName: "front:CARD=Intel,DEV=0", controlName: "hw:CARD=Intel".
    // deviceName: "default:CARD=Intel", controlName: "CARD=Intel".
    size_t pos2 = device_name.find(',');
    control_name = (pos2 == std::string::npos) ?
        device_name.substr(pos1) :
        kMixerPrefix + device_name.substr(pos1, pos2 - pos1);
  }

  return control_name;
}

snd_pcm_format_t BitsToFormat(int bits_per_sample) {
  switch (bits_per_sample) {
    case 8:
      return SND_PCM_FORMAT_U8;

    case 16:
      return SND_PCM_FORMAT_S16;

    case 24:
      return SND_PCM_FORMAT_S24;

    case 32:
      return SND_PCM_FORMAT_S32;

    default:
      return SND_PCM_FORMAT_UNKNOWN;
  }
}

int CloseDevice(media::AlsaWrapper* wrapper, snd_pcm_t* handle) {
  std::string device_name = wrapper->PcmName(handle);
  int error = wrapper->PcmClose(handle);
  if (error < 0) {
    LOG(ERROR) << "PcmClose: " << device_name << ", "
               << wrapper->StrError(error);
  }

  return error;
}

snd_pcm_t* OpenCaptureDevice(media::AlsaWrapper* wrapper,
                             const char* device_name,
                             int channels,
                             int sample_rate,
                             snd_pcm_format_t pcm_format,
                             int latency_us) {
  return OpenDevice(wrapper, device_name, SND_PCM_STREAM_CAPTURE, channels,
                    sample_rate, pcm_format, latency_us);
}

snd_pcm_t* OpenPlaybackDevice(media::AlsaWrapper* wrapper,
                              const char* device_name,
                              int channels,
                              int sample_rate,
                              snd_pcm_format_t pcm_format,
                              int latency_us) {
  return OpenDevice(wrapper, device_name, SND_PCM_STREAM_PLAYBACK, channels,
                    sample_rate, pcm_format, latency_us);
}

snd_mixer_t* OpenMixer(media::AlsaWrapper* wrapper,
                       const std::string& device_name) {
  snd_mixer_t* mixer = NULL;

  int error = wrapper->MixerOpen(&mixer, 0);
  if (error < 0) {
    LOG(ERROR) << "MixerOpen: " << device_name << ", "
               << wrapper->StrError(error);
    return NULL;
  }

  std::string control_name = DeviceNameToControlName(device_name);
  error = wrapper->MixerAttach(mixer, control_name.c_str());
  if (error < 0) {
    LOG(ERROR) << "MixerAttach, " << control_name << ", "
               << wrapper->StrError(error);
    alsa_util::CloseMixer(wrapper, mixer, device_name);
    return NULL;
  }

  error = wrapper->MixerElementRegister(mixer, NULL, NULL);
  if (error < 0) {
    LOG(ERROR) << "MixerElementRegister: " << control_name << ", "
               << wrapper->StrError(error);
    alsa_util::CloseMixer(wrapper, mixer, device_name);
    return NULL;
  }

  return mixer;
}

void CloseMixer(media::AlsaWrapper* wrapper, snd_mixer_t* mixer,
                const std::string& device_name) {
  if (!mixer)
    return;

  wrapper->MixerFree(mixer);

  int error = 0;
  if (!device_name.empty()) {
    std::string control_name = DeviceNameToControlName(device_name);
    error = wrapper->MixerDetach(mixer, control_name.c_str());
    if (error < 0) {
      LOG(WARNING) << "MixerDetach: " << control_name << ", "
                   << wrapper->StrError(error);
    }
  }

  error = wrapper->MixerClose(mixer);
  if (error < 0) {
    LOG(WARNING) << "MixerClose: " << wrapper->StrError(error);
  }
}

snd_mixer_elem_t* LoadCaptureMixerElement(media::AlsaWrapper* wrapper,
                                          snd_mixer_t* mixer) {
  if (!mixer)
    return NULL;

  int error = wrapper->MixerLoad(mixer);
  if (error < 0) {
    LOG(ERROR) << "MixerLoad: " << wrapper->StrError(error);
    return NULL;
  }

  snd_mixer_elem_t* elem = NULL;
  snd_mixer_elem_t* mic_elem = NULL;
  const char kCaptureElemName[] = "Capture";
  const char kMicElemName[] = "Mic";
  for (elem = wrapper->MixerFirstElem(mixer);
       elem;
       elem = wrapper->MixerNextElem(elem)) {
    if (wrapper->MixerSelemIsActive(elem)) {
      const char* elem_name = wrapper->MixerSelemName(elem);
      if (strcmp(elem_name, kCaptureElemName) == 0)
        return elem;
      else if (strcmp(elem_name, kMicElemName) == 0)
        mic_elem = elem;
    }
  }

  // Did not find any Capture handle, use the Mic handle.
  return mic_elem;
}

}  // namespace alsa_util