/*
 * Copyright (C) 2012 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.
 */

#include <android-base/stringprintf.h>
#include <base/logging.h>
#include <errno.h>
#include <malloc.h>
#include <nativehelper/ScopedLocalRef.h>
#include <nativehelper/ScopedPrimitiveArray.h>
#include <semaphore.h>
#include <string.h>
#include "JavaClassConstants.h"
#include "NfcJniUtil.h"
#include "nfa_api.h"
#include "nfa_p2p_api.h"

using android::base::StringPrintf;

extern bool nfc_debug_enabled;

namespace android {

/*****************************************************************************
**
** private variables and functions
**
*****************************************************************************/
static sem_t sConnlessRecvSem;
static jboolean sConnlessRecvWaitingForData = JNI_FALSE;
static uint8_t* sConnlessRecvBuf = NULL;
static uint32_t sConnlessRecvLen = 0;
static uint32_t sConnlessRecvRemoteSap = 0;

/*******************************************************************************
**
** Function:        nativeLlcpConnectionlessSocket_doSendTo
**
** Description:     Send data to peer.
**                  e: JVM environment.
**                  o: Java object.
**                  nsap: service access point.
**                  data: buffer for data.
**
** Returns:         True if ok.
**
*******************************************************************************/
static jboolean nativeLlcpConnectionlessSocket_doSendTo(JNIEnv* e, jobject o,
                                                        jint nsap,
                                                        jbyteArray data) {
  DLOG_IF(INFO, nfc_debug_enabled)
      << StringPrintf("%s: nsap = %d", __func__, nsap);

  ScopedLocalRef<jclass> c(e, e->GetObjectClass(o));
  jfieldID f = e->GetFieldID(c.get(), "mHandle", "I");
  jint handle = e->GetIntField(o, f);

  ScopedByteArrayRO bytes(e, data);
  if (bytes.get() == NULL) {
    return JNI_FALSE;
  }
  size_t byte_count = bytes.size();

  DLOG_IF(INFO, nfc_debug_enabled)
      << StringPrintf("NFA_P2pSendUI: len = %zu", byte_count);
  uint8_t* raw_ptr = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(
      &bytes[0]));  // TODO: API bug; NFA_P2pSendUI should take const*!
  tNFA_STATUS status =
      NFA_P2pSendUI((tNFA_HANDLE)handle, nsap, byte_count, raw_ptr);

  DLOG_IF(INFO, nfc_debug_enabled)
      << StringPrintf("%s: NFA_P2pSendUI done, status = %d", __func__, status);
  if (status != NFA_STATUS_OK) {
    LOG(ERROR) << StringPrintf("%s: NFA_P2pSendUI failed, status = %d",
                               __func__, status);
    return JNI_FALSE;
  }
  return JNI_TRUE;
}

/*******************************************************************************
**
** Function:        nativeLlcpConnectionlessSocket_receiveData
**
** Description:     Receive data from the stack.
**                  data: buffer contains data.
**                  len: length of data.
**                  remoteSap: remote service access point.
**
** Returns:         None
**
*******************************************************************************/
void nativeLlcpConnectionlessSocket_receiveData(uint8_t* data, uint32_t len,
                                                uint32_t remoteSap) {
  DLOG_IF(INFO, nfc_debug_enabled)
      << StringPrintf("%s: waiting for data = %d, len = %d", __func__,
                      sConnlessRecvWaitingForData, len);

  // Sanity...
  if (sConnlessRecvLen < len) {
    len = sConnlessRecvLen;
  }

  if (sConnlessRecvWaitingForData) {
    sConnlessRecvWaitingForData = JNI_FALSE;
    sConnlessRecvLen = len;
    memcpy(sConnlessRecvBuf, data, len);
    sConnlessRecvRemoteSap = remoteSap;

    sem_post(&sConnlessRecvSem);
  }
}

/*******************************************************************************
**
** Function:        connectionlessCleanup
**
** Description:     Free resources.
**
** Returns:         None
**
*******************************************************************************/
static jobject connectionlessCleanup() {
  sConnlessRecvWaitingForData = JNI_FALSE;
  sConnlessRecvLen = 0;
  if (sConnlessRecvBuf != NULL) {
    free(sConnlessRecvBuf);
    sConnlessRecvBuf = NULL;
  }
  return NULL;
}

/*******************************************************************************
**
** Function:        nativeLlcpConnectionlessSocket_abortWait
**
** Description:     Abort current operation and unblock threads.
**
** Returns:         None
**
*******************************************************************************/
void nativeLlcpConnectionlessSocket_abortWait() { sem_post(&sConnlessRecvSem); }

/*******************************************************************************
**
** Function:        nativeLlcpConnectionlessSocket_doReceiveFrom
**
** Description:     Receive data from a peer.
**                  e: JVM environment.
**                  o: Java object.
**                  linkMiu: max info unit
**
** Returns:         LlcpPacket Java object.
**
*******************************************************************************/
static jobject nativeLlcpConnectionlessSocket_doReceiveFrom(JNIEnv* e, jobject,
                                                            jint linkMiu) {
  DLOG_IF(INFO, nfc_debug_enabled)
      << StringPrintf("%s: linkMiu = %d", __func__, linkMiu);
  jobject llcpPacket = NULL;
  ScopedLocalRef<jclass> clsLlcpPacket(e, NULL);

  if (sConnlessRecvWaitingForData != JNI_FALSE) {
    DLOG_IF(INFO, nfc_debug_enabled)
        << StringPrintf("%s: Already waiting for incoming data", __func__);
    return NULL;
  }

  sConnlessRecvBuf = (uint8_t*)malloc(linkMiu);
  if (sConnlessRecvBuf == NULL) {
    DLOG_IF(INFO, nfc_debug_enabled) << StringPrintf(
        "%s: Failed to allocate %d bytes memory buffer", __func__, linkMiu);
    return NULL;
  }
  sConnlessRecvLen = linkMiu;

  // Create the write semaphore
  if (sem_init(&sConnlessRecvSem, 0, 0) == -1) {
    LOG(ERROR) << StringPrintf("%s: semaphore creation failed (errno=0x%08x)",
                               __func__, errno);
    return connectionlessCleanup();
  }

  sConnlessRecvWaitingForData = JNI_TRUE;

  // Wait for sConnlessRecvSem completion status
  if (sem_wait(&sConnlessRecvSem)) {
    LOG(ERROR) << StringPrintf(
        "%s: Failed to wait for write semaphore (errno=0x%08x)", __func__,
        errno);
    goto TheEnd;
  }

  // Create new LlcpPacket object
  if (nfc_jni_cache_object_local(e, "com/android/nfc/LlcpPacket",
                                 &(llcpPacket)) == -1) {
    LOG(ERROR) << StringPrintf("%s: Find LlcpPacket class error", __func__);
    return connectionlessCleanup();
  }

  // Get NativeConnectionless class object
  clsLlcpPacket.reset(e->GetObjectClass(llcpPacket));
  if (e->ExceptionCheck()) {
    e->ExceptionClear();
    LOG(ERROR) << StringPrintf("%s: Get Object class error", __func__);
    return connectionlessCleanup();
  }

  // Set Llcp Packet remote SAP
  jfieldID f;
  f = e->GetFieldID(clsLlcpPacket.get(), "mRemoteSap", "I");
  e->SetIntField(llcpPacket, f, (jbyte)sConnlessRecvRemoteSap);

  // Set Llcp Packet Buffer
  DLOG_IF(INFO, nfc_debug_enabled)
      << StringPrintf("%s: Received Llcp packet buffer size = %d\n", __func__,
                      sConnlessRecvLen);
  f = e->GetFieldID(clsLlcpPacket.get(), "mDataBuffer", "[B");

  {
    ScopedLocalRef<jbyteArray> receivedData(e,
                                            e->NewByteArray(sConnlessRecvLen));
    e->SetByteArrayRegion(receivedData.get(), 0, sConnlessRecvLen,
                          (jbyte*)sConnlessRecvBuf);
    e->SetObjectField(llcpPacket, f, receivedData.get());
  }

TheEnd:  // TODO: should all the "return connectionlessCleanup()"s in this
         // function jump here instead?
  connectionlessCleanup();
  if (sem_destroy(&sConnlessRecvSem)) {
    LOG(ERROR) << StringPrintf(
        "%s: Failed to destroy sConnlessRecvSem semaphore (errno=0x%08x)",
        __func__, errno);
  }
  return llcpPacket;
}

/*******************************************************************************
**
** Function:        nativeLlcpConnectionlessSocket_doClose
**
** Description:     Close socket.
**                  e: JVM environment.
**                  o: Java object.
**
** Returns:         True if ok.
**
*******************************************************************************/
static jboolean nativeLlcpConnectionlessSocket_doClose(JNIEnv* e, jobject o) {
  DLOG_IF(INFO, nfc_debug_enabled) << StringPrintf("%s", __func__);

  ScopedLocalRef<jclass> c(e, e->GetObjectClass(o));
  jfieldID f = e->GetFieldID(c.get(), "mHandle", "I");
  jint handle = e->GetIntField(o, f);

  tNFA_STATUS status = NFA_P2pDisconnect((tNFA_HANDLE)handle, FALSE);
  if (status != NFA_STATUS_OK) {
    LOG(ERROR) << StringPrintf("%s: disconnect failed, status = %d", __func__,
                               status);
    return JNI_FALSE;
  }
  return JNI_TRUE;
}

/*****************************************************************************
**
** Description:     JNI functions
**
*****************************************************************************/
static JNINativeMethod gMethods[] = {
    {"doSendTo", "(I[B)Z", (void*)nativeLlcpConnectionlessSocket_doSendTo},
    {"doReceiveFrom", "(I)Lcom/android/nfc/LlcpPacket;",
     (void*)nativeLlcpConnectionlessSocket_doReceiveFrom},
    {"doClose", "()Z", (void*)nativeLlcpConnectionlessSocket_doClose},
};

/*******************************************************************************
**
** Function:        register_com_android_nfc_NativeLlcpConnectionlessSocket
**
** Description:     Regisgter JNI functions with Java Virtual Machine.
**                  e: Environment of JVM.
**
** Returns:         Status of registration.
**
*******************************************************************************/
int register_com_android_nfc_NativeLlcpConnectionlessSocket(JNIEnv* e) {
  return jniRegisterNativeMethods(e, gNativeLlcpConnectionlessSocketClassName,
                                  gMethods, NELEM(gMethods));
}

}  // namespace android