/*
 * Author: Yevgeniy Kiveisha <yevgeniy.kiveisha@intel.com>
 * Copyright (c) 2014 Intel Corporation.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <iostream>
#include <unistd.h>
#include <stdlib.h>

#include "nrf24l01.h"

using namespace upm;

NRF24l01::NRF24l01 (uint8_t cs) {
    mraa_init();
    nrfInitModule (cs, 8);
}

NRF24l01::~NRF24l01 () {
    mraa_result_t error = MRAA_SUCCESS;
    error = mraa_spi_stop(m_spi);
    if (error != MRAA_SUCCESS) {
        mraa_result_print(error);
    }
    error = mraa_gpio_close (m_cePinCtx);
    if (error != MRAA_SUCCESS) {
        mraa_result_print(error);
    }
    error = mraa_gpio_close (m_csnPinCtx);
    if (error != MRAA_SUCCESS) {
        mraa_result_print(error);
    }
}

void
NRF24l01::nrfInitModule (uint8_t chip_select, uint8_t chip_enable) {
    mraa_result_t error = MRAA_SUCCESS;

    m_csn         = chip_select;
    m_ce         = chip_enable;
    m_channel     = 1;

    m_csnPinCtx = mraa_gpio_init (m_csn);
    if (m_csnPinCtx == NULL) {
        fprintf (stderr, "Are you sure that pin%d you requested is valid on your platform?", m_csn);
        exit (1);
    }

    m_cePinCtx = mraa_gpio_init (m_ce);
    if (m_cePinCtx == NULL) {
        fprintf (stderr, "Are you sure that pin%d you requested is valid on your platform?", m_ce);
        exit (1);
    }

    error = mraa_gpio_dir (m_csnPinCtx, MRAA_GPIO_OUT);
    if (error != MRAA_SUCCESS) {
        mraa_result_print (error);
    }

    error = mraa_gpio_dir (m_cePinCtx, MRAA_GPIO_OUT);
    if (error != MRAA_SUCCESS) {
        mraa_result_print (error);
    }

    nrfCELow ();
    m_spi = mraa_spi_init (0);
}

void
NRF24l01::nrfConfigModule() {
    /* Set RF channel */
    nrfConfigRegister (RF_CH, m_channel);

    /* Set length of incoming payload */
    nrfConfigRegister (RX_PW_P0, m_payload);
    nrfConfigRegister (RX_PW_P1, m_payload);
    /* Set length of incoming payload for broadcast */
    nrfConfigRegister (RX_PW_P2, m_payload);

    /* Start receiver */
    nrfPowerUpRX ();
    nrfFlushRX ();
}

/* Clocks only one byte into the given MiRF register */
void
NRF24l01::nrfConfigRegister(uint8_t reg, uint8_t value) {
    nrfCSOn ();
    mraa_spi_write (m_spi, W_REGISTER | (REGISTER_MASK & reg));
    mraa_spi_write (m_spi, value);
    nrfCSOff ();
}

void
NRF24l01::nrfPowerUpRX() {
    m_ptx = 0;
    nrfCELow();
    nrfConfigRegister(CONFIG, NRF_CONFIG | ( (1<<PWR_UP) | (1<<PRIM_RX) ) );
    nrfCEHigh();
    nrfConfigRegister(STATUS,(1 << TX_DS) | (1 << MAX_RT));
}

void
NRF24l01::nrfFlushRX() {
    nrfCSOn ();
    mraa_spi_write (m_spi, FLUSH_RX);
    nrfCSOff ();
}

/* Sets the receiving address */
void
NRF24l01::nrfSetRXaddr(uint8_t * addr) {
    nrfCELow();
    nrfWriteRegister(RX_ADDR_P1, addr, ADDR_LEN);
    nrfCEHigh();
}

/* Sets the transmitting address */
void
NRF24l01::nrfSetTXaddr(uint8_t * addr)
{
    /* RX_ADDR_P0 must be set to the sending addr for auto ack to work. */
    nrfWriteRegister (RX_ADDR_P0, addr, ADDR_LEN);
    nrfWriteRegister (TX_ADDR, addr, ADDR_LEN);
}

/* The broadcast address should be 0xFFFFF */
void
NRF24l01::nrfSetBroadcastAddr (uint8_t * addr) {
    nrfCELow ();
    nrfWriteRegister (RX_ADDR_P2, addr, ADDR_LEN);
    nrfCEHigh ();
}

void
NRF24l01::nrfSetPayload (uint8_t load) {
    m_payload = load;
}

void
NRF24l01::nrfWriteRegister(uint8_t reg, uint8_t * value, uint8_t len)
{
    nrfCSOn ();
    mraa_spi_write (m_spi, W_REGISTER | (REGISTER_MASK & reg));
    nrfTransmitSync(value, len);
    nrfCSOff ();
}

void
NRF24l01::nrfTransmitSync(uint8_t *dataout, uint8_t len){
    uint8_t i;
    for(i = 0; i < len; i++) {
        mraa_spi_write (m_spi, dataout[i]);
    }
}

/* Checks if data is available for reading */
bool
NRF24l01::nrfDataReady() {
    uint8_t status = nrfGetStatus();
    if ( status & (1 << RX_DR) ) {
        return 1;
    }

    return !nrfRXFifoEmpty();
}

uint8_t
NRF24l01::nrfGetStatus () {
    uint8_t rv;
    nrfReadRegister (STATUS, &rv, 1);
    return rv;
}

/* Reads an array of bytes from the given start position in the MiRF registers. */
void
NRF24l01::nrfReadRegister (uint8_t reg, uint8_t * value, uint8_t len)
{
    nrfCSOn ();
    mraa_spi_write (m_spi, R_REGISTER | (REGISTER_MASK & reg));
    nrfTransferSync (value, value, len);
    nrfCSOff ();
}

void
NRF24l01::nrfTransferSync (uint8_t *dataout,uint8_t *datain,uint8_t len) {
    uint8_t i;
    for(i = 0;i < len;i++) {
        datain[i] = mraa_spi_write (m_spi, dataout[i]);
    }
}

bool
NRF24l01::nrfRXFifoEmpty () {
    uint8_t fifo_status;
    nrfReadRegister (FIFO_STATUS, &fifo_status, sizeof(fifo_status));
    return (fifo_status & (1 << RX_EMPTY));
}

/* Reads payload bytes into data array */
void
NRF24l01::nrfGetData (uint8_t * data)
{
    nrfCSOn ();
    /* Send cmd to read rx payload */
    mraa_spi_write (m_spi, R_RX_PAYLOAD);
    /* Read payload */
    nrfTransferSync(data, data, m_payload);
    nrfCSOff ();
    nrfConfigRegister(STATUS, (1<<RX_DR));
}

/* Sends a data package to the default address. Be sure to send the correct
 * amount of bytes as configured as payload on the receiver. */
void
NRF24l01::nrfSend(uint8_t * value) {
    uint8_t status;
    status = nrfGetStatus();

    while (m_ptx) {
        status = nrfGetStatus();

        if((status & ((1 << TX_DS)  | (1 << MAX_RT)))){
            m_ptx = 0;
            break;
        }
    } // Wait until last paket is send

    nrfCELow();
    nrfPowerUpTX();                            // Set to transmitter mode , Power up
    nrfCSOn ();
    mraa_spi_write (m_spi, FLUSH_TX);        // Write cmd to flush tx fifo
    nrfCSOff ();

    nrfCSOn ();
    mraa_spi_write (m_spi, W_TX_PAYLOAD);     // Write cmd to write payload
    nrfTransmitSync(value, m_payload);        // Write payload
    nrfCSOff ();
    nrfCEHigh();                            // Start transmission
}

void
NRF24l01::nrfSend () {
    nrfSend (m_txBuffer);
}

bool
NRF24l01::nrfIsSending () {
    uint8_t status;
    if (m_ptx)    { // Sending mode.
        status = nrfGetStatus();
        /* if sending successful (TX_DS) or max retries exceded (MAX_RT). */
        if((status & ((1 << TX_DS)  | (1 << MAX_RT)))){
            nrfPowerUpRX();
            return false;
        }
        return true;
    }
    return false;
}

void
NRF24l01::nrfPowerUpTX () {
    m_ptx = 1;
    nrfConfigRegister (CONFIG, NRF_CONFIG | ( (1<<PWR_UP) | (0<<PRIM_RX) ) );
}

void
NRF24l01::nrfPowerDown () {
    nrfCELow ();
    nrfConfigRegister (CONFIG, NRF_CONFIG);
}

mraa_result_t
NRF24l01::nrfCEHigh () {
    return mraa_gpio_write (m_cePinCtx, HIGH);
}

mraa_result_t
NRF24l01::nrfCELow () {
    return mraa_gpio_write (m_cePinCtx, LOW);
}

mraa_result_t
NRF24l01::nrfCSOn () {
    return mraa_gpio_write (m_csnPinCtx, LOW);
}

mraa_result_t
NRF24l01::nrfCSOff () {
    return mraa_gpio_write (m_csnPinCtx, HIGH);
}

void
NRF24l01::nrfListenForChannel() {
    if(!nrfIsSending() && nrfDataReady()) {
        nrfGetData(m_rxBuffer);
        dataRecievedHandler(); /* let know that data arrived */
    }
}