/*
** Copyright 2006, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
**     http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/

#define LOG_TAG "BluetoothAudioGateway.cpp"

#include "android_bluetooth_common.h"
#include "android_bluetooth_c.h"
#include "android_runtime/AndroidRuntime.h"
#include "JNIHelp.h"
#include "jni.h"
#include "utils/Log.h"
#include "utils/misc.h"

#define USE_ACCEPT_DIRECTLY (0)
#define USE_SELECT (0) /* 1 for select(), 0 for poll(); used only when
                          USE_ACCEPT_DIRECTLY == 0 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/uio.h>
#include <ctype.h>

#if USE_SELECT
#include <sys/select.h>
#else
#include <sys/poll.h>
#endif

#ifdef HAVE_BLUETOOTH
#include <bluetooth/bluetooth.h>
#include <bluetooth/rfcomm.h>
#include <bluetooth/sco.h>
#endif

namespace android {

#ifdef HAVE_BLUETOOTH
static jfieldID field_mNativeData;
    /* in */
static jfieldID field_mHandsfreeAgRfcommChannel;
static jfieldID field_mHeadsetAgRfcommChannel;
    /* out */
static jfieldID field_mTimeoutRemainingMs; /* out */

static jfieldID field_mConnectingHeadsetAddress;
static jfieldID field_mConnectingHeadsetRfcommChannel; /* -1 when not connected */
static jfieldID field_mConnectingHeadsetSocketFd;

static jfieldID field_mConnectingHandsfreeAddress;
static jfieldID field_mConnectingHandsfreeRfcommChannel; /* -1 when not connected */
static jfieldID field_mConnectingHandsfreeSocketFd;


typedef struct {
    int hcidev;
    int hf_ag_rfcomm_channel;
    int hs_ag_rfcomm_channel;
    int hf_ag_rfcomm_sock;
    int hs_ag_rfcomm_sock;
} native_data_t;

static inline native_data_t * get_native_data(JNIEnv *env, jobject object) {
    return (native_data_t *)(env->GetIntField(object,
                                                 field_mNativeData));
}

static int setup_listening_socket(int dev, int channel);
#endif

static void classInitNative(JNIEnv* env, jclass clazz) {
    LOGV("%s", __FUNCTION__);
#ifdef HAVE_BLUETOOTH

    /* in */
    field_mNativeData = get_field(env, clazz, "mNativeData", "I");
    field_mHandsfreeAgRfcommChannel =
        get_field(env, clazz, "mHandsfreeAgRfcommChannel", "I");
    field_mHeadsetAgRfcommChannel =
        get_field(env, clazz, "mHeadsetAgRfcommChannel", "I");

    /* out */
    field_mConnectingHeadsetAddress =
        get_field(env, clazz,
                  "mConnectingHeadsetAddress", "Ljava/lang/String;");
    field_mConnectingHeadsetRfcommChannel =
        get_field(env, clazz, "mConnectingHeadsetRfcommChannel", "I");
    field_mConnectingHeadsetSocketFd =
        get_field(env, clazz, "mConnectingHeadsetSocketFd", "I");

    field_mConnectingHandsfreeAddress =
        get_field(env, clazz,
                  "mConnectingHandsfreeAddress", "Ljava/lang/String;");
    field_mConnectingHandsfreeRfcommChannel =
        get_field(env, clazz, "mConnectingHandsfreeRfcommChannel", "I");
    field_mConnectingHandsfreeSocketFd =
        get_field(env, clazz, "mConnectingHandsfreeSocketFd", "I");

    field_mTimeoutRemainingMs =
        get_field(env, clazz, "mTimeoutRemainingMs", "I");
#endif
}

static void initializeNativeDataNative(JNIEnv* env, jobject object) {
    LOGV("%s", __FUNCTION__);
#ifdef HAVE_BLUETOOTH
    native_data_t *nat = (native_data_t *)calloc(1, sizeof(native_data_t));
    if (NULL == nat) {
        LOGE("%s: out of memory!", __FUNCTION__);
        return;
    }

    nat->hcidev = BLUETOOTH_ADAPTER_HCI_NUM;

    env->SetIntField(object, field_mNativeData, (jint)nat);
    nat->hf_ag_rfcomm_channel =
        env->GetIntField(object, field_mHandsfreeAgRfcommChannel);
    nat->hs_ag_rfcomm_channel =
        env->GetIntField(object, field_mHeadsetAgRfcommChannel);
    LOGV("HF RFCOMM channel = %d.", nat->hf_ag_rfcomm_channel);
    LOGV("HS RFCOMM channel = %d.", nat->hs_ag_rfcomm_channel);

    /* Set the default values of these to -1. */
    env->SetIntField(object, field_mConnectingHeadsetRfcommChannel, -1);
    env->SetIntField(object, field_mConnectingHandsfreeRfcommChannel, -1);

    nat->hf_ag_rfcomm_sock = -1;
    nat->hs_ag_rfcomm_sock = -1;
#endif
}

static void cleanupNativeDataNative(JNIEnv* env, jobject object) {
    LOGV("%s", __FUNCTION__);
#ifdef HAVE_BLUETOOTH
    native_data_t *nat = get_native_data(env, object);
    if (nat) {
        free(nat);
    }
#endif
}

#ifdef HAVE_BLUETOOTH

#if USE_ACCEPT_DIRECTLY==0
static int set_nb(int sk, bool nb) {
    int flags = fcntl(sk, F_GETFL);
    if (flags < 0) {
        LOGE("Can't get socket flags with fcntl(): %s (%d)",
             strerror(errno), errno);
        close(sk);
        return -1;
    }
    flags &= ~O_NONBLOCK;
    if (nb) flags |= O_NONBLOCK;
    int status = fcntl(sk, F_SETFL, flags);
    if (status < 0) {
        LOGE("Can't set socket to nonblocking mode with fcntl(): %s (%d)",
             strerror(errno), errno);
        close(sk);
        return -1;
    }
    return 0;
}
#endif /*USE_ACCEPT_DIRECTLY==0*/

static int do_accept(JNIEnv* env, jobject object, int ag_fd,
                     jfieldID out_fd,
                     jfieldID out_address,
                     jfieldID out_channel) {

#if USE_ACCEPT_DIRECTLY==0
    if (set_nb(ag_fd, true) < 0)
        return -1;
#endif

    struct sockaddr_rc raddr;
    int alen = sizeof(raddr);
    int nsk = accept(ag_fd, (struct sockaddr *) &raddr, &alen);
    if (nsk < 0) {
        LOGE("Error on accept from socket fd %d: %s (%d).",
             ag_fd,
             strerror(errno),
             errno);
#if USE_ACCEPT_DIRECTLY==0
        set_nb(ag_fd, false);
#endif
        return -1;
    }

    env->SetIntField(object, out_fd, nsk);
    env->SetIntField(object, out_channel, raddr.rc_channel);

    char addr[BTADDR_SIZE];
    get_bdaddr_as_string(&raddr.rc_bdaddr, addr);
    env->SetObjectField(object, out_address, env->NewStringUTF(addr));

    LOGI("Successful accept() on AG socket %d: new socket %d, address %s, RFCOMM channel %d",
         ag_fd,
         nsk,
         addr,
         raddr.rc_channel);
#if USE_ACCEPT_DIRECTLY==0
    set_nb(ag_fd, false);
#endif
    return 0;
}

#if USE_SELECT
static inline int on_accept_set_fields(JNIEnv* env, jobject object,
                                       fd_set *rset, int ag_fd,
                                       jfieldID out_fd,
                                       jfieldID out_address,
                                       jfieldID out_channel) {

    env->SetIntField(object, out_channel, -1);

    if (ag_fd >= 0 && FD_ISSET(ag_fd, &rset)) {
        return do_accept(env, object, ag_fd,
                         out_fd, out_address, out_channel);
    }
    else {
        LOGI("fd = %d, FD_ISSET() = %d",
             ag_fd,
             FD_ISSET(ag_fd, &rset));
        if (ag_fd >= 0 && !FD_ISSET(ag_fd, &rset)) {
            LOGE("WTF???");
            return -1;
        }
    }

    return 0;
}
#endif
#endif /* HAVE_BLUETOOTH */

static jboolean waitForHandsfreeConnectNative(JNIEnv* env, jobject object,
                                              jint timeout_ms) {
//    LOGV("%s", __FUNCTION__);
#ifdef HAVE_BLUETOOTH

    env->SetIntField(object, field_mTimeoutRemainingMs, timeout_ms);

    int n = 0;
    native_data_t *nat = get_native_data(env, object);
#if USE_ACCEPT_DIRECTLY
    if (nat->hf_ag_rfcomm_channel > 0) {
        LOGI("Setting HF AG server socket to RFCOMM port %d!",
             nat->hf_ag_rfcomm_channel);
        struct timeval tv;
        int len = sizeof(tv);
        if (getsockopt(nat->hf_ag_rfcomm_channel,
                       SOL_SOCKET, SO_RCVTIMEO, &tv, &len) < 0) {
            LOGE("getsockopt(%d, SOL_SOCKET, SO_RCVTIMEO): %s (%d)",
                 nat->hf_ag_rfcomm_channel,
                 strerror(errno),
                 errno);
            return JNI_FALSE;
        }
        LOGI("Current HF AG server socket RCVTIMEO is (%d(s), %d(us))!",
             (int)tv.tv_sec, (int)tv.tv_usec);
        if (timeout_ms >= 0) {
            tv.tv_sec = timeout_ms / 1000;
            tv.tv_usec = 1000 * (timeout_ms % 1000);
            if (setsockopt(nat->hf_ag_rfcomm_channel,
                           SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) {
                LOGE("setsockopt(%d, SOL_SOCKET, SO_RCVTIMEO): %s (%d)",
                     nat->hf_ag_rfcomm_channel,
                     strerror(errno),
                     errno);
                return JNI_FALSE;
            }
            LOGI("Changed HF AG server socket RCVTIMEO to (%d(s), %d(us))!",
                 (int)tv.tv_sec, (int)tv.tv_usec);
        }

        if (!do_accept(env, object, nat->hf_ag_rfcomm_sock,
                       field_mConnectingHandsfreeSocketFd,
                       field_mConnectingHandsfreeAddress,
                       field_mConnectingHandsfreeRfcommChannel))
        {
            env->SetIntField(object, field_mTimeoutRemainingMs, 0);
            return JNI_TRUE;
        }
        return JNI_FALSE;
    }
#else
#if USE_SELECT
    fd_set rset;
    FD_ZERO(&rset);
    int cnt = 0;
    if (nat->hf_ag_rfcomm_channel > 0) {
        LOGI("Setting HF AG server socket to RFCOMM port %d!",
             nat->hf_ag_rfcomm_channel);
        cnt++;
        FD_SET(nat->hf_ag_rfcomm_sock, &rset);
    }
    if (nat->hs_ag_rfcomm_channel > 0) {
        LOGI("Setting HS AG server socket to RFCOMM port %d!",
             nat->hs_ag_rfcomm_channel);
        cnt++;
        FD_SET(nat->hs_ag_rfcomm_sock, &rset);
    }
    if (cnt == 0) {
        LOGE("Neither HF nor HS listening sockets are open!");
        return JNI_FALSE;
    }

    struct timeval to;
    if (timeout_ms >= 0) {
        to.tv_sec = timeout_ms / 1000;
        to.tv_usec = 1000 * (timeout_ms % 1000);
    }
    n = select(MAX(nat->hf_ag_rfcomm_sock,
                       nat->hs_ag_rfcomm_sock) + 1,
                   &rset,
                   NULL,
                   NULL,
                   (timeout_ms < 0 ? NULL : &to));
    if (timeout_ms > 0) {
        jint remaining = to.tv_sec*1000 + to.tv_usec/1000;
        LOGI("Remaining time %ldms", (long)remaining);
        env->SetIntField(object, field_mTimeoutRemainingMs,
                         remaining);
    }

    LOGI("listening select() returned %d", n);

    if (n <= 0) {
        if (n < 0)  {
            LOGE("listening select() on RFCOMM sockets: %s (%d)",
                 strerror(errno),
                 errno);
        }
        return JNI_FALSE;
    }

    n = on_accept_set_fields(env, object,
                             &rset, nat->hf_ag_rfcomm_sock,
                             field_mConnectingHandsfreeSocketFd,
                             field_mConnectingHandsfreeAddress,
                             field_mConnectingHandsfreeRfcommChannel);

    n += on_accept_set_fields(env, object,
                              &rset, nat->hs_ag_rfcomm_sock,
                              field_mConnectingHeadsetSocketFd,
                              field_mConnectingHeadsetAddress,
                              field_mConnectingHeadsetRfcommChannel);

    return !n ? JNI_TRUE : JNI_FALSE;
#else
    struct pollfd fds[2];
    int cnt = 0;
    if (nat->hf_ag_rfcomm_channel > 0) {
//        LOGI("Setting HF AG server socket %d to RFCOMM port %d!",
//             nat->hf_ag_rfcomm_sock,
//             nat->hf_ag_rfcomm_channel);
        fds[cnt].fd = nat->hf_ag_rfcomm_sock;
        fds[cnt].events = POLLIN | POLLPRI | POLLOUT | POLLERR;
        cnt++;
    }
    if (nat->hs_ag_rfcomm_channel > 0) {
//        LOGI("Setting HS AG server socket %d to RFCOMM port %d!",
//             nat->hs_ag_rfcomm_sock,
//             nat->hs_ag_rfcomm_channel);
        fds[cnt].fd = nat->hs_ag_rfcomm_sock;
        fds[cnt].events = POLLIN | POLLPRI | POLLOUT | POLLERR;
        cnt++;
    }
    if (cnt == 0) {
        LOGE("Neither HF nor HS listening sockets are open!");
        return JNI_FALSE;
    }
    n = poll(fds, cnt, timeout_ms);
    if (n <= 0) {
        if (n < 0)  {
            LOGE("listening poll() on RFCOMM sockets: %s (%d)",
                 strerror(errno),
                 errno);
        }
        else {
            env->SetIntField(object, field_mTimeoutRemainingMs, 0);
//            LOGI("listening poll() on RFCOMM socket timed out");
        }
        return JNI_FALSE;
    }

    //LOGI("listening poll() on RFCOMM socket returned %d", n);
    int err = 0;
    for (cnt = 0; cnt < (int)(sizeof(fds)/sizeof(fds[0])); cnt++) {
        //LOGI("Poll on fd %d revent = %d.", fds[cnt].fd, fds[cnt].revents);
        if (fds[cnt].fd == nat->hf_ag_rfcomm_sock) {
            if (fds[cnt].revents & (POLLIN | POLLPRI | POLLOUT)) {
                LOGI("Accepting HF connection.\n");
                err += do_accept(env, object, fds[cnt].fd,
                               field_mConnectingHandsfreeSocketFd,
                               field_mConnectingHandsfreeAddress,
                               field_mConnectingHandsfreeRfcommChannel);
                n--;
            }
        }
        else if (fds[cnt].fd == nat->hs_ag_rfcomm_sock) {
            if (fds[cnt].revents & (POLLIN | POLLPRI | POLLOUT)) {
                LOGI("Accepting HS connection.\n");
                err += do_accept(env, object, fds[cnt].fd,
                               field_mConnectingHeadsetSocketFd,
                               field_mConnectingHeadsetAddress,
                               field_mConnectingHeadsetRfcommChannel);
                n--;
            }
        }
    } /* for */

    if (n != 0) {
        LOGI("Bogus poll(): %d fake pollfd entrie(s)!", n);
        return JNI_FALSE;
    }

    return !err ? JNI_TRUE : JNI_FALSE;
#endif /* USE_SELECT */
#endif /* USE_ACCEPT_DIRECTLY */
#else
    return JNI_FALSE;
#endif /* HAVE_BLUETOOTH */
}

static jboolean setUpListeningSocketsNative(JNIEnv* env, jobject object) {
    LOGV("%s", __FUNCTION__);
#ifdef HAVE_BLUETOOTH
    native_data_t *nat = get_native_data(env, object);

    nat->hf_ag_rfcomm_sock =
        setup_listening_socket(nat->hcidev, nat->hf_ag_rfcomm_channel);
    if (nat->hf_ag_rfcomm_sock < 0)
        return JNI_FALSE;

    nat->hs_ag_rfcomm_sock =
        setup_listening_socket(nat->hcidev, nat->hs_ag_rfcomm_channel);
    if (nat->hs_ag_rfcomm_sock < 0) {
        close(nat->hf_ag_rfcomm_sock);
        nat->hf_ag_rfcomm_sock = -1;
        return JNI_FALSE;
    }

    return JNI_TRUE;
#else
    return JNI_FALSE;
#endif /* HAVE_BLUETOOTH */
}

#ifdef HAVE_BLUETOOTH
static int setup_listening_socket(int dev, int channel) {
    struct sockaddr_rc laddr;
    int sk, lm;

    sk = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
    if (sk < 0) {
        LOGE("Can't create RFCOMM socket");
        return -1;
    }

    if (debug_no_encrypt()) {
        lm = RFCOMM_LM_AUTH;
    } else {
        lm = RFCOMM_LM_AUTH | RFCOMM_LM_ENCRYPT;
    }

    if (lm && setsockopt(sk, SOL_RFCOMM, RFCOMM_LM, &lm, sizeof(lm)) < 0) {
        LOGE("Can't set RFCOMM link mode");
        close(sk);
        return -1;
    }

    laddr.rc_family = AF_BLUETOOTH;
    bdaddr_t any = android_bluetooth_bdaddr_any();
    memcpy(&laddr.rc_bdaddr, &any, sizeof(bdaddr_t));
    laddr.rc_channel = channel;

    if (bind(sk, (struct sockaddr *)&laddr, sizeof(laddr)) < 0) {
        LOGE("Can't bind RFCOMM socket");
        close(sk);
        return -1;
    }

    listen(sk, 10);
    return sk;
}
#endif /* HAVE_BLUETOOTH */

/*
    private native void tearDownListeningSocketsNative();
*/
static void tearDownListeningSocketsNative(JNIEnv *env, jobject object) {
    LOGV("%s", __FUNCTION__);
#ifdef HAVE_BLUETOOTH
    native_data_t *nat = get_native_data(env, object);

    if (nat->hf_ag_rfcomm_sock > 0) {
        if (close(nat->hf_ag_rfcomm_sock) < 0) {
            LOGE("Could not close HF server socket: %s (%d)\n",
                 strerror(errno), errno);
        }
        nat->hf_ag_rfcomm_sock = -1;
    }
    if (nat->hs_ag_rfcomm_sock > 0) {
        if (close(nat->hs_ag_rfcomm_sock) < 0) {
            LOGE("Could not close HS server socket: %s (%d)\n",
                 strerror(errno), errno);
        }
        nat->hs_ag_rfcomm_sock = -1;
    }
#endif /* HAVE_BLUETOOTH */
}

static JNINativeMethod sMethods[] = {
     /* name, signature, funcPtr */

    {"classInitNative", "()V", (void*)classInitNative},
    {"initializeNativeDataNative", "()V", (void *)initializeNativeDataNative},
    {"cleanupNativeDataNative", "()V", (void *)cleanupNativeDataNative},

    {"setUpListeningSocketsNative", "()Z", (void *)setUpListeningSocketsNative},
    {"tearDownListeningSocketsNative", "()V", (void *)tearDownListeningSocketsNative},
    {"waitForHandsfreeConnectNative", "(I)Z", (void *)waitForHandsfreeConnectNative},
};

int register_android_bluetooth_BluetoothAudioGateway(JNIEnv *env) {
    return AndroidRuntime::registerNativeMethods(env,
            "android/bluetooth/BluetoothAudioGateway", sMethods,
            NELEM(sMethods));
}

} /* namespace android */