/* Copyright (c) 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 <sys/socket.h>

#include <stdio.h>

extern "C" {
  #include "cras_hfp_slc.h"
  #include "cras_telephony.h"
}

static struct hfp_slc_handle *handle;
static struct cras_telephony_handle fake_telephony;
static int cras_bt_device_update_hardware_volume_called;
static int slc_initialized_cb_called;
static int slc_disconnected_cb_called;
static int cras_system_add_select_fd_called;
static void(*slc_cb)(void *data);
static void *slc_cb_data;
static int fake_errno;
static struct cras_bt_device *device =
    reinterpret_cast<struct cras_bt_device *>(2);

int slc_initialized_cb(struct hfp_slc_handle *handle);
int slc_disconnected_cb(struct hfp_slc_handle *handle);

void ResetStubData() {
  slc_initialized_cb_called = 0;
  cras_system_add_select_fd_called = 0;
  cras_bt_device_update_hardware_volume_called = 0;
  slc_cb = NULL;
  slc_cb_data = NULL;
}

namespace {

TEST(HfpSlc, CreateSlcHandle) {
  ResetStubData();

  handle = hfp_slc_create(0, 0, device, slc_initialized_cb,
                          slc_disconnected_cb);
  ASSERT_EQ(1, cras_system_add_select_fd_called);
  ASSERT_EQ(handle, slc_cb_data);

  hfp_slc_destroy(handle);
}

TEST(HfpSlc, InitializeSlc) {
  int err;
  int sock[2];
  char buf[256];
  char *chp;
  ResetStubData();

  ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sock));
  handle = hfp_slc_create(sock[0], 0, device, slc_initialized_cb,
                          slc_disconnected_cb);

  err = write(sock[1], "AT+CIND=?\r", 10);
  ASSERT_EQ(10, err);
  slc_cb(slc_cb_data);
  err = read(sock[1], buf, 256);

  /* Assert "\r\n+CIND: ... \r\n" response is received */
  chp = strstr(buf, "\r\n");
  ASSERT_NE((void *)NULL, (void *)chp);
  ASSERT_EQ(0, strncmp("\r\n+CIND:", chp, 8));
  chp+=2;
  chp = strstr(chp, "\r\n");
  ASSERT_NE((void *)NULL, (void *)chp);

  /* Assert "\r\nOK\r\n" response is received */
  chp+=2;
  chp = strstr(chp, "\r\n");
  ASSERT_NE((void *)NULL, (void *)chp);
  ASSERT_EQ(0, strncmp("\r\nOK", chp, 4));

  err = write(sock[1], "AT+CMER=3,0,0,1\r", 16);
  ASSERT_EQ(16, err);
  slc_cb(slc_cb_data);

  ASSERT_EQ(1, slc_initialized_cb_called);

  /* Assert "\r\nOK\r\n" response is received */
  err = read(sock[1], buf, 256);

  chp = strstr(buf, "\r\n");
  ASSERT_NE((void *)NULL, (void *)chp);
  ASSERT_EQ(0, strncmp("\r\nOK", chp, 4));

  err = write(sock[1], "AT+VGS=13\r", 10);
  ASSERT_EQ(err, 10);
  slc_cb(slc_cb_data);

  err = read(sock[1], buf, 256);

  chp = strstr(buf, "\r\n");
  ASSERT_NE((void *)NULL, (void *)chp);
  ASSERT_EQ(0, strncmp("\r\nOK", chp, 4));

  ASSERT_EQ(1, cras_bt_device_update_hardware_volume_called);

  hfp_slc_destroy(handle);
}

TEST(HfpSlc, DisconnectSlc) {
  int sock[2];
  ResetStubData();

  ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sock));
  handle = hfp_slc_create(sock[0], 0, device, slc_initialized_cb,
                          slc_disconnected_cb);
  /* Close socket right away to make read() get negative err code, and
   * fake the errno to ECONNRESET. */
  close(sock[0]);
  close(sock[1]);
  fake_errno = 104;
  slc_cb(slc_cb_data);

  ASSERT_EQ(1, slc_disconnected_cb_called);

  hfp_slc_destroy(handle);
}
} // namespace

int slc_initialized_cb(struct hfp_slc_handle *handle) {
  slc_initialized_cb_called++;
  return 0;
}

int slc_disconnected_cb(struct hfp_slc_handle *handle) {
  slc_disconnected_cb_called++;
  return 0;
}

extern "C" {
int cras_system_add_select_fd(int fd,
			      void (*callback)(void *data),
			      void *callback_data) {
  cras_system_add_select_fd_called++;
  slc_cb = callback;
  slc_cb_data = callback_data;
  return 0;
}

void cras_system_rm_select_fd(int fd) {
}

void cras_bt_device_update_hardware_volume(struct cras_bt_device *device,
    int volume)
{
  cras_bt_device_update_hardware_volume_called++;
}

/* To return fake errno */
int *__errno_location() {
  return &fake_errno;
}
}

// For telephony
struct cras_telephony_handle* cras_telephony_get()
{
  return &fake_telephony;
}

void cras_telephony_store_dial_number(int len, const char* num)
{
}

int cras_telephony_event_answer_call()
{
  return 0;
}

int cras_telephony_event_terminate_call()
{
  return 0;
}

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