/*
 * Copyright (C) 2010 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 <semaphore.h>
#include <errno.h>

#include "com_android_nfc.h"

namespace android {

/*
 * Callbacks
 */
 
static void nfc_jni_disconnect_callback(void*        pContext,
                                               NFCSTATUS    status)
{
   struct nfc_jni_callback_data * pCallbackData = (struct nfc_jni_callback_data *) pContext;
   LOG_CALLBACK("nfc_jni_disconnect_callback", status);

   /* Report the callback status and wake up the caller */
   pCallbackData->status = status;
   sem_post(&pCallbackData->sem);
}

 
static void nfc_jni_connect_callback(void* pContext, uint8_t nErrCode, NFCSTATUS status)
{
   struct nfc_jni_callback_data * pCallbackData = (struct nfc_jni_callback_data *) pContext;
   LOG_CALLBACK("nfc_jni_llcp_connect_callback", status);

   if(status == NFCSTATUS_SUCCESS)
   {
      TRACE("Socket connected\n");
   }
   else
   {
      LOGD("Socket not connected:");
      switch(nErrCode)
      {
         case PHFRINFC_LLCP_DM_OPCODE_SAP_NOT_ACTIVE:
            {
               LOGD("> SAP NOT ACTIVE\n");
            }break;

         case PHFRINFC_LLCP_DM_OPCODE_SAP_NOT_FOUND:
            {
               LOGD("> SAP NOT FOUND\n");
            }break;

         case PHFRINFC_LLCP_DM_OPCODE_CONNECT_REJECTED:
            {
               LOGD("> CONNECT REJECTED\n");
            }break;

         case PHFRINFC_LLCP_DM_OPCODE_CONNECT_NOT_ACCEPTED:
            {
               LOGD("> CONNECT NOT ACCEPTED\n");
            }break;

         case PHFRINFC_LLCP_DM_OPCODE_SOCKET_NOT_AVAILABLE:
            {
               LOGD("> SOCKET NOT AVAILABLE\n");
            }break;
      }
   }
   
   /* Report the callback status and wake up the caller */
   pCallbackData->status = status;
   sem_post(&pCallbackData->sem);
} 



 
static void nfc_jni_receive_callback(void* pContext, NFCSTATUS    status)
{
   struct nfc_jni_callback_data * pCallbackData = (struct nfc_jni_callback_data *) pContext;
   LOG_CALLBACK("nfc_jni_llcp_receive_callback", status);
   
   /* Report the callback status and wake up the caller */
   pCallbackData->status = status;
   sem_post(&pCallbackData->sem);
}

static void nfc_jni_send_callback(void *pContext, NFCSTATUS status)
{
   struct nfc_jni_callback_data * pCallbackData = (struct nfc_jni_callback_data *) pContext;
   LOG_CALLBACK("nfc_jni_llcp_send_callback", status);

   /* Report the callback status and wake up the caller */
   pCallbackData->status = status;
   sem_post(&pCallbackData->sem);
}

/*
 * Methods
 */
static jboolean com_android_nfc_NativeLlcpSocket_doConnect(JNIEnv *e, jobject o, jint nSap)
{
   NFCSTATUS ret;
   struct timespec ts;
   phLibNfc_Handle hRemoteDevice;
   phLibNfc_Handle hLlcpSocket;
   struct nfc_jni_callback_data cb_data;
   jboolean result = JNI_FALSE;

   /* Retrieve handles */
   hRemoteDevice = nfc_jni_get_p2p_device_handle(e,o);
   hLlcpSocket = nfc_jni_get_nfc_socket_handle(e,o);
   
   /* Create the local semaphore */
   if (!nfc_cb_data_init(&cb_data, NULL))
   {
      goto clean_and_return;
   }

   TRACE("phLibNfc_Llcp_Connect(%d)",nSap);
   REENTRANCE_LOCK();
   ret = phLibNfc_Llcp_Connect(hRemoteDevice,
                               hLlcpSocket,
                               nSap,
                               nfc_jni_connect_callback,
                               (void*)&cb_data);
   REENTRANCE_UNLOCK();
   if(ret != NFCSTATUS_PENDING)
   {
      LOGE("phLibNfc_Llcp_Connect(%d) returned 0x%04x[%s]", nSap, ret, nfc_jni_get_status_name(ret));
      goto clean_and_return;
   }
   TRACE("phLibNfc_Llcp_Connect(%d) returned 0x%04x[%s]", nSap, ret, nfc_jni_get_status_name(ret));

   /* Wait for callback response */
   if(sem_wait(&cb_data.sem))
   {
      LOGE("Failed to wait for semaphore (errno=0x%08x)", errno);
      goto clean_and_return;
   }

   if(cb_data.status != NFCSTATUS_SUCCESS)
   {
      LOGW("LLCP Connect request failed");
      goto clean_and_return;
   }

   result = JNI_TRUE;

clean_and_return:
   nfc_cb_data_deinit(&cb_data);
   return result;
}

static jboolean com_android_nfc_NativeLlcpSocket_doConnectBy(JNIEnv *e, jobject o, jstring sn)
{
   NFCSTATUS ret;
   struct timespec ts;
   phNfc_sData_t serviceName;
   phLibNfc_Handle hRemoteDevice;
   phLibNfc_Handle hLlcpSocket;
   struct nfc_jni_callback_data cb_data;
   jboolean result = JNI_FALSE;

   /* Retrieve handles */
   hRemoteDevice = nfc_jni_get_p2p_device_handle(e,o);
   hLlcpSocket = nfc_jni_get_nfc_socket_handle(e,o);

   /* Create the local semaphore */
   if (!nfc_cb_data_init(&cb_data, NULL))
   {
      goto clean_and_return;
   }

   /* Service socket */
   serviceName.buffer = (uint8_t*)e->GetStringUTFChars(sn, NULL);
   serviceName.length = (uint32_t)e->GetStringUTFLength(sn);
   
   TRACE("phLibNfc_Llcp_ConnectByUri()");
   REENTRANCE_LOCK();
   ret = phLibNfc_Llcp_ConnectByUri(hRemoteDevice,
                                    hLlcpSocket,
                                    &serviceName,
                                    nfc_jni_connect_callback,
                                    (void*)&cb_data);
   REENTRANCE_UNLOCK();
   if(ret != NFCSTATUS_PENDING)
   {
      LOGE("phLibNfc_Llcp_ConnectByUri() returned 0x%04x[%s]", ret, nfc_jni_get_status_name(ret));
      goto clean_and_return;
   }
   TRACE("phLibNfc_Llcp_ConnectByUri() returned 0x%04x[%s]", ret, nfc_jni_get_status_name(ret));

   /* Wait for callback response */
   if(sem_wait(&cb_data.sem))
   {
      LOGE("Failed to wait for semaphore (errno=0x%08x)", errno);
      goto clean_and_return;
   }

   if(cb_data.status != NFCSTATUS_SUCCESS)
   {
      goto clean_and_return;
   }

   result = JNI_TRUE;

clean_and_return:
   nfc_cb_data_deinit(&cb_data);
   return result;
}

static jboolean com_android_nfc_NativeLlcpSocket_doClose(JNIEnv *e, jobject o)
{
   NFCSTATUS ret;
   phLibNfc_Handle hLlcpSocket;

   /* Retrieve socket handle */
   hLlcpSocket = nfc_jni_get_nfc_socket_handle(e,o);

   TRACE("phLibNfc_Llcp_Close()");
   REENTRANCE_LOCK();
   ret = phLibNfc_Llcp_Close(hLlcpSocket);
   REENTRANCE_UNLOCK();
   if(ret != NFCSTATUS_SUCCESS)
   {
      LOGE("phLibNfc_Llcp_Close() returned 0x%04x[%s]", ret, nfc_jni_get_status_name(ret));
      return FALSE; 
   }
   TRACE("phLibNfc_Llcp_Close() returned 0x%04x[%s]", ret, nfc_jni_get_status_name(ret));
   return TRUE;
}

static jboolean com_android_nfc_NativeLlcpSocket_doSend(JNIEnv *e, jobject o, jbyteArray  data)
{
   NFCSTATUS ret;
   struct timespec ts;
   phLibNfc_Handle hRemoteDevice;
   phLibNfc_Handle hLlcpSocket;
   phNfc_sData_t sSendBuffer = {NULL, 0};
   struct nfc_jni_callback_data cb_data;
   jboolean result = JNI_FALSE;
   
   /* Retrieve handles */
   hRemoteDevice = nfc_jni_get_p2p_device_handle(e,o);
   hLlcpSocket = nfc_jni_get_nfc_socket_handle(e,o);
   
   /* Create the local semaphore */
   if (!nfc_cb_data_init(&cb_data, NULL))
   {
      goto clean_and_return;
   }

   sSendBuffer.buffer = (uint8_t*)e->GetByteArrayElements(data, NULL);
   sSendBuffer.length = (uint32_t)e->GetArrayLength(data);
   
   TRACE("phLibNfc_Llcp_Send()");
   REENTRANCE_LOCK();
   ret = phLibNfc_Llcp_Send(hRemoteDevice,
                            hLlcpSocket,
                            &sSendBuffer,
                            nfc_jni_send_callback,
                            (void*)&cb_data);
   REENTRANCE_UNLOCK();
   if(ret != NFCSTATUS_PENDING)
   {
      LOGE("phLibNfc_Llcp_Send() returned 0x%04x[%s]", ret, nfc_jni_get_status_name(ret));
      goto clean_and_return;
   } 
   TRACE("phLibNfc_Llcp_Send() returned 0x%04x[%s]", ret, nfc_jni_get_status_name(ret));

   /* Wait for callback response */
   if(sem_wait(&cb_data.sem))
   {
      LOGE("Failed to wait for semaphore (errno=0x%08x)", errno);
      goto clean_and_return;
   }

   if(cb_data.status != NFCSTATUS_SUCCESS)
   {
       goto clean_and_return;
   }

   result = JNI_TRUE;

clean_and_return:
   if (sSendBuffer.buffer != NULL)
   {
      e->ReleaseByteArrayElements(data, (jbyte*)sSendBuffer.buffer, JNI_ABORT);
   }
   nfc_cb_data_deinit(&cb_data);
   return result;
}

static jint com_android_nfc_NativeLlcpSocket_doReceive(JNIEnv *e, jobject o, jbyteArray  buffer)
{
   NFCSTATUS ret;
   struct timespec ts;
   phLibNfc_Handle hRemoteDevice;
   phLibNfc_Handle hLlcpSocket;
   phNfc_sData_t sReceiveBuffer = {NULL, 0};
   struct nfc_jni_callback_data cb_data;
   jint result = -1;
   
   /* Retrieve handles */
   hRemoteDevice = nfc_jni_get_p2p_device_handle(e,o);
   hLlcpSocket = nfc_jni_get_nfc_socket_handle(e,o);
   
   /* Create the local semaphore */
   if (!nfc_cb_data_init(&cb_data, NULL))
   {
      goto clean_and_return;
   }

   sReceiveBuffer.buffer = (uint8_t*)e->GetByteArrayElements(buffer, NULL);
   sReceiveBuffer.length = (uint32_t)e->GetArrayLength(buffer);
   
   TRACE("phLibNfc_Llcp_Recv()");
   REENTRANCE_LOCK();
   ret = phLibNfc_Llcp_Recv(hRemoteDevice,
                            hLlcpSocket,
                            &sReceiveBuffer,
                            nfc_jni_receive_callback,
                            (void*)&cb_data);
   REENTRANCE_UNLOCK();
   if(ret == NFCSTATUS_PENDING)
   {
      /* Wait for callback response */
      if(sem_wait(&cb_data.sem))
      {
         LOGE("Failed to wait for semaphore (errno=0x%08x)", errno);
         goto clean_and_return;
      }

      if(cb_data.status == NFCSTATUS_SUCCESS)
      {
         result = sReceiveBuffer.length;
      }
   }
   else if (ret == NFCSTATUS_SUCCESS)
   {
      result = sReceiveBuffer.length;
   }
   else
   {
      /* Return status should be either SUCCESS or PENDING */
      LOGE("phLibNfc_Llcp_Recv() returned 0x%04x[%s]", ret, nfc_jni_get_status_name(ret));
      goto clean_and_return;
   }
   TRACE("phLibNfc_Llcp_Recv() returned 0x%04x[%s]", ret, nfc_jni_get_status_name(ret));

clean_and_return:
   if (sReceiveBuffer.buffer != NULL)
   {
      e->ReleaseByteArrayElements(buffer, (jbyte*)sReceiveBuffer.buffer, 0);
   }
   nfc_cb_data_deinit(&cb_data);
   return result;
}

static jint com_android_nfc_NativeLlcpSocket_doGetRemoteSocketMIU(JNIEnv *e, jobject o)
{
   NFCSTATUS ret;
   phLibNfc_Handle hRemoteDevice;
   phLibNfc_Handle hLlcpSocket;
   phLibNfc_Llcp_sSocketOptions_t   remoteSocketOption;
   
   /* Retrieve handles */
   hRemoteDevice = nfc_jni_get_p2p_device_handle(e,o);
   hLlcpSocket = nfc_jni_get_nfc_socket_handle(e,o);   
   
   TRACE("phLibNfc_Llcp_SocketGetRemoteOptions(MIU)");
   REENTRANCE_LOCK();
   ret  = phLibNfc_Llcp_SocketGetRemoteOptions(hRemoteDevice,
                                               hLlcpSocket,
                                               &remoteSocketOption);
   REENTRANCE_UNLOCK();
   if(ret == NFCSTATUS_SUCCESS)
   {
      TRACE("phLibNfc_Llcp_SocketGetRemoteOptions(MIU) returned 0x%04x[%s]", ret, nfc_jni_get_status_name(ret));
      return remoteSocketOption.miu;
   }
   else
   {
      LOGW("phLibNfc_Llcp_SocketGetRemoteOptions(MIU) returned 0x%04x[%s]", ret, nfc_jni_get_status_name(ret));
      return 0;
   }
}

static jint com_android_nfc_NativeLlcpSocket_doGetRemoteSocketRW(JNIEnv *e, jobject o)
{
   NFCSTATUS ret;
   phLibNfc_Handle hRemoteDevice;
   phLibNfc_Handle hLlcpSocket;
   phLibNfc_Llcp_sSocketOptions_t   remoteSocketOption;

   /* Retrieve handles */
   hRemoteDevice = nfc_jni_get_p2p_device_handle(e,o);
   hLlcpSocket = nfc_jni_get_nfc_socket_handle(e,o);   

   TRACE("phLibNfc_Llcp_SocketGetRemoteOptions(RW)");
   REENTRANCE_LOCK();
   ret  = phLibNfc_Llcp_SocketGetRemoteOptions(hRemoteDevice,
                                               hLlcpSocket,
                                               &remoteSocketOption);
   REENTRANCE_UNLOCK();
   if(ret == NFCSTATUS_SUCCESS)
   {
      TRACE("phLibNfc_Llcp_SocketGetRemoteOptions(RW) returned 0x%04x[%s]", ret, nfc_jni_get_status_name(ret));
      return remoteSocketOption.rw;
   }
   else
   {
      LOGW("phLibNfc_Llcp_SocketGetRemoteOptions(RW) returned 0x%04x[%s]", ret, nfc_jni_get_status_name(ret));
      return 0;
   }
}


/*
 * JNI registration.
 */
static JNINativeMethod gMethods[] =
{
   {"doConnect", "(I)Z",
      (void *)com_android_nfc_NativeLlcpSocket_doConnect},

   {"doConnectBy", "(Ljava/lang/String;)Z",
      (void *)com_android_nfc_NativeLlcpSocket_doConnectBy},
      
   {"doClose", "()Z",
      (void *)com_android_nfc_NativeLlcpSocket_doClose},
      
   {"doSend", "([B)Z",
      (void *)com_android_nfc_NativeLlcpSocket_doSend},

   {"doReceive", "([B)I",
      (void *)com_android_nfc_NativeLlcpSocket_doReceive},
      
   {"doGetRemoteSocketMiu", "()I",
      (void *)com_android_nfc_NativeLlcpSocket_doGetRemoteSocketMIU},
           
   {"doGetRemoteSocketRw", "()I",
      (void *)com_android_nfc_NativeLlcpSocket_doGetRemoteSocketRW},
};


int register_com_android_nfc_NativeLlcpSocket(JNIEnv *e)
{
   return jniRegisterNativeMethods(e,
      "com/android/nfc/nxp/NativeLlcpSocket",gMethods, NELEM(gMethods));
}

} // namespace android