/*
 * Copyright (C) 2020-2021 Aloysius Indrayanto
 *
 * This file is part of the eCxx library, see LICENSE file for the license details.
 */

/*
 * ----------------------------------------------------------------------------------
 * The code in this file is copied (with a few modifications) from:
 *     Wire.cpp - TWI/I2C library for Arduino & Wiring
 * ----------------------------------------------------------------------------------
 * Copyright (C) 2006 Nicholas Zambetti. All right reserved.
 *
 * This library is free software; you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free Software
 * Foundation; either version 2.1 of the License, or (at your option) any later
 * version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along
 * with this library; if not, write to the Free Software Foundation, Inc., 51
 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 * ----------------------------------------------------------------------------------
 */


#include "CoreLib.h"
#include "CoreSys.h"
#include "CoreUtil.h"
#include "PolledTimeout.h"
#include "SoftTWIDriver.h"
#include "TWIDefs.h"


namespace eCxx {

namespace NodeMCU {


//
// Internal data
//
static constexpr uint32_t STWIBufferSize = 8UL * Application::TWIBufferSize;

static uint8_t rxBuffer[STWIBufferSize];
static uint8_t txBuffer[STWIBufferSize];

static uint8_t rxBufferIndex          = 0;
static uint8_t rxBufferLength         = 0;

static uint8_t txAddress              = 0;
static uint8_t txBufferIndex          = 0;
static uint8_t txBufferLength         = 0;

static bool    transmitting           = false;

static SoftTWI_RxCallback onReceiveCb = nullptr;
static SoftTWI_TxCallback onRequestCb = nullptr;


//
// Callback functions
//
static void onReceiveCallback(uint8_t* recv, size_t count)
{
    // Simply return if there is no user callback set
    if(!onReceiveCb) return;

    // Copy to Rx buffer
    for(uint8_t i = 0; i < count; ++i) rxBuffer[i] = recv[i];

    // Set Rx buffer iterator variables
    rxBufferIndex  = 0;
    rxBufferLength = count;

    // Call user callback
    (*onReceiveCb)(count);
}


static void onRequestCallback()
{
    // Simply return if there is no user callback set
    if(!onRequestCb) return;

    // Reset Tx buffer iterator variables
    txBufferIndex  = 0;
    txBufferLength = 0;

    // Call user callback
    (*onRequestCb)();
}


//
// Software TWI middle-level functions
//
extern void stwi_begin()
{
    SoftTWIDriver::init(NodeMCU::TWI_SDA, NodeMCU::TWI_SCL);

    rxBufferIndex  = 0;
    rxBufferLength = 0;

    txBufferIndex  = 0;
    txBufferLength = 0;
}


extern void stwi_begin(uint8_t address, SoftTWI_RxCallback rxcb, SoftTWI_TxCallback txcb)
{
    stwi_begin();

    onReceiveCb = rxcb;
    onRequestCb = txcb;

    SoftTWIDriver::setAddress(address);

    SoftTWIDriver::attachSlaveRxEvent(&onReceiveCallback);
    SoftTWIDriver::attachSlaveTxEvent(&onRequestCallback);

    SoftTWIDriver::enableSlave();
}


extern void stwi_setFrequency(uint32_t freq)
{ SoftTWIDriver::setClock(freq); }


extern void stwi_beginTransmission(uint8_t address)
{
    transmitting   = true;
    txAddress      = address;
    txBufferIndex  = 0;
    txBufferLength = 0;
}


extern TWIResultCode stwi_endTransmission(bool sendStop)
{
    const TWIResultCode res = SoftTWIDriver::writeTo(txAddress, txBuffer, txBufferLength, sendStop);

    txBufferIndex  = 0;
    txBufferLength = 0;
    transmitting   = false;

    return res;
}


extern size_t stwi_write(uint8_t data)
{
    if(transmitting) {
        if(txBufferLength >= STWIBufferSize) return 0;

        txBuffer[txBufferIndex] = data;
        ++txBufferIndex;
        txBufferLength = txBufferIndex;
    }
    else {
        SoftTWIDriver::transmit(&data, 1);
    }

    return 1;
}


extern size_t stwi_write(const uint8_t* data, size_t quantity)
{
    if(transmitting) {
        for(size_t i = 0; i < quantity; ++i) {
            if( !stwi_write(data[i]) ) return i;
        }
    }
    else {
        SoftTWIDriver::transmit(data, quantity);
    }

    return quantity;
}


extern size_t stwi_requestFrom(uint8_t address, size_t size, bool sendStop)
{
    if(size > STWIBufferSize) size = STWIBufferSize;

    const size_t read = ( SoftTWIDriver::readFrom(address, rxBuffer, size, sendStop) == TWIResultCode::NoError )
                      ? size : 0;

    rxBufferIndex  = 0;
    rxBufferLength = read;

    return read;
}


extern int32_t stwi_available()
{
    const int32_t result = rxBufferLength - rxBufferIndex;

    if(!result) Application::yield();

    return result;
}


extern int32_t stwi_read()
{
    int32_t value = -1;

    if(rxBufferIndex < rxBufferLength) {
        value = rxBuffer[rxBufferIndex];
        ++rxBufferIndex;
    }

    return value;
}


extern int32_t stwi_peek()
{
    int32_t value = -1;

    if(rxBufferIndex < rxBufferLength) value = rxBuffer[rxBufferIndex];

    return value;
}


} // namespace NodeMCU

} // namespace eCxx
