/*
 * 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"

extern uint8_t device_connected_flag;

namespace android {

extern void nfc_jni_restart_discovery_locked(struct nfc_jni_native_data *nat);

/*
 * Callbacks
 */
static void nfc_jni_presence_check_callback(void* pContext, NFCSTATUS status)
{   
   struct nfc_jni_callback_data * pCallbackData = (struct nfc_jni_callback_data *) pContext;
   LOG_CALLBACK("nfc_jni_presence_check_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,
                                     phLibNfc_Handle hRemoteDev,
                                     phLibNfc_sRemoteDevInformation_t *psRemoteDevInfo, NFCSTATUS status)
{   
   struct nfc_jni_callback_data * pCallbackData = (struct nfc_jni_callback_data *) pContext;
   phNfc_sData_t * psGeneralBytes = (phNfc_sData_t *)pCallbackData->pContext;
   LOG_CALLBACK("nfc_jni_connect_callback", status);
   
   if(status == NFCSTATUS_SUCCESS)
   {
      psGeneralBytes->length = psRemoteDevInfo->RemoteDevInfo.NfcIP_Info.ATRInfo_Length;
      psGeneralBytes->buffer = (uint8_t*)malloc(psRemoteDevInfo->RemoteDevInfo.NfcIP_Info.ATRInfo_Length);
      psGeneralBytes->buffer = psRemoteDevInfo->RemoteDevInfo.NfcIP_Info.ATRInfo;
   }
   
   /* Report the callback status and wake up the caller */
   pCallbackData->status = status;
   sem_post(&pCallbackData->sem);
}

static void nfc_jni_disconnect_callback(void *pContext, phLibNfc_Handle hRemoteDev, 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_receive_callback(void *pContext, phNfc_sData_t *data, NFCSTATUS status)
{
   struct nfc_jni_callback_data * pCallbackData = (struct nfc_jni_callback_data *) pContext;
   phNfc_sData_t **ptr = (phNfc_sData_t **)pCallbackData->pContext;
   LOG_CALLBACK("nfc_jni_receive_callback", status);

   if(status == NFCSTATUS_SUCCESS)
   {
      *ptr = data;
   }
   else
   {
      *ptr = NULL;
   }

   /* 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_send_callback", status);

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

/*
 * Functions
 */

static void nfc_jni_transceive_callback(void *pContext,
  phLibNfc_Handle handle, phNfc_sData_t *pResBuffer, NFCSTATUS status)
{
   struct nfc_jni_callback_data * pCallbackData = (struct nfc_jni_callback_data *) pContext;
   LOG_CALLBACK("nfc_jni_transceive_callback", status);

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

static jboolean com_android_nfc_NativeP2pDevice_doConnect(JNIEnv *e, jobject o)
{
    phLibNfc_Handle handle = 0;
    NFCSTATUS status;
    jboolean result = JNI_FALSE;
    struct nfc_jni_callback_data cb_data;

    jclass target_cls = NULL;
    jobject tag;
    jmethodID ctor;
    jfieldID f;
    jbyteArray generalBytes = NULL;
    phNfc_sData_t sGeneralBytes;
    unsigned int i;

    CONCURRENCY_LOCK();

    handle = nfc_jni_get_p2p_device_handle(e, o);

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

    TRACE("phLibNfc_RemoteDev_Connect(P2P)");
    REENTRANCE_LOCK();
    status = phLibNfc_RemoteDev_Connect(handle, nfc_jni_connect_callback, (void*)&cb_data);
    REENTRANCE_UNLOCK();
    if(status != NFCSTATUS_PENDING)
    {
      LOGE("phLibNfc_RemoteDev_Connect(P2P) returned 0x%04x[%s]", status, nfc_jni_get_status_name(status));
      goto clean_and_return;
    }
    TRACE("phLibNfc_RemoteDev_Connect(P2P) returned 0x%04x[%s]", status, nfc_jni_get_status_name(status));

    /* 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;
    }

    /* Set General Bytes */
    target_cls = e->GetObjectClass(o);

    f = e->GetFieldID(target_cls, "mGeneralBytes", "[B");

    TRACE("General Bytes Length = %d", sGeneralBytes.length);
    TRACE("General Bytes =");
    for(i=0;i<sGeneralBytes.length;i++)
    {
      TRACE("0x%02x ", sGeneralBytes.buffer[i]);
    }

    generalBytes = e->NewByteArray(sGeneralBytes.length);

    e->SetByteArrayRegion(generalBytes, 0,
                         sGeneralBytes.length, 
                         (jbyte *)sGeneralBytes.buffer);

    e->SetObjectField(o, f, generalBytes);

    result = JNI_TRUE;

clean_and_return:
    if (result != JNI_TRUE)
    {
       /* Restart the polling loop if the connection failed */
       nfc_jni_restart_discovery_locked(nfc_jni_get_nat_ext(e));
    }
    nfc_cb_data_deinit(&cb_data);
    CONCURRENCY_UNLOCK();
    return result;
}

static jboolean com_android_nfc_NativeP2pDevice_doDisconnect(JNIEnv *e, jobject o)
{
    phLibNfc_Handle     handle = 0;
    jboolean            result = JNI_FALSE;
    NFCSTATUS           status;
    struct nfc_jni_callback_data cb_data;

    CONCURRENCY_LOCK();

    handle = nfc_jni_get_p2p_device_handle(e, o);

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

    /* Disconnect */
    TRACE("Disconnecting from target (handle = 0x%x)", handle);

    /* NativeNfcTag waits for tag to leave the field here with presence check.
     * We do not in P2P path because presence check is not safe while transceive may be
     * in progress.
     */

    TRACE("phLibNfc_RemoteDev_Disconnect()");
    REENTRANCE_LOCK();
    status = phLibNfc_RemoteDev_Disconnect(handle, NFC_DISCOVERY_CONTINUE,nfc_jni_disconnect_callback, (void *)&cb_data);
    REENTRANCE_UNLOCK();
    if(status != NFCSTATUS_PENDING)
    {
        LOGE("phLibNfc_RemoteDev_Disconnect() returned 0x%04x[%s]", status, nfc_jni_get_status_name(status));
        if(status == NFCSTATUS_TARGET_NOT_CONNECTED)
        {
            LOGE("phLibNfc_RemoteDev_Disconnect() failed: Target not connected");
        }
        else
        {
            LOGE("phLibNfc_RemoteDev_Disconnect() failed");
            nfc_jni_restart_discovery_locked(nfc_jni_get_nat_ext(e));
        }

        goto clean_and_return;
    }
    TRACE("phLibNfc_RemoteDev_Disconnect() returned 0x%04x[%s]", status, nfc_jni_get_status_name(status));

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

    /* Disconnect Status */
    if(cb_data.status != NFCSTATUS_SUCCESS)
    {
        goto clean_and_return;
    }

    result = JNI_TRUE;

clean_and_return:
    /* Reset device connected flag */
    device_connected_flag = 0;
    nfc_cb_data_deinit(&cb_data);
    CONCURRENCY_UNLOCK();
    return result;
}

static jbyteArray com_android_nfc_NativeP2pDevice_doTransceive(JNIEnv *e,
   jobject o, jbyteArray data)
{
   NFCSTATUS status;
   uint8_t offset = 2;
   uint8_t *buf;
   uint32_t buflen;
   phLibNfc_sTransceiveInfo_t transceive_info;
   jbyteArray result = NULL;
   phLibNfc_Handle handle = nfc_jni_get_p2p_device_handle(e, o);
   phNfc_sData_t * receive_buffer = NULL;
   struct nfc_jni_callback_data cb_data;

   CONCURRENCY_LOCK();

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

   /* Transceive*/
   TRACE("Transceive data to target (handle = 0x%x)", handle);

   buf = (uint8_t *)e->GetByteArrayElements(data, NULL);
   buflen = (uint32_t)e->GetArrayLength(data);
   
   TRACE("Buffer Length = %d\n", buflen);

   transceive_info.sSendData.buffer = buf; //+ offset;
   transceive_info.sSendData.length = buflen; //- offset;
   transceive_info.sRecvData.buffer = (uint8_t*)malloc(1024);
   transceive_info.sRecvData.length = 1024;

   if(transceive_info.sRecvData.buffer == NULL)
   {
      goto clean_and_return;
   }

   TRACE("phLibNfc_RemoteDev_Transceive(P2P)");
   REENTRANCE_LOCK();
   status = phLibNfc_RemoteDev_Transceive(handle, &transceive_info, nfc_jni_transceive_callback, (void *)&cb_data);
   REENTRANCE_UNLOCK();
   if(status != NFCSTATUS_PENDING)
   {
      LOGE("phLibNfc_RemoteDev_Transceive(P2P) returned 0x%04x[%s]", status, nfc_jni_get_status_name(status));
      goto clean_and_return;
   }
   TRACE("phLibNfc_RemoteDev_Transceive(P2P) returned 0x%04x[%s]", status, nfc_jni_get_status_name(status));

   /* 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;
   }

   /* Copy results back to Java */
   result = e->NewByteArray(receive_buffer->length);
   if(result != NULL)
      e->SetByteArrayRegion(result, 0,
         receive_buffer->length,
         (jbyte *)receive_buffer->buffer);

clean_and_return:
   if(transceive_info.sRecvData.buffer != NULL)
   {
      free(transceive_info.sRecvData.buffer);
   }

   e->ReleaseByteArrayElements(data,
      (jbyte *)transceive_info.sSendData.buffer, JNI_ABORT);

   nfc_cb_data_deinit(&cb_data);

   CONCURRENCY_UNLOCK();

   return result;
}


static jbyteArray com_android_nfc_NativeP2pDevice_doReceive(
   JNIEnv *e, jobject o)
{
   NFCSTATUS status;
   struct timespec ts;
   phLibNfc_Handle handle;
   jbyteArray buf = NULL;
   static phNfc_sData_t *data;
   struct nfc_jni_callback_data cb_data;

   CONCURRENCY_LOCK();

   handle = nfc_jni_get_p2p_device_handle(e, o);
   
   /* Create the local semaphore */
   if (!nfc_cb_data_init(&cb_data, (void*)data))
   {
      goto clean_and_return;
   }

   /* Receive */
   TRACE("phLibNfc_RemoteDev_Receive()");
   REENTRANCE_LOCK();
   status = phLibNfc_RemoteDev_Receive(handle, nfc_jni_receive_callback,(void *)&cb_data);
   REENTRANCE_UNLOCK();
   if(status != NFCSTATUS_PENDING)
   {
      LOGE("phLibNfc_RemoteDev_Receive() returned 0x%04x[%s]", status, nfc_jni_get_status_name(status));
      goto clean_and_return;   
   }
   TRACE("phLibNfc_RemoteDev_Receive() returned 0x%04x[%s]", status, nfc_jni_get_status_name(status));

   /* 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(data == NULL)
   {
      goto clean_and_return;
   }

   buf = e->NewByteArray(data->length);
   e->SetByteArrayRegion(buf, 0, data->length, (jbyte *)data->buffer);

clean_and_return:
   nfc_cb_data_deinit(&cb_data);
   CONCURRENCY_UNLOCK();
   return buf;
}

static jboolean com_android_nfc_NativeP2pDevice_doSend(
   JNIEnv *e, jobject o, jbyteArray buf)
{
   NFCSTATUS status;
   phNfc_sData_t data;
   jboolean result = JNI_FALSE;
   struct nfc_jni_callback_data cb_data;
   
   phLibNfc_Handle handle = nfc_jni_get_p2p_device_handle(e, o);
   
   CONCURRENCY_LOCK();

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

   /* Send */
   TRACE("Send data to the Initiator (handle = 0x%x)", handle);

   data.length = (uint32_t)e->GetArrayLength(buf);
   data.buffer = (uint8_t *)e->GetByteArrayElements(buf, NULL);

   TRACE("phLibNfc_RemoteDev_Send()");
   REENTRANCE_LOCK();
   status = phLibNfc_RemoteDev_Send(handle, &data, nfc_jni_send_callback,(void *)&cb_data);
   REENTRANCE_UNLOCK();
   if(status != NFCSTATUS_PENDING)
   {
      LOGE("phLibNfc_RemoteDev_Send() returned 0x%04x[%s]", status, nfc_jni_get_status_name(status));
      goto clean_and_return;   
   }
   TRACE("phLibNfc_RemoteDev_Send() returned 0x%04x[%s]", status, nfc_jni_get_status_name(status));

   /* 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 (result != JNI_TRUE)
   {
      e->ReleaseByteArrayElements(buf, (jbyte *)data.buffer, JNI_ABORT);
   }
   nfc_cb_data_deinit(&cb_data);
   CONCURRENCY_UNLOCK();
   return result;
}

/*
 * JNI registration.
 */
static JNINativeMethod gMethods[] =
{
   {"doConnect", "()Z",
      (void *)com_android_nfc_NativeP2pDevice_doConnect},
   {"doDisconnect", "()Z",
      (void *)com_android_nfc_NativeP2pDevice_doDisconnect},
   {"doTransceive", "([B)[B",
      (void *)com_android_nfc_NativeP2pDevice_doTransceive},
   {"doReceive", "()[B",
      (void *)com_android_nfc_NativeP2pDevice_doReceive},
   {"doSend", "([B)Z",
      (void *)com_android_nfc_NativeP2pDevice_doSend},
};

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

} // namepspace android