#include "audio_source.h"
#include "webrtcpush_config.h"
#include "webrtcpush_log.h"

#include <gst/gst.h>
#include <gst/app/gstappsink.h>

struct AudioSource {
  GstElement *pipeline;
  GstElement *appsink;
  GstElement *enc;
  guint8 *frame_buf; /* 缓存最新帧数据，避免 sample 生命周期问题 */
};

AudioSource *audio_source_start(const char *alsa_device, char **error_message)
{
  AudioSource *src;
  GstElement *pipe, *asrc, *conv, *resample, *caps_f, *enc, *sink;
  GstCaps *caps;
  GstStateChangeReturn ret;

  if (!alsa_device || !alsa_device[0]) {
    if (error_message)
      *error_message = g_strdup("no ALSA device");
    return NULL;
  }

  src = g_new0(AudioSource, 1);

  pipe = gst_pipeline_new("audio-pipe");
  if (!g_strcmp0(alsa_device, "test")) {
    asrc = gst_element_factory_make("audiotestsrc", "asrc");
    if (asrc)
      g_object_set(asrc, "wave", 0, "freq", 440, NULL);
  } else {
    asrc = gst_element_factory_make("alsasrc", "asrc");
    if (asrc)
      g_object_set(asrc, "device", alsa_device, NULL);
  }
  conv = gst_element_factory_make("audioconvert", "conv");
  resample = gst_element_factory_make("audioresample", "resample");
  caps_f = gst_element_factory_make("capsfilter", "caps_f");
  enc = gst_element_factory_make("opusenc", "enc");
  sink = gst_element_factory_make("appsink", "sink");

  if (!pipe || !asrc || !conv || !resample || !caps_f || !enc || !sink) {
    if (error_message)
      *error_message = g_strdup("failed to create audio GStreamer elements");
    if (pipe)
      gst_object_unref(pipe);
    g_free(src);
    return NULL;
  }

  /* Opus 要求 48000Hz；单声道足够车机麦 */
  caps = gst_caps_new_simple("audio/x-raw",
      "rate", G_TYPE_INT, WEBRTCPUSH_OPUS_CLOCKRATE,
      "channels", G_TYPE_INT, WEBRTCPUSH_OPUS_CHANNELS,
      "format", G_TYPE_STRING, "S16LE", NULL);
  g_object_set(caps_f, "caps", caps, NULL);
  gst_caps_unref(caps);

  g_object_set(enc,
      "bitrate", WEBRTCPUSH_OPUS_BITRATE,
      "bitrate-type", 0, /* 0=cbr */
      "inband-fec", TRUE,
      NULL);
  /* framesize 属性部分 opusenc 不支持，先检查再设置 */
  {
    GParamSpec *pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(enc), "framesize");
    if (pspec)
      g_object_set(enc, "framesize", 20, NULL);
  }

  g_object_set(sink,
      "emit-signals", FALSE,
      "drop", FALSE,
      "max-buffers", WEBRTCPUSH_AUDIO_QUEUE_DEPTH,
      NULL);

  gst_bin_add_many(GST_BIN(pipe), asrc, conv, resample, caps_f, enc, sink, NULL);
  if (!gst_element_link_many(asrc, conv, resample, caps_f, enc, sink, NULL)) {
    if (error_message)
      *error_message = g_strdup("failed to link audio chain");
    gst_object_unref(pipe);
    g_free(src);
    return NULL;
  }

  src->pipeline = pipe;
  src->appsink = sink;
  src->enc = enc;

  ret = gst_element_set_state(pipe, GST_STATE_PLAYING);
  if (ret == GST_STATE_CHANGE_FAILURE) {
    if (error_message)
      *error_message = g_strdup("audio pipeline failed to reach PLAYING");
    gst_element_set_state(pipe, GST_STATE_NULL);
    gst_object_unref(pipe);
    g_free(src);
    return NULL;
  }

  my_zlog_info("audio_source: started device=%s opus=%uch %uHz %ukbps",
      alsa_device, WEBRTCPUSH_OPUS_CHANNELS, WEBRTCPUSH_OPUS_CLOCKRATE,
      WEBRTCPUSH_OPUS_BITRATE / 1000);
  return src;
}

void audio_source_stop(AudioSource *src)
{
  if (!src)
    return;
  if (src->pipeline) {
    gst_element_set_state(src->pipeline, GST_STATE_NULL);
    gst_object_unref(src->pipeline);
  }
  g_free(src->frame_buf);
  g_free(src);
}

gboolean audio_source_pull(AudioSource *src, const uint8_t **data, size_t *size,
                           guint64 *pts_ns, gint timeout_ms)
{
  GstAppSink *appsink;
  GstSample *sample;
  GstBuffer *buffer;
  GstMapInfo map;

  if (!src || !src->appsink || !data || !size || timeout_ms < 1)
    return FALSE;

  appsink = GST_APP_SINK(src->appsink);
  sample = gst_app_sink_try_pull_sample(appsink, (guint64)timeout_ms * GST_MSECOND);
  if (!sample)
    return FALSE;

  buffer = gst_sample_get_buffer(sample);
  if (!buffer || !gst_buffer_map(buffer, &map, GST_MAP_READ)) {
    gst_sample_unref(sample);
    return FALSE;
  }

  g_free(src->frame_buf);
  src->frame_buf = g_memdup2(map.data, map.size);
  *data = src->frame_buf;
  *size = map.size;
  if (pts_ns)
    *pts_ns = GST_BUFFER_PTS(buffer);

  gst_buffer_unmap(buffer, &map);
  gst_sample_unref(sample);
  return TRUE;
}
