// ----------------------------------------------------------------------------------------
//
//  avr/redir/redir.cpp
//    
//  Bob Cook Development, Robotics Library
//  http://www.bobcookdev.com/rl/
//    
//  This file provides redirection support for the C library standard input/output.
//
//  Copyright (C) 2007 Bob Cook
//
//      This program is free software; you can redistribute it and/or modify it
//      under the terms of the GNU General Public License as published by the
//      Free Software Foundation; either version 2 of the License, or (at your
//      option) any later version.
//    
//      This program 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 General Public License 
//      for more details.
//    
//      You should have received a copy of the GNU General Public License along
//      with this program; if not, write to the Free Software Foundation, Inc., 
//      51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
//
// ----------------------------------------------------------------------------------------


#include "redir.h"

#include <stdio.h>

#include "packages/avr/can/can_messages.h"
#include "packages/avr/can/mcp2515.h"
#include "packages/avr/device/hwdefs.h"
#include "packages/avr/device/spinwait.h"
#include "packages/avr/device/uart.h"


// ----------------------------------------------------------------------------------------

#include "project_defs.h"


// ----------------------------------------------------------------------------------------

#if defined( PRJ_REDIR_VIA_UART0 )
#if defined( PRJ_REDIR_VIA_UART1 ) || defined( PRJ_REDIR_VIA_MCP2515_CAN )
#error Too many STDIO output options selected!
#endif
#if !defined( HW_HAS_UART0 )
#error Target hardware does not support UART0!
#endif
#endif

#if defined( PRJ_REDIR_VIA_UART1 )
#if defined( PRJ_REDIR_VIA_UART0 ) || defined( PRJ_REDIR_VIA_MCP2515_CAN )
#error Too many STDIO output options selected!
#endif
#if !defined( HW_HAS_UART1 )
#error Target hardware does not support UART1!
#endif
#endif

#if defined( PRJ_REDIR_VIA_MCP2515_CAN )
#if defined( PRJ_REDIR_VIA_UART0 ) || defined( PRJ_REDIR_VIA_UART1 )
#error Too many STDIO output options selected!
#endif
#endif

#if defined( PRJ_REDIR_VIA_MCP2515_CAN )
#if !defined( PRJ_REDIR_CAN_PUTCHAR_NODE )
#error PRJ_REDIR_CAN_PUTCHAR_NODE is required!
#endif
#if !defined( PRJ_REDIR_CAN_PUTCHAR_NODE )
#error PRJ_REDIR_CAN_PUTCHAR_NODE is required!
#endif
#if !defined( PRJ_REDIR_CAN_THIS_NODE )
#error PRJ_REDIR_CAN_THIS_NODE is required!
#endif
#if !defined( PRJ_REDIR_CAN_BUFFER_SIZE )
#define PRJ_REDIR_CAN_BUFFER_SIZE 16
#endif
#if ( PRJ_REDIR_CAN_BUFFER_SIZE == 0 )
#error PRJ_REDIR_CAN_BUFFER_SIZE cannot be zero!
#endif
#endif

#if defined( PRJ_REDIR_CAN_BUFFER_MASK )
#undef PRJ_REDIR_CAN_BUFFER_MASK
#endif
#define PRJ_REDIR_CAN_BUFFER_MASK ( PRJ_REDIR_CAN_BUFFER_SIZE - 1 )

#if defined( PRJ_REDIR_VIA_UART0 )    \
    || defined( PRJ_REDIR_VIA_UART1 ) \
    || defined( PRJ_REDIR_VIA_MCP2515_CAN )
    
#define STDIO_INCLUDE_FUNCTIONS

#endif


// ----------------------------------------------------------------------------------------

#if defined( PRJ_REDIR_VIA_MCP2515_CAN )

static uint8_t can_tx_buffer[ PRJ_REDIR_CAN_BUFFER_SIZE ];
static uint8_t can_tx_used;

static volatile uint8_t can_rx_buffer[ PRJ_REDIR_CAN_BUFFER_SIZE ];
static volatile uint8_t can_rx_head;
static volatile uint8_t can_rx_tail;

#endif


// ----------------------------------------------------------------------------------------

#if defined( STDIO_INCLUDE_FUNCTIONS )

static int stdio_putchar( char c, FILE* f )
{
    (void) f;

#if defined( PRJ_REDIR_OUTPUT_DO_AUTO_CRLF )
    
    if ( c == '\n' )
    {
        putchar( '\r' );
    }
    
#endif

#if defined( PRJ_REDIR_VIA_UART0 )
    
    uart0_write( static_cast< uint8_t >( c ) );
    
#elif defined( PRJ_REDIR_VIA_UART1 )
    
    uart1_write( static_cast< uint8_t >( c ) );
    
#elif defined( PRJ_REDIR_VIA_MCP2515_CAN )
    
    can_tx_buffer[ can_tx_used++ ] = static_cast< uint8_t >( c );
    
    bool wait_for_completion = ( c == '\n' );

    if ( can_tx_used >= array_sizeof( can_tx_buffer ) )
    {
        wait_for_completion = true;
    }

    for ( ;; )
    {
        if ( can_send( PRJ_REDIR_CAN_THIS_NODE,
                       PRJ_REDIR_CAN_PUTCHAR_NODE,
                       can_msgid_stdio,
                       can_tx_buffer,
                       can_tx_used ) )
        {
            can_tx_used = 0;
            break;
        }

        if ( ! wait_for_completion )
        {
            break;
        }

        spinwait_delay_ms( 1 );
    }
    
#endif
    
    return 0;
}

#endif

    
// ----------------------------------------------------------------------------------------

#if defined( STDIO_INCLUDE_FUNCTIONS )

static int stdio_getchar( FILE* f )
{
    (void) f;

#if defined( PRJ_REDIR_VIA_UART0 )

#if !defined( PRJ_REDIR_INPUT_BLOCKING_GETCHAR )
    
    if ( ! uart0_is_char_available() )
    {
        return _FDEV_EOF;
    }

#endif
    
    return ( static_cast< int >( uart0_read() ) & 0x00FF );

#elif defined( PRJ_REDIR_VIA_UART1 )

#if !defined( PRJ_REDIR_INPUT_BLOCKING_GETCHAR )
    
    if ( ! uart1_is_char_available() )
    {
        return _FDEV_EOF;
    }

#endif
    
    return ( static_cast< int >( uart1_read() ) & 0x00FF );
    
#elif defined( PRJ_REDIR_VIA_MCP2515_CAN )
    
    if ( can_rx_head != can_rx_tail )
    {
        can_rx_tail = ( can_rx_tail + 1 ) & PRJ_REDIR_CAN_BUFFER_MASK;
        return ( static_cast< int >( can_rx_buffer[ can_rx_tail ] ) & 0x00FF );
    }
            
    return _FDEV_EOF;

#endif

}

#endif


// ----------------------------------------------------------------------------------------

#if defined( STDIO_INCLUDE_FUNCTIONS )

void redir_initialize()
{

#if !defined( PRJ_REDIR_CALLER_WILL_INIT_TRANSPORT_PKG )

#if defined( PRJ_REDIR_VIA_UART0 )

    uart0_init();

#if defined( PRJ_REDIR_UART_BAUDRATE )

    uart0_set_baudrate( PRJ_REDIR_UART_BAUDRATE );

#endif

#elif defined( PRJ_REDIR_VIA_UART1 )

    uart1_init();

#if defined( PRJ_REDIR_UART_BAUDRATE )

    uart1_set_baudrate( PRJ_REDIR_UART_BAUDRATE );

#endif

#elif defined( PRJ_REDIR_VIA_MCP2515_CAN )

    if ( ! mcp2515_init() )
    {
        // initialization error

        // TODO
    }

#endif

#endif // #if !defined( PRJ_REDIR_CALLER_WILL_INIT_TRANSPORT_PKG )

    static FILE stdio_stream;

    fdev_setup_stream( &stdio_stream, stdio_putchar, stdio_getchar, _FDEV_SETUP_RW );
    stdout = &stdio_stream;
    stdin  = &stdio_stream;

}

#endif


// ----------------------------------------------------------------------------------------

#if defined( STDIO_INCLUDE_FUNCTIONS ) && defined( PRJ_REDIR_VIA_MCP2515_CAN )

void redir_recv_can_stdio( const uint8_t* data_ptr, uint8_t len )
{
    for ( ; len > 0; len--, data_ptr++ )
    {
        uint8_t rx_head = ( can_rx_head + 1 ) & PRJ_REDIR_CAN_BUFFER_MASK;
        uint8_t data    = *data_ptr;

        if ( rx_head != can_rx_tail )
        {
            can_rx_head = rx_head;
            can_rx_buffer[ rx_head ] = data;
        }
        else
        {
            // overflow error

            // TODO
        }
    }
}

#endif


// ----------------------------------------------------------------------------------------
//  end of redir.cpp
// ----------------------------------------------------------------------------------------

