/** ----------------------------------------------------------------------
 *
 * Copyright (C) 2013 ST Microelectronics S.A.
 *
 * 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 _GNU_SOURCE
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <linux/input.h> /* not required for all builds */
#include <poll.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <pthread.h>

#include "halcore.h"
#include "halcore_private.h"
#include "android_logmsg.h"

#define ST21NFC_MAGIC 0xEA

#define ST21NFC_GET_WAKEUP _IOR(ST21NFC_MAGIC, 0x01, unsigned int)
#define ST21NFC_PULSE_RESET _IOR(ST21NFC_MAGIC, 0x02, unsigned int)
#define ST21NFC_SET_POLARITY_RISING _IOR(ST21NFC_MAGIC, 0x03, unsigned int)
#define ST21NFC_SET_POLARITY_FALLING _IOR(ST21NFC_MAGIC, 0x04, unsigned int)
#define ST21NFC_SET_POLARITY_HIGH _IOR(ST21NFC_MAGIC, 0x05, unsigned int)
#define ST21NFC_SET_POLARITY_LOW _IOR(ST21NFC_MAGIC, 0x06, unsigned int)

#define LINUX_DBGBUFFER_SIZE 300

static int fidI2c = 0;
static int cmdPipe[2] = {0, 0};

static struct pollfd event_table[2];
static pthread_t threadHandle = (pthread_t)NULL;
pthread_mutex_t i2ctransport_mtx = PTHREAD_MUTEX_INITIALIZER;

/**************************************************************************************************
 *
 *                                      Private API Declaration
 *
 **************************************************************************************************/

static int i2cSetPolarity(int fid, bool low, bool edge);
static int i2cResetPulse(int fid);
static int i2cRead(int fid, uint8_t* pvBuffer, int length);
static int i2cGetGPIOState(int fid);
static int i2cWrite(int fd, const uint8_t* pvBuffer, int length);

/**************************************************************************************************
 *
 *                                      Public API Entry-Points
 *
 **************************************************************************************************/

/**
 * Worker thread for I2C data processing.
 * On exit of this thread, destroy the HAL thread instance.
 * @param arg  Handle of the HAL layer
 */
static void* I2cWorkerThread(void* arg)
{
    bool closeThread = false;
    HALHANDLE hHAL = (HALHANDLE)arg;
    STLOG_HAL_V("echo thread started...\n");
    bool readOk= false;

    do {
        event_table[0].fd = fidI2c;
        event_table[0].events = POLLIN;
        event_table[0].revents = 0;

        event_table[1].fd = cmdPipe[0];
        event_table[1].events = POLLIN;
        event_table[1].revents = 0;

        STLOG_HAL_D("echo thread go to sleep...\n");

        int poll_status = poll(event_table, 2, -1);

        if (-1 == poll_status) {
            STLOG_HAL_E("error in poll call\n");
            return 0;
        }

        if (event_table[0].revents & POLLIN) {
            STLOG_HAL_D("echo thread wakeup from chip...\n");

            uint8_t buffer[300];

            do {
                // load first four bytes:
                int bytesRead = i2cRead(fidI2c, buffer, 3);

                if (bytesRead == 3) {
                    if ((buffer[0] != 0x7E) && (buffer[1] != 0x7E)) {
                        readOk = true;
                    } else {
                        if (buffer[1] != 0x7E) {
                            STLOG_HAL_W("Idle data: 2nd byte is 0x%02x\n, reading next 2 bytes",
                                  buffer[1]);
                            buffer[0] = buffer[1];
                            buffer[1] = buffer[2];
                            bytesRead = i2cRead(fidI2c, buffer + 2, 1);
                            if (bytesRead == 1) {
                                readOk = true;
                            }
                        } else if (buffer[2] != 0x7E) {
                            STLOG_HAL_W("Idle data: 3rd byte is 0x%02x\n, reading next  byte",
                                  buffer[2]);
                            buffer[0] = buffer[2];
                            bytesRead = i2cRead(fidI2c, buffer + 1, 2);
                            if (bytesRead == 2) {
                                readOk = true;
                            }
                        } else {
                            STLOG_HAL_W("received idle data\n");
                        }
                    }

                    if (readOk == true) {
                        int remaining = buffer[2];

                        // read and pass to HALCore
                        bytesRead = i2cRead(fidI2c, buffer + 3, remaining);
                        if (bytesRead == remaining) {
                            DispHal("RX DATA", buffer, 3 + bytesRead);
                            HalSendUpstream(hHAL, buffer, 3 + bytesRead);
                        } else {
                            readOk = false;
                            STLOG_HAL_E("! didn't read expected bytes from i2c\n");
                        }
                    }

                } else {
                    STLOG_HAL_E("! didn't read 3 requested bytes from i2c\n");
                }

                readOk = false;
                memset(buffer, 0xca, sizeof(buffer));

                /* read while we have data available */
            } while (i2cGetGPIOState(fidI2c) == 1);
        }

        if (event_table[1].revents & POLLIN) {
            STLOG_HAL_V("thread received command.. \n");

            char cmd = 0;
            read(cmdPipe[0], &cmd, 1);

            switch (cmd) {
                case 'X':
                    STLOG_HAL_D("received close command\n");
                    closeThread = true;
                    break;

                case 'W': {
                    size_t length;
                    uint8_t buffer[MAX_BUFFER_SIZE];
                    STLOG_HAL_V("received write command\n");
                    read(cmdPipe[0], &length, sizeof(length));
                    if (length <= MAX_BUFFER_SIZE)
                      {
                        read(cmdPipe[0], buffer, length);
                        i2cWrite(fidI2c, buffer, length);
                      }
                    else {
                        STLOG_HAL_E("! received bigger data than expected!! Data not transmitted to NFCC \n");
                        size_t bytes_read = 1;
                        // Read all the data to empty but do not use it as not expected
                        while((bytes_read > 0) && (length > 0))
                          {
                            bytes_read = read(cmdPipe[0],buffer,MAX_BUFFER_SIZE);
                            length = length - bytes_read;
                          }
                    }
                }
                break;
            }
        }

    } while (!closeThread);

    close(fidI2c);
    close(cmdPipe[0]);
    close(cmdPipe[1]);

    HalDestroy(hHAL);
    STLOG_HAL_D("thread exit\n");
    return 0;
}

/**
 * Put command into queue for worker thread to process it.
 * @param x Command 'X' to close I2C layer or 'W' to write data down to I2C
 * layer followed by data frame
 * @param len Size of command or data
 * @return
 */
int I2cWriteCmd(const uint8_t* x, size_t len)
{
    return write(cmdPipe[1], x, len);
}

/**
 * Initialize the I2C layer.
 * @param dev NFC NCI device context, NFC callbacks for control/data, HAL handle
 * @param callb HAL Core callback upon reception on I2C
 * @param pHandle HAL context handle
 */
bool I2cOpenLayer(void* dev, HAL_CALLBACK callb, HALHANDLE* pHandle)
{
    uint32_t NoDbgFlag = HAL_FLAG_DEBUG;

      (void) pthread_mutex_lock(&i2ctransport_mtx);
    fidI2c = open("/dev/st21nfc", O_RDWR);
    if (fidI2c < 0) {
        STLOG_HAL_W("unable to open /dev/st21nfc\n");
        return false;
    }

    i2cSetPolarity(fidI2c, false, true);
    i2cResetPulse(fidI2c);

    if ((pipe(cmdPipe) == -1)) {
        STLOG_HAL_W("unable to open cmdpipe\n");
        return false;
    }

    *pHandle = HalCreate(dev, callb, NoDbgFlag);

    if (!*pHandle) {
        STLOG_HAL_E("failed to create NFC HAL Core \n");
        return false;
    }

      (void) pthread_mutex_unlock(&i2ctransport_mtx);

    return (pthread_create(&threadHandle, NULL, I2cWorkerThread, *pHandle) == 0);
}

/**
 * Terminates the I2C layer.
 */
void I2cCloseLayer()
{
    uint8_t cmd = 'X';
    int ret;
    ALOGD("%s: enter\n", __func__);

    (void)pthread_mutex_lock(&i2ctransport_mtx);

    if (threadHandle == (pthread_t)NULL)
        return;

    I2cWriteCmd(&cmd, sizeof(cmd));
    /* wait for terminate */
    ret = pthread_join(threadHandle,(void**)NULL);
    if (ret != 0) {
        ALOGE("%s: failed to wait for thread (%d)", __func__, ret);
    }
    threadHandle = (pthread_t)NULL;
    (void)pthread_mutex_unlock(&i2ctransport_mtx);
}
/**************************************************************************************************
 *
 *                                      Private API Definition
 *
 **************************************************************************************************/
/**
 * Call the st21nfc driver to adjust wake-up polarity.
 * @param fid File descriptor for NFC device
 * @param low Polarity (HIGH or LOW)
 * @param edge Polarity (RISING or FALLING)
 * @return Result of IOCTL system call (0 if ok)
 */
static int i2cSetPolarity(int fid, bool low, bool edge)
{
    int result;
    unsigned int io_code;

    if (low) {
        if (edge) {
            io_code = ST21NFC_SET_POLARITY_FALLING;
        } else {
            io_code = ST21NFC_SET_POLARITY_LOW;
        }

    } else {
        if (edge) {
            io_code = ST21NFC_SET_POLARITY_RISING;
        } else {
            io_code = ST21NFC_SET_POLARITY_HIGH;
        }
    }

    if (-1 == (result = ioctl(fid, io_code, NULL))) {
        result = -1;
    }

    return result;
} /* i2cSetPolarity*/

/**
 * Call the st21nfc driver to generate a 30ms pulse on RESET line.
 * @param fid File descriptor for NFC device
 * @return Result of IOCTL system call (0 if ok)
 */
static int i2cResetPulse(int fid)
{
    int result;

    if (-1 == (result = ioctl(fid, ST21NFC_PULSE_RESET, NULL))) {
        result = -1;
    }
    STLOG_HAL_D("! i2cResetPulse!!, result = %d", result);
    return result;
} /* i2cResetPulse*/

/**
 * Write data to st21nfc, on failure do max 3 retries.
 * @param fid File descriptor for NFC device
 * @param pvBuffer Data to write
 * @param length Data size
 * @return 0 if bytes written, -1 if error
 */
static int i2cWrite(int fid, const uint8_t* pvBuffer, int length)
{
    int retries = 0;
    int result = 0;

    while (retries < 3) {
        result = write(fid, pvBuffer, length);

        if (result < 0) {
            char msg[LINUX_DBGBUFFER_SIZE];

            strerror_r(errno, msg, LINUX_DBGBUFFER_SIZE);
            STLOG_HAL_W("! i2cWrite!!, errno is '%s'", msg);
            usleep(4000);
            retries++;
        } else if (result > 0) {
            result = 0;
            return result;
        } else {
            STLOG_HAL_W("write on i2c failed, retrying\n");
            usleep(4000);
            retries++;
        }
    }

    return -1;
} /* i2cWrite */

/**
 * Read data from st21nfc, on failure do max 3 retries.
 *
 * @param fid File descriptor for NFC device
 * @param pvBuffer Buffer where to copy read data
 * @param length Data size to read
 * @return Length of read data, -1 if error
 */
static int i2cRead(int fid, uint8_t* pvBuffer, int length)
{
    int retries = 0;
    int result = -1;

    while ((retries < 3) && (result < 0)) {
        result = read(fid, pvBuffer, length);

        if (result == -1) {
            int e = errno;
            if (e == EAGAIN) {
                /* File is nonblocking, and no data is available.
                 * This is not an error condition!
                 */
                result = 0;
                STLOG_HAL_D("## i2cRead - got EAGAIN. No data available. return 0 bytes");
            } else {
                /* unexpected result */
                char msg[LINUX_DBGBUFFER_SIZE];
                strerror_r(e, msg, LINUX_DBGBUFFER_SIZE);
                STLOG_HAL_W("## i2cRead returns %d errno %d (%s)", result, e, msg);
            }
        }

        if (result < 0) {
            if (retries < 3) {
                /* delays are different and increasing for the three retries. */
                static const uint8_t delayTab[] = {2, 3, 5};
                int delay = delayTab[retries];

                retries++;
                STLOG_HAL_W("## i2cRead retry %d/3 in %d milliseconds.", retries, delay);
                usleep(delay * 1000);
                continue;
            }
        }
    }
    return result;
} /* i2cRead */

/**
 * Get the activation status of wake-up pin from st21nfc.
 *  The decision 'active' depends on selected polarity.
 *  The decision is handled inside the driver(st21nfc).
 * @param fid File descriptor for NFC device
 * @return
 *  Result < 0:     Error condition
 *  Result > 0:     Pin active
 *  Result = 0:     Pin not active
 */
static int i2cGetGPIOState(int fid)
{
    int result;

    if (-1 == (result = ioctl(fid, ST21NFC_GET_WAKEUP, NULL))) {
        result = -1;
    }

    return result;
} /* i2cGetGPIOState */
