/**
 * @file adcs_linux_comm_intf.c
 *
 * @brief This file contains functions for sending ADCS data through
 * socket for linux build
 *
 * @copyright Copyright 2023 Antaris, Inc.
 */
/*********************************************************************/

#include "adcs_va.h"
#include "exo_common.h"
#include "exo_osal.h"
#include "exo_osal_mem_management.h"
#include <arpa/inet.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include "adcs_vapi_common.h"
#include "obc_main.h"

#define PORT (55556)   ///< CAN to Ethernet Socket port number

//#define ADCS_SERVER  ///< Define the socket server or client

/**
 * @brief  ADCS socket related global variables
 */
volatile int32 adcs_server_fd = -1;
volatile int32 adcs_client_fd = -1;
volatile uint8 conn_status = -1;
s_obc_sock_info adcs_soc_inf;

/**
 * @brief ADCS TCP server init api
 */
void adcs_tcp_server(void *param)
{
    int server_socket, client_addr_len;
    struct sockaddr_in server_addr, client_addr;

    // Create a TCP socket
    if((server_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        perror("socket");
        // exit(EXIT_FAILURE);
    }

    // Bind the socket to a port
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = inet_addr(adcs_soc_inf.ip_addr);
    server_addr.sin_port = atoi(adcs_soc_inf.port_num);

    if(bind(server_socket, (struct sockaddr *)&server_addr,
                sizeof(server_addr)) == -1)
    {
        perror("bind");
        // exit(EXIT_FAILURE);
    }

    // Listen for incoming connections
    if(listen(server_socket, 10) == -1)
    {
        perror("listen");
        // exit(EXIT_FAILURE);
    }

    DEBUG_CPRINT(("Server listening on port %d...\n", server_addr.sin_port));

    // Accept a new client connection
    client_addr_len = sizeof(client_addr);
    while(1)
    {
        if((adcs_server_fd =
                    accept(server_socket, (struct sockaddr *)&client_addr,
                        (socklen_t *)&client_addr_len)) == -1)
        {
            perror("accept");
        }

        DEBUG_CPRINT(("Accepted new connection from %s:%d\n",
                inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)));
    }
}

/**
 * @brief ADCS socket init interface api
 */

int adcs_sock_init(void)
{

#ifdef ADCS_SERVER
    int status;

    status = os_thread_create(ADCS_SERVER_THREAD, T_ADCS_STACK_SIZE,
            P_ADCS_SERVER, "adcs_server_thread",
            adcs_tcp_server, NULL, NULL, NULL);

    return status;
#else
    int status;
    struct sockaddr_in serv_addr;
    if((adcs_client_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        DEBUG_CPRINT(("ADCS_SOCK: Socket creation error \n"));
        return -1;
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(atoi(adcs_soc_inf.port_num));
    // Convert IPv4 and IPv6 addresses from text to binary
    // form
    if(inet_pton(AF_INET, adcs_soc_inf.ip_addr, &serv_addr.sin_addr) <= 0)
    {
        DEBUG_CPRINT(("ADCS_SOCK: Invalid address/ Address not supported \n"));
        return -1;
    }

    if((status = connect(adcs_client_fd, (struct sockaddr *)&serv_addr,
                    sizeof(serv_addr))) < 0)
    {
        DEBUG_CPRINT(("ADCS_SOCK: Connection Failed \n"));
        return -1;
    }
    else
    {
        conn_status = 1;
    }

    return status;
#endif
}

/**
 * @brief Function to request Telemetry from ADCS
 */
int8 adcs_requesttlm(uint8 tlmId, uint8* msgBuffer, uint16 len)
{
    e_adcs_status status = OPERATION_FAILURE;
    int16_t send_status = -1;
    int16_t rcv_status = 0;
    uint8_t *data_ptr = NULL;
#ifdef ADCS_SERVER
    int adcs_sock_fd = adcs_server_fd;
#else
    int adcs_sock_fd = adcs_client_fd;
#endif

    data_ptr = os_malloc(3);
    os_memcpy(data_ptr, &tlmId, 1);
    os_memcpy(&data_ptr[1], &len, 2);
    if(1 == conn_status)
    {
        if(check_socket_disconnected(adcs_sock_fd))
        {
            conn_status = 0;
            send_status = OPERATION_FAILURE;
            close(adcs_sock_fd);
        }
        else
        {
            send_status = send(adcs_sock_fd, data_ptr, 3, 0);
            if(3 != send_status)
            {
                DEBUG_CPRINT(("ADCS_SOCK: socket send ERROR  %d errno %d \n",\
                            send_status,errno));
            }
            else
            {
                rcv_status = read(adcs_sock_fd, msgBuffer, len);
                if((0 < rcv_status) && (rcv_status == len))
                {
                    status = OPERATION_SUCCESS;
                }
                else if(0 >= rcv_status)
                {
                    DEBUG_CPRINT(("ADCS_SOCK: socket read error  %d errno %d \n",
                                rcv_status, errno));
                }
                else
                {
                    DEBUG_CPRINT(("ADCS_SOCK: TLM Data requested size : %d" 
                                "data received size : %d mismatch ERROR \n",\
                                len, rcv_status));
                }
            }
        }
    }
    os_free(data_ptr);
    return status;
}

/**
 * @brief Function to send telecommand to ADCS
 */

int8 adcs_send_cmd(uint8 tcId, uint8* msgBuffer, uint16 len)
{
    int8_t status;
    status = adcs_sendtc_sock(tcId, msgBuffer, len);
    return status;
}
/**
 * @brief Function to send telecommand to ADCS through socket interface
 */

int8 adcs_sendtc_sock(uint8 tc_id, uint8 *tx_buffer, uint16 tx_size)
{
    int8_t status = -1;
    int16_t send_status = -1;
    int16_t rcv_status = 0;
    int8_t tc_status = -1;
    uint8_t *data_ptr = NULL;

#ifdef ADCS_SERVER
    int adcs_sock_fd = adcs_server_fd;
#else
    int adcs_sock_fd = adcs_client_fd;
#endif

    data_ptr = os_malloc(tx_size + 3);

    if((NULL != data_ptr) && (NULL != tx_buffer))
    {
        os_memcpy(data_ptr, &tc_id, 1);
        os_memcpy(&data_ptr[1], &tx_size, 2);
        os_memcpy(&data_ptr[3], tx_buffer, tx_size);
        if(1 == conn_status)
        {
            if(check_socket_disconnected(adcs_sock_fd))
            {
                conn_status = 0;
                close(adcs_sock_fd);
            }
            else
            {
                send_status = send(adcs_sock_fd, data_ptr, tx_size + 3, 0);
                if((tx_size + 3) != send_status)
                {
                    DEBUG_CPRINT(("ADCS_SOCK: socket send ERROR  %d errno %d \n",
                                send_status,errno));
                }
                else
                {
                    rcv_status = read(adcs_sock_fd, &tc_status, 1);
                    if(0 < rcv_status)
                    {
                        status = tc_status;
                    }
                    else if(0 >= rcv_status)
                    {
                        DEBUG_CPRINT(("ADCS_SOCK: socket read error %d errno %d\n",
                                    rcv_status, errno));
                    }
                    else
                    {
                        DEBUG_CPRINT(("ADCS_SOCK: TC Data requested size : %d data" 
                                    "received size : %d mismatch ERROR \n",\
                                    1, rcv_status));
                    }
                }
            }
        }
    }
    os_free(data_ptr);
    return status;
}
