/*
 * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <string.h>

#include "osi/osi.h"
#include "osi/allocator.h"
#include "common/bt_target.h"

#include "stack/obex_api.h"
#include "stack/goep_common.h"
#include "stack/goepc_api.h"
#include "goep_int.h"

#if (GOEPC_INCLUDED == TRUE)

/*******************************************************************************
**
** Function         GOEPC_Init
**
** Description      Initialize GOEP Client role, must call before using any
**                  other GOEPC APIs
**
** Returns          GOEP_SUCCESS if successful, otherwise failed
**
*******************************************************************************/
UINT16 GOEPC_Init(void)
{
#if (GOEP_DYNAMIC_MEMORY)
    if (!goepc_cb_ptr) {
        goepc_cb_ptr = (tGOEPC_CB *)osi_malloc(sizeof(tGOEPC_CB));
        if (!goepc_cb_ptr) {
            return GOEP_NO_RESOURCES;
        }
    }
#endif /* #if (GOEP_DYNAMIC_MEMORY) */
    memset(&goepc_cb, 0, sizeof(tGOEPC_CB));
    goepc_cb.trace_level = BT_TRACE_LEVEL_ERROR;
    return GOEP_SUCCESS;
}

/*******************************************************************************
**
** Function         GOEPC_Deinit
**
** Description      Deinit GOEP Client role, once deinit, can not use any other
**                  GOEPC APIs until call GOEPC_Init again
**
*******************************************************************************/
void GOEPC_Deinit(void)
{
#if (GOEP_DYNAMIC_MEMORY)
    if (goepc_cb_ptr) {
        osi_free(goepc_cb_ptr);
        goepc_cb_ptr = NULL;
    }
#endif /* #if (GOEP_DYNAMIC_MEMORY) */
}

/*******************************************************************************
**
** Function         GOEPC_Open
**
** Description      Start the progress to establish a GOEP connection to server
**
** Returns          GOEP_SUCCESS if successful, otherwise failed, when the
**                  connection is established, GOEPC_OPENED_EVT will come
**
*******************************************************************************/
UINT16 GOEPC_Open(tOBEX_SVR_INFO *svr, tGOEPC_EVT_CBACK callback, UINT16 *out_handle)
{
    UINT16 ret = GOEP_SUCCESS;
    tGOEPC_CCB *p_ccb = NULL;

    do {
        /* check parameter, allow out_handle to be NULL */
        if (svr == NULL || callback == NULL) {
            ret = GOEP_INVALID_PARAM;
            break;
        }

        p_ccb = goepc_allocate_ccb();
        if (p_ccb == NULL) {
            ret =  GOEP_NO_RESOURCES;
            break;
        }

        if (OBEX_CreateConn(svr, goepc_obex_callback, &p_ccb->obex_handle) != OBEX_SUCCESS) {
            ret = GOEP_TL_ERROR;
            break;
        }

        /* success */
        p_ccb->callback = callback;
        p_ccb->state = GOEPC_STATE_OPENING;
        if (out_handle) {
            *out_handle = p_ccb->allocated;
        }
    } while (0);

    if (ret != GOEP_SUCCESS && p_ccb != NULL) {
        goepc_free_ccb(p_ccb);
    }
    return ret;
}

/*******************************************************************************
**
** Function         GOEPC_Close
**
** Description      Close a GOEP connection immediately
**
** Returns          GOEP_SUCCESS if successful, otherwise failed
**
*******************************************************************************/
UINT16 GOEPC_Close(UINT16 handle)
{
    tGOEPC_CCB *p_ccb = NULL;

    UINT16 ccb_idx = handle - 1;
    if (ccb_idx >= GOEPC_MAX_CONNECTION || !goepc_cb.ccb[ccb_idx].allocated) {
        return GOEP_BAD_HANDLE;
    }

    p_ccb = &goepc_cb.ccb[ccb_idx];
    if (p_ccb->obex_handle) {
        OBEX_RemoveConn(p_ccb->obex_handle);
    }
    goepc_free_ccb(p_ccb);

    return GOEP_SUCCESS;
}

/*******************************************************************************
**
** Function         GOEPC_SendRequest
**
** Description      Send the prepared request packet to server
**
** Returns          GOEP_SUCCESS if successful, otherwise failed
**
*******************************************************************************/
UINT16 GOEPC_SendRequest(UINT16 handle)
{
    UINT16 ret = GOEP_SUCCESS;
    tGOEPC_CCB *p_ccb = NULL;
    BOOLEAN final = FALSE;

    do {
        UINT16 ccb_idx = handle - 1;
        if (ccb_idx >= GOEPC_MAX_CONNECTION || !goepc_cb.ccb[ccb_idx].allocated) {
            ret = GOEP_BAD_HANDLE;
            break;
        }
        p_ccb = &goepc_cb.ccb[ccb_idx];

        if (p_ccb->pkt == NULL) {
            ret = GOEP_INVALID_STATE;
            break;
        }

        final = OBEX_CheckFinalBit(p_ccb->pkt);
        /* check whether state machine allow this operation */
        if (!goepc_check_obex_req_allow(p_ccb->state, final)) {
            ret = GOEP_INVALID_STATE;
            break;
        }

        if (p_ccb->congest) {
            ret = GOEP_CONGEST;
            break;
        }

        /* execute srm state machine */
        goepc_srm_sm_execute(p_ccb, TRUE, p_ccb->pkt_srm_en, p_ccb->pkt_srm_wait);

        tGOEPC_DATA data;
        data.pkt = p_ccb->pkt;

        p_ccb->last_pkt_opcode = p_ccb->curr_pkt_opcode;
        p_ccb->pkt = NULL;
        p_ccb->pkt_srm_en = FALSE;
        p_ccb->pkt_srm_wait = FALSE;

        /* execute main state machine */
        if (final) {
            goepc_sm_execute(p_ccb, GOEPC_SM_EVENT_REQ_FB, &data);
        }
        else {
            goepc_sm_execute(p_ccb, GOEPC_SM_EVENT_REQ, &data);
        }
        /* since goepc_sm_execute may free ccb, can not access ccb here */
    } while (0);

    return ret;
}

/*******************************************************************************
**
** Function         GOEPC_PrepareRequest
**
** Description      Prepare a request packet, packet will be store internally
**
** Returns          GOEP_SUCCESS if successful, otherwise failed
**
*******************************************************************************/
UINT16 GOEPC_PrepareRequest(UINT16 handle, tOBEX_PARSE_INFO *info, UINT16 buff_size)
{
    UINT16 ret = GOEP_SUCCESS;
    tGOEPC_CCB *p_ccb = NULL;
    BT_HDR *pkt = NULL;

    do {
        UINT16 ccb_idx = handle - 1;
        if (ccb_idx >= GOEPC_MAX_CONNECTION || !goepc_cb.ccb[ccb_idx].allocated) {
            ret = GOEP_BAD_HANDLE;
            break;
        }
        p_ccb = &goepc_cb.ccb[ccb_idx];

        if (info == NULL || buff_size < OBEX_MIN_PACKET_SIZE) {
            ret = GOEP_INVALID_PARAM;
            break;
        }

        if (p_ccb->pkt != NULL) {
            ret = GOEP_INVALID_STATE;
            break;
        }

        if (!goepc_check_obex_req_param(info)) {
            ret = GOEP_INVALID_PARAM;
            break;
        }

        if (OBEX_BuildRequest(info, buff_size, &pkt) != OBEX_SUCCESS) {
            ret = GOEP_NO_RESOURCES;
            break;
        }

        p_ccb->curr_pkt_opcode = info->opcode;
        p_ccb->pkt = pkt;
    } while (0);

    return ret;
}

/*******************************************************************************
**
** Function         GOEPC_DropRequest
**
** Description      Drop the prepared internal request packet
**
** Returns          GOEP_SUCCESS if successful, otherwise failed
**
*******************************************************************************/
UINT16 GOEPC_DropRequest(UINT16 handle)
{
    UINT16 ccb_idx = handle - 1;
    if (ccb_idx >= GOEPC_MAX_CONNECTION || !goepc_cb.ccb[ccb_idx].allocated) {
        return GOEP_BAD_HANDLE;
    }

    tGOEPC_CCB *p_ccb = &goepc_cb.ccb[ccb_idx];
    if (p_ccb->pkt == NULL) {
        return GOEP_INVALID_STATE;
    }

    osi_free(p_ccb->pkt);
    p_ccb->pkt = NULL;
    p_ccb->pkt_srm_en = FALSE;
    p_ccb->pkt_srm_wait = FALSE;
    return GOEP_SUCCESS;
}

/*******************************************************************************
**
** Function         GOEPC_RequestSetSRM
**
** Description      Modify the prepared internal request packet, append SRM header
**                  or SRMP header
**
** Returns          GOEP_SUCCESS if successful, otherwise failed
**
*******************************************************************************/
UINT16 GOEPC_RequestSetSRM(UINT16 handle, BOOLEAN srm_en, BOOLEAN srm_wait)
{
    UINT16 ret = GOEP_SUCCESS;
    tGOEPC_CCB *p_ccb = NULL;

    do {
        UINT16 ccb_idx = handle - 1;
        if (ccb_idx >= GOEPC_MAX_CONNECTION || !goepc_cb.ccb[ccb_idx].allocated) {
            ret = GOEP_BAD_HANDLE;
            break;
        }
        p_ccb = &goepc_cb.ccb[ccb_idx];

        if (!srm_en && !srm_wait) {
            ret = GOEP_INVALID_PARAM;
            break;
        }

        if (p_ccb->pkt == NULL) {
            ret = GOEP_INVALID_STATE;
            break;
        }

        if (srm_en) {
            if (OBEX_AppendHeaderSRM(p_ccb->pkt, OBEX_SRM_ENABLE) == OBEX_SUCCESS) {
                p_ccb->pkt_srm_en = TRUE;
            }
            else {
                ret = GOEP_NO_RESOURCES;
                break;
            }
        }
        if (srm_wait) {
            if (OBEX_AppendHeaderSRMP(p_ccb->pkt, OBEX_SRMP_WAIT) == OBEX_SUCCESS) {
                p_ccb->pkt_srm_wait = TRUE;
            }
            else {
                ret = GOEP_NO_RESOURCES;
                break;
            }
        }
    } while (0);

    return ret;
}

/*******************************************************************************
**
** Function         GOEPC_RequestAddHeader
**
** Description      Modify the prepared internal request packet, append header
**
** Returns          GOEP_SUCCESS if successful, otherwise failed
**
*******************************************************************************/
UINT16 GOEPC_RequestAddHeader(UINT16 handle, UINT8 header_id, const UINT8 *data, UINT16 data_len)
{
    UINT16 ret = GOEP_SUCCESS;
    tGOEPC_CCB *p_ccb = NULL;

    do {
        UINT16 ccb_idx = handle - 1;
        if (ccb_idx >= GOEPC_MAX_CONNECTION || !goepc_cb.ccb[ccb_idx].allocated) {
            ret = GOEP_BAD_HANDLE;
            break;
        }
        p_ccb = &goepc_cb.ccb[ccb_idx];

        if (p_ccb->pkt == NULL) {
            ret = GOEP_INVALID_STATE;
            break;
        }

        if ((data == NULL && data_len != 0) || (data != NULL && data_len == 0)) {
            ret = GOEP_INVALID_PARAM;
            break;
        }

        if (OBEX_AppendHeaderRaw(p_ccb->pkt, header_id, data, data_len) != OBEX_SUCCESS) {
            ret = GOEP_NO_RESOURCES;
            break;
        }
    } while (0);

    return ret;
}

#endif /* #if (GOEPC_INCLUDED == TRUE) */
