/*
 * MertenBus.c
 *
 *  Created on: 28.07.2018
 *      Author: Probst
 */

#include "MertenBus.h"
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <xdc/runtime/Error.h>
#include <xdc/runtime/Assert.h>
#include <xdc/runtime/Diags.h>
#include <xdc/runtime/Log.h>
#include <xdc/runtime/Types.h>

#include "UARTTivaHD.h"
#include <ti/drivers/UART.h>
//ToDo: remove after busy test
#include <driverlib/uart.h>

#include <ti/sysbios/BIOS.h>
#include <ti/sysbios/knl/Semaphore.h>
#include <ti/sysbios/knl/Task.h>


#define MB_MAX_RETRIALS         3
#define MB_MAX_OBJECT_INSTANCES 1
#define MB_MIN_TELEGRAM_LENGTH  4 // Adr | type and qryCmdId | block parity | delimiter
#define MB_QUERY_ID             0
#define MB_COMMAND_ID           1
#define MB_ACKNOWLEDGE_ID       2
#define MB_ASSERT(x)            assert(x)

/* === private variables ==================================================== */


static MertenBusObject MertenBusObjs[MB_MAX_OBJECT_INSTANCES];
static uint8_t MertenBusInstanceCnt = 0;

/* === private functions ==================================================== */

static uint8_t calcBlockParity(const uint8_t data[], uint8_t numData) {
    uint8_t bPar = 0;
    MB_IDX_TYPE idx;
    for (idx = 0; idx < numData; idx++) {
        bPar ^= data[idx];
    }
    if (bPar == MB_TELEGRAM_DELIMITER) {
        // avoid MB_TELEGRAM_DELIMITER by bit-wise inversion
        bPar = (uint8_t) ~bPar;
    }
    return (bPar);
}

static inline uint8_t getType(const uint8_t data[]) {
    return (data[1]>>6);
}

static inline uint8_t getCmdOrQryId(const uint8_t data[]) {
    return (data[1] & 0x3F);
}

/* === public functions ===================================================== */

MertenBusHandle mertenBusInitAndStart(uint8_t uartIdx, uint32_t txPortBase, uint8_t txPin)
{
    MertenBusHandle objPtr = &MertenBusObjs[MertenBusInstanceCnt++];
    UART_Params uartParams;
    /* Create a UART with the parameters below. */
    UART_Params_init(&uartParams);
    UARTTivaHD_ExtraParams customParams;
    // ToDo: change to half duplex
    customParams.duplexMode = UART_DUPLEX_HALF;
    customParams.telegramEndChar = MB_TELEGRAM_DELIMITER;
    customParams.txPortBase = txPortBase;
    customParams.txPin = txPin;
    uartParams.custom = (uintptr_t) &customParams;
    uartParams.baudRate = 2400;
    uartParams.dataLength = UART_LEN_8;
    uartParams.parityType = UART_PAR_ODD;
    uartParams.stopBits = UART_STOP_ONE;
    uartParams.readMode = UART_MODE_BLOCKING;
    uartParams.readDataMode = UART_DATA_BINARY;
    uartParams.readReturnMode = UART_RETURN_NEWLINE;
    uartParams.readTimeout = 100; // unit is clock module ticks, 1ms according to .cfg
    uartParams.writeMode = UART_MODE_BLOCKING;
    uartParams.writeDataMode = UART_DATA_BINARY;
    uartParams.writeTimeout = BIOS_WAIT_FOREVER;

    objPtr->uartHdl = UART_open(uartIdx, &uartParams);
    MB_ASSERT(objPtr->uartHdl != NULL);
    objPtr->transmissionStatus = MBTS_na;
    return (objPtr);
}

int_fast8_t MertenBusSendTelegram(MertenBusTelegramObject *teleg, MertenBusHandle objPtr)
{
    // bool isReadTimeout;
    /* check for enough space for check sum and telegram delimiter */
    MB_ASSERT(teleg->sendSizeMax>teleg->idx+1);
    /* array starting with real telegram (excluding first delimiter for parser synchronization) */
    UChar *sendTelegram = &teleg->sendString[1];
    /* add parity and delimiter */
    /* don't take leading delimiter into account */
    teleg->sendString[teleg->idx] = calcBlockParity(sendTelegram, (teleg->idx) - 1);
    teleg->idx++;
    teleg->sendString[teleg->idx] = MB_TELEGRAM_DELIMITER;
    teleg->idx++;
    /* send telegram length excluding first delimiter */
    MB_IDX_TYPE sendTelegramLength = (teleg->idx) - 1;
    objPtr->retryCnt = 0;
    /* get pointer to UART object */
    UARTTivaHD_Object *uartObj = objPtr->uartHdl->object;
    UARTTivaHD_HWAttrs const    *hwAttrs = objPtr->uartHdl->hwAttrs;
    /* calculate read timeout in clock module units (1ms)
     * and set read timeout - write ends too soon in full duplex mode due to hardware fifo */
    uint32_t readTimeout = teleg->receiveSizeMax * (1000 * (8+3) / uartObj->baudRate) + 100;
    uartObj->readTimeout = readTimeout; /* not needed, because unused in TivaC TI RTOS */
    Clock_setTimeout(Clock_handle(&uartObj->timeoutClk),readTimeout);
    objPtr->transmissionStatus = MBTS_ongoing;
    while (objPtr->transmissionStatus == MBTS_ongoing)
    {
        Log_print0(Diags_USER1, "Start transmitting ...");
        /* call blocking write */
        UART_write(objPtr->uartHdl, &teleg->sendString[0], teleg->idx);
        Log_print0(Diags_USER1, "Write ended.");
        while (UARTBusy(hwAttrs->baseAddr)) {;}
        Log_print0(Diags_USER1, "Start receiving ...");
        /* call blocking read; received string contains delimiter at end */
        teleg->receivedByteCount = UART_read(objPtr->uartHdl, &teleg->receivedString[0], teleg->receiveSizeMax);
        Log_print1(Diags_USER1, "Received %d symbols.", teleg->receivedByteCount);
        // isReadTimeout = uartObj->state.bufTimeout;
        /* check minimum telegram length, delimiter (thus also timeout), block parity, response type,
         * typeId and address */
        bool isOk = (teleg->receivedByteCount >= MB_MIN_TELEGRAM_LENGTH)
                && (teleg->receivedString[(teleg->receivedByteCount) - 1] == MB_TELEGRAM_DELIMITER)
                && (teleg->receivedString[(teleg->receivedByteCount) - 2]
                        == calcBlockParity(&teleg->receivedString[0], (teleg->receivedByteCount) - 2)
                && (getType(&teleg->receivedString[0]) == MB_ACKNOWLEDGE_ID)
                && (getCmdOrQryId(&teleg->receivedString[0]) == getCmdOrQryId(sendTelegram))
                && (teleg->receivedString[0] == sendTelegram[0]));
        if (!isOk)
        {
            Log_print0(Diags_USER1, "Telegram defective!");
        }
        if (isOk)
        {
            uint_fast8_t typeSent = getType(sendTelegram);
            if (typeSent == MB_COMMAND_ID)
            {
                /* a command got sent, response should be the same w.r.t. length and data */
                isOk = (teleg->receivedByteCount == sendTelegramLength);
                if (isOk && (sendTelegramLength>4))
                {
                    /* compare data, skip address and type at start and parity and delimiter at end */
                    MB_IDX_TYPE cIdx;
                    for (cIdx = 2; cIdx<(sendTelegramLength-2); cIdx++)
                    {
                        isOk &= (teleg->receivedString[cIdx] == sendTelegram[cIdx]);
                    }
                }
                if (!isOk)
                {
                    Log_print0(Diags_USER1, "Length or data of command acknowledge defective!");
                }
            }
        }

        if (isOk)
        {
            objPtr->transmissionStatus = MBTS_ok;
            /* set index to first data content of response, i.e. after address and type/id symbols */
            teleg->idx = 2;
        }
        else if (objPtr->retryCnt < MB_MAX_RETRIALS)
        {
            (objPtr->retryCnt)++;
        }
        else
        {
            objPtr->transmissionStatus = MBTS_failure;
        }
    } // while (objPtr->transmissionStatus == MBTS_ongoing)
    return (objPtr->transmissionStatus);
}

void MertenBusConstructTelegram(MertenBusTelegramObject *teleg, uint8_t moduleAdr, uint8_t telegramType, uint8_t qryOrCmdId)
{
    teleg->receivedByteCount = 0;
    teleg->idx = 0;
    teleg->sendSizeMax = MB_SENT_STRING_MAX_LENGTH;
    teleg->receiveSizeMax = MB_RECEIVE_STRING_MAX_LENGTH;
    MB_ASSERT(moduleAdr!=MB_TELEGRAM_DELIMITER);
    MB_ASSERT(telegramType<2);
    MB_ASSERT(qryOrCmdId<64);
    /* sendString must hold additional MB_TELEGRAM_DELIMITER at beginning, too. */
    MB_ASSERT(teleg->sendSizeMax>=MB_MIN_TELEGRAM_LENGTH+1);
    MB_ASSERT(teleg->receiveSizeMax>=MB_MIN_TELEGRAM_LENGTH);
    /* start with telegram delimiter to reset slave's parser */
    teleg->sendString[(teleg->idx)++] = MB_TELEGRAM_DELIMITER;
    teleg->sendString[(teleg->idx)++] = moduleAdr;
    teleg->sendString[(teleg->idx)++] = (telegramType<<6) + qryOrCmdId;
}

void MertenBusConstructQueryTelegram(MertenBusTelegramObject *teleg, uint8_t moduleAdr, uint8_t qryId)
{
    MertenBusConstructTelegram(teleg, moduleAdr, MB_QUERY_ID, qryId);
}

void MertenBusConstructCommandTelegram(MertenBusTelegramObject *teleg, uint8_t moduleAdr, uint8_t cmdId)
{
    MertenBusConstructTelegram(teleg, moduleAdr, MB_COMMAND_ID, cmdId);
}

void MertenBusAddByteToTelegram(MertenBusTelegramObject *teleg, uint8_t data)
{
    MB_ASSERT(data!=MB_TELEGRAM_DELIMITER);
    MB_ASSERT((teleg->idx)<teleg->sendSizeMax);
    teleg->sendString[(teleg->idx)++] = data;
}
