#include "rcm.h"

int get_payload_aft_len( int payload_file_fd )  {

    struct stat payload_file_stat;
    fstat( payload_file_fd, &payload_file_stat );
    size_t payload_size = payload_file_stat.st_size;

    if ( payload_size > MAX_PAYLOAD_FILE_SIZE ) {
        fprintf( stderr, "Error: Payload file exceeds max payload size: %d bytes.\n", MAX_PAYLOAD_FILE_SIZE );
        return -1;
    }

    size_t payload_aft_len = 0;
    if ( payload_size > MAX_PAYLOAD_BEF_SIZE ) {
        payload_aft_len = payload_size - MAX_PAYLOAD_BEF_SIZE;
    }
    
    return payload_aft_len;
}

int read_intermezzo( uint8_t *rcm_cmd_buf ) {

    int ret = -1;
    int intermezzo_fd = 0;
    
    intermezzo_fd = open( "intermezzo.bin", O_RDONLY );
    if ( intermezzo_fd < 0 ) {
        fprintf( stderr, "Error: Couldn't open the intermezzo.bin file.\n" );
        goto exit;
    }
    
    struct stat intermezzo_stat;
    fstat( intermezzo_fd, &intermezzo_stat );
    size_t intermezzo_size = intermezzo_stat.st_size;
    if ( intermezzo_size > INTERMEZZO_LEN ) {
        fprintf( stderr, "Error: Intermezzo file exceeds max intermezzo size: %d bytes.\n", INTERMEZZO_LEN );
        goto exit;
    }

    read( intermezzo_fd, rcm_cmd_buf + RCM_CMD_BUF_INTERMEZZO_START, INTERMEZZO_LEN );
    
    ret = 0;
    
exit:
    if ( intermezzo_fd > 0) close( intermezzo_fd );

    return ret;

}

int read_payload_file( int payload_file_fd, uint8_t *rcm_cmd_buf, size_t rcm_cmd_buf_len ) {

    uint32_t payload_bef_len = read( payload_file_fd, rcm_cmd_buf + RCM_CMD_BUF_PAYLOAD_START, MAX_PAYLOAD_BEF_SIZE );
    uint32_t payload_aft_len = 0;

    if ( rcm_cmd_buf_len > RCM_CMD_BUF_PAYLOAD_CONT ) {
        payload_aft_len = read( payload_file_fd, rcm_cmd_buf + RCM_CMD_BUF_PAYLOAD_CONT, rcm_cmd_buf_len - RCM_CMD_BUF_PAYLOAD_CONT );
    }

    payload_bef_len = TO_LITTLE_ENDIAN( payload_bef_len ); 
    payload_aft_len = TO_LITTLE_ENDIAN( payload_aft_len ); 
    memcpy( rcm_cmd_buf + RCM_CMD_BUF_PAYLOAD_BEF_LENVAR, (uint8_t *)&payload_bef_len, 0x4 );
    memcpy( rcm_cmd_buf + RCM_CMD_BUF_PAYLOAD_AFT_LENVAR, (uint8_t *)&payload_aft_len, 0x4 );

    return 0;

}

int build_rcm_cmd( int payload_file_fd,  uint8_t *rcm_cmd_buf, size_t rcm_cmd_buf_len, uint32_t payload_thumb_mode ) {

    int ret = -1;

    const uint32_t rcm_cmd_len = TO_LITTLE_ENDIAN( RCM_CMD_LEN );
    const uint32_t payload_entry = TO_LITTLE_ENDIAN( BOOTROM_PAYLOAD_ENTRY | 0x1 );
    payload_thumb_mode = TO_LITTLE_ENDIAN( payload_thumb_mode ); 

    ret = read_intermezzo( rcm_cmd_buf );
    if ( ret < 0 ) {
        goto exit;
    }
    
    ret = read_payload_file( payload_file_fd, rcm_cmd_buf, rcm_cmd_buf_len );
    if ( ret < 0 ) {
        goto exit;
    }

    memcpy( rcm_cmd_buf, (uint8_t *)&rcm_cmd_len, sizeof(uint32_t) );
    memcpy( rcm_cmd_buf + RCM_CMD_BUF_MEMCPY_RET_ADD, (uint8_t *)&payload_entry, sizeof(uint32_t) );
    memcpy( rcm_cmd_buf + RCM_CMD_BUF_PAYLOAD_THUMB_MODE, (uint8_t *)&payload_thumb_mode, sizeof(uint32_t) );

    ret = 0;

exit:

    return ret;

}

int send_rcm_cmd( int rcm_usb, char* payload_filename, uint32_t payload_thumb_mode ) {

    int ret = -1;
    uint8_t *rcm_cmd_buf = 0;
    int payload_file_fd = 0;
    
    payload_file_fd = open( payload_filename, O_RDONLY );
    if ( payload_file_fd < 0 ) {
        fprintf( stderr, "Error: Couldn't open the payload file: %s.\n", payload_filename );
        goto exit;
    }

    int payload_aft_len = get_payload_aft_len( payload_file_fd );
    if ( payload_aft_len < 0 ) {
        goto exit;
    }

    size_t rcm_cmd_buf_len = RCM_CMD_BUF_PAYLOAD_CONT + payload_aft_len;
    size_t padding = 0x1000 - ( rcm_cmd_buf_len % 0x1000 );
    uint32_t n_writes = ( rcm_cmd_buf_len + padding) / 0x1000;
    if ( ! ( n_writes % 2 ) ) {
        padding += 0x1000;
    }

    rcm_cmd_buf = malloc( rcm_cmd_buf_len + padding );
    if ( !rcm_cmd_buf ) {
        fprintf( stderr, "Error: Couldn't alloc memory for RCM CMD buffer\n" );
        goto exit;
    }
    memset( rcm_cmd_buf, 0x00, rcm_cmd_buf_len + padding);

    ret = build_rcm_cmd( payload_file_fd, rcm_cmd_buf, rcm_cmd_buf_len, payload_thumb_mode );
    if ( ret < 0 ) {
        goto exit;
    }
    
    ret = usb_send_bulk_txn( rcm_usb, RCM_EP1_OUT, rcm_cmd_buf_len + padding, rcm_cmd_buf );
    
exit:
    if (payload_file_fd > 0) close(payload_file_fd);
    if (rcm_cmd_buf) free(rcm_cmd_buf);

    return ret;

}

