/* Copyright 2013 The Chromium OS 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 <gtest/gtest.h>
#include <stdint.h>
#include <time.h>

extern "C" {
  #include "cras_hfp_info.c"
}

static struct hfp_info *info;
static struct cras_iodev dev;
static cras_audio_format format;

static thread_callback thread_cb;
static void *cb_data;
static timespec ts;

void ResetStubData() {
  format.format = SND_PCM_FORMAT_S16_LE;
  format.num_channels = 1;
  format.frame_rate = 8000;
  dev.format = &format;
}

namespace {

TEST(HfpInfo, AddRmDev) {
  info = hfp_info_create();
  ASSERT_NE(info, (void *)NULL);
  dev.direction = CRAS_STREAM_OUTPUT;

  /* Test add dev */
  ASSERT_EQ(0, hfp_info_add_iodev(info, &dev));
  ASSERT_TRUE(hfp_info_has_iodev(info));

  /* Test remove dev */
  ASSERT_EQ(0, hfp_info_rm_iodev(info, &dev));
  ASSERT_FALSE(hfp_info_has_iodev(info));

  hfp_info_destroy(info);
}

TEST(HfpInfo, AddRmDevInvalid) {
  info = hfp_info_create();
  ASSERT_NE(info, (void *)NULL);

  dev.direction = CRAS_STREAM_OUTPUT;

  /* Remove an iodev which doesn't exist */
  ASSERT_NE(0, hfp_info_rm_iodev(info, &dev));

  /* Adding an iodev twice returns error code */
  ASSERT_EQ(0, hfp_info_add_iodev(info, &dev));
  ASSERT_NE(0, hfp_info_add_iodev(info, &dev));

  hfp_info_destroy(info);
}

TEST(HfpInfo, AcquirePlaybackBuffer) {
  unsigned buffer_frames, buffer_frames2, queued;
  uint8_t *samples;

  ResetStubData();

  info = hfp_info_create();
  ASSERT_NE(info, (void *)NULL);

  hfp_info_start(1, 48, info);
  dev.direction = CRAS_STREAM_OUTPUT;
  ASSERT_EQ(0, hfp_info_add_iodev(info, &dev));

  buffer_frames = 500;
  hfp_buf_acquire(info, &dev, &samples, &buffer_frames);
  ASSERT_EQ(500, buffer_frames);

  hfp_buf_release(info, &dev, 500);
  ASSERT_EQ(500, hfp_buf_queued(info, &dev));

  /* Assert the amount of frames of available buffer + queued buf is
   * greater than or equal to the buffer size, 2 bytes per frame
   */
  queued = hfp_buf_queued(info, &dev);
  buffer_frames = 500;
  hfp_buf_acquire(info, &dev, &samples, &buffer_frames);
  ASSERT_GE(info->playback_buf->used_size / 2, buffer_frames + queued);

  /* Consume all queued data from read buffer */
  buf_increment_read(info->playback_buf, queued * 2);

  queued = hfp_buf_queued(info, &dev);
  ASSERT_EQ(0, queued);

  /* Assert consecutive acquire buffer will acquire full used size of buffer */
  buffer_frames = 500;
  hfp_buf_acquire(info, &dev, &samples, &buffer_frames);
  hfp_buf_release(info, &dev, buffer_frames);

  buffer_frames2 = 500;
  hfp_buf_acquire(info, &dev, &samples, &buffer_frames2);
  hfp_buf_release(info, &dev, buffer_frames2);

  ASSERT_GE(info->playback_buf->used_size / 2, buffer_frames + buffer_frames2);

  hfp_info_destroy(info);
}

TEST(HfpInfo, AcquireCaptureBuffer) {
  unsigned buffer_frames, buffer_frames2;
  uint8_t *samples;

  ResetStubData();

  info = hfp_info_create();
  ASSERT_NE(info, (void *)NULL);

  hfp_info_start(1, 48, info);
  dev.direction = CRAS_STREAM_INPUT;
  ASSERT_EQ(0, hfp_info_add_iodev(info, &dev));

  /* Put fake data 100 bytes(50 frames) in capture buf for test */
  buf_increment_write(info->capture_buf, 100);

  /* Assert successfully acquire and release 100 bytes of data */
  buffer_frames = 50;
  hfp_buf_acquire(info, &dev, &samples, &buffer_frames);
  ASSERT_EQ(50, buffer_frames);

  hfp_buf_release(info, &dev, buffer_frames);
  ASSERT_EQ(0, hfp_buf_queued(info, &dev));

  /* Push fake data to capture buffer */
  buf_increment_write(info->capture_buf, info->capture_buf->used_size - 100);
  buf_increment_write(info->capture_buf, 100);

  /* Assert consecutive acquire call will consume the whole buffer */
  buffer_frames = 1000;
  hfp_buf_acquire(info, &dev, &samples, &buffer_frames);
  hfp_buf_release(info, &dev, buffer_frames);
  ASSERT_GE(1000, buffer_frames);

  buffer_frames2 = 1000;
  hfp_buf_acquire(info, &dev, &samples, &buffer_frames2);
  hfp_buf_release(info, &dev, buffer_frames2);

  ASSERT_GE(info->capture_buf->used_size / 2, buffer_frames + buffer_frames2);

  hfp_info_destroy(info);
}

TEST(HfpInfo, HfpReadWriteFD) {
  int rc;
  int sock[2];
  uint8_t sample[480];
  uint8_t *buf;
  unsigned buffer_count;

  ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sock));

  info = hfp_info_create();
  ASSERT_NE(info, (void *)NULL);

  dev.direction = CRAS_STREAM_INPUT;
  hfp_info_start(sock[1], 48, info);
  ASSERT_EQ(0, hfp_info_add_iodev(info, &dev));

  /* Mock the sco fd and send some fake data */
  send(sock[0], sample, 48, 0);

  rc = hfp_read(info);
  ASSERT_EQ(48, rc);

  rc = hfp_buf_queued(info, &dev);
  ASSERT_EQ(48 / 2, rc);

  /* Fill the write buffer*/
  buffer_count = info->capture_buf->used_size;
  buf = buf_write_pointer_size(info->capture_buf, &buffer_count);
  buf_increment_write(info->capture_buf, buffer_count);
  ASSERT_NE((void *)NULL, buf);

  rc = hfp_read(info);
  ASSERT_EQ(0, rc);

  ASSERT_EQ(0, hfp_info_rm_iodev(info, &dev));
  dev.direction = CRAS_STREAM_OUTPUT;
  ASSERT_EQ(0, hfp_info_add_iodev(info, &dev));

  /* Initial buffer is empty */
  rc = hfp_write(info);
  ASSERT_EQ(0, rc);

  buffer_count = 1024;
  buf = buf_write_pointer_size(info->playback_buf, &buffer_count);
  buf_increment_write(info->playback_buf, buffer_count);

  rc = hfp_write(info);
  ASSERT_EQ(48, rc);

  rc = recv(sock[0], sample, 48, 0);
  ASSERT_EQ(48, rc);

  hfp_info_destroy(info);
}

TEST(HfpInfo, StartHfpInfo) {
  int sock[2];

  ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sock));

  info = hfp_info_create();
  ASSERT_NE(info, (void *)NULL);

  hfp_info_start(sock[0], 48, info);
  ASSERT_EQ(1, hfp_info_running(info));
  ASSERT_EQ(cb_data, (void *)info);

  hfp_info_stop(info);
  ASSERT_EQ(0, hfp_info_running(info));
  ASSERT_EQ(NULL, cb_data);

  hfp_info_destroy(info);
}

TEST(HfpInfo, StartHfpInfoAndRead) {
  int rc;
  int sock[2];
  uint8_t sample[480];

  ResetStubData();

  ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sock));

  info = hfp_info_create();
  ASSERT_NE(info, (void *)NULL);

  /* Start and send two chunk of fake data */
  hfp_info_start(sock[1], 48, info);
  send(sock[0], sample ,48, 0);
  send(sock[0], sample ,48, 0);

  /* Trigger thread callback */
  thread_cb((struct hfp_info *)cb_data);

  dev.direction = CRAS_STREAM_INPUT;
  ASSERT_EQ(0, hfp_info_add_iodev(info, &dev));

  /* Expect no data read, since no idev present at previous thread callback */
  rc = hfp_buf_queued(info, &dev);
  ASSERT_EQ(0, rc);

  /* Trigger thread callback after idev added. */
  ts.tv_sec = 0;
  ts.tv_nsec = 5000000;
  thread_cb((struct hfp_info *)cb_data);

  rc = hfp_buf_queued(info, &dev);
  ASSERT_EQ(48 / 2, rc);

  /* Assert wait time is unchanged. */
  ASSERT_EQ(0, ts.tv_sec);
  ASSERT_EQ(5000000, ts.tv_nsec);

  hfp_info_stop(info);
  ASSERT_EQ(0, hfp_info_running(info));

  hfp_info_destroy(info);
}

TEST(HfpInfo, StartHfpInfoAndWrite) {
  int rc;
  int sock[2];
  uint8_t sample[480];

  ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sock));

  info = hfp_info_create();
  ASSERT_NE(info, (void *)NULL);

  hfp_info_start(sock[1], 48, info);
  send(sock[0], sample ,48, 0);
  send(sock[0], sample ,48, 0);

  /* Trigger thread callback */
  thread_cb((struct hfp_info *)cb_data);

  dev.direction = CRAS_STREAM_OUTPUT;
  ASSERT_EQ(0, hfp_info_add_iodev(info, &dev));

  /* Assert queued samples unchanged before output device added */
  ASSERT_EQ(0, hfp_buf_queued(info, &dev));

  /* Put some fake data and trigger thread callback again */
  buf_increment_write(info->playback_buf, 1008);
  thread_cb((struct hfp_info *)cb_data);

  /* Assert some samples written */
  rc = recv(sock[0], sample ,48, 0);
  ASSERT_EQ(48, rc);
  ASSERT_EQ(480, hfp_buf_queued(info, &dev));

  hfp_info_stop(info);
  hfp_info_destroy(info);
}

} // namespace

extern "C" {

struct audio_thread *cras_iodev_list_get_audio_thread()
{
  return NULL;
}

void audio_thread_add_callback(int fd, thread_callback cb,
                               void *data)
{
  thread_cb = cb;
  cb_data = data;
  return;
}

int audio_thread_rm_callback_sync(struct audio_thread *thread, int fd)
{
  thread_cb = NULL;
  cb_data = NULL;
  return 0;
}
}

int main(int argc, char **argv) {
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}