/** \file
 *
 *  Contains the UartStream class implementation.
 *
 *  Copyright (c) 2007,2008,2009 MBARI
 *  MBARI Proprietary Information.  All Rights Reserved
 *
 */

#include "UartStream.h"

#include "LPC3Reg.h"
#include "data/StrValue.h"
#include "data/Slate.h"

#define MAP_SIZE 4096UL
#define MAP_MASK (MAP_SIZE - 1)

// High Speed UART Control Registers
#define HSU2_CTRL (0x4001800C)
#define HSU7_CTRL (0x4001C00C)

// GPIO Set/Clear Registers
#define PIO_OUTP_SET (0x40028004)
#define PIO_OUTP_CLR (0x40028008)

// UART Line Status Registers
#define U3LSR (0x40080014)
#define U4LSR (0x40088014)
#define U5LSR (0x40090014)
#define U6LSR (0x40098014)

// UART Xceiver Enable Lines
#define UART1_SHUTDOWN (0x4)
#define UART2_SHUTDOWN (0x8)
#define UART5_SHUTDOWN (0x40)
#define UART6_SHUTDOWN (0x20)
#define UART7_SHUTDOWN (0x80)


#define FATAL do { fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", \
  __LINE__, __FILE__, errno, strerror(errno)); exit(1); } while(0)

// Constructor
/// param uartCfg name of uart device definition in vehicle.cfg file
/// param baudCfg name of baud rate definition in vehicle.cfg file
/// param timeoutSec inter-character timeout
/// param logger logger of the owning component
/// param bufferSize size of buffer for readUntil operations
/// param blocking set to true to idle processor waiting for timeoutSec
UartStream::UartStream( const ConfigURI& uartCfg, const ConfigURI& baudCfg, const double timeoutSec,
                        Logger& logger, size_t bufferSize, bool blocking, bool debug )
    : UartStreamBase(
          LookupFilename( uartCfg, logger ),
          LookupBaudrate( baudCfg, logger ),
          timeoutSec,
          logger,
          bufferSize,
          blocking,
          debug
      )
{}

// Alternate constructor, for debugging, etc.
UartStream::UartStream( const Str& filename, UartStream::Baudrate baudrate, const double timeoutSec,
                        Logger& logger, size_t bufferSize, bool blocking, bool debug )
    : UartStreamBase(
          filename,
          baudrate,
          timeoutSec,
          logger,
          bufferSize,
          blocking,
          debug
      )
{}


/// Destructor
UartStream::~UartStream()
{}

void UartStream::waitForBufferEmpty()
{
    Timestamp start( Timestamp::Now() );
    Timespan sendTimeout( 0.5 );

    switch( uartId_ )
    {
    case UartStream::UART1_TX0:
        break;
    case UartStream::UART2_TX1:
        while( start.elapsed() < sendTimeout )
        {
            if( LPC3Reg::Hsu2Level_ & 0xFF00 )
            {
                break;
            }
            SLEEP_TIME.sleepFor();
        }
        break;
    case UartStream::UART3_S1:
        while( start.elapsed() < sendTimeout )
        {
            if( LPC3Reg::U3LsrPhys_ & 0x40 )
            {
                break;
            }
            SLEEP_TIME.sleepFor();
        }
        break;
    case UartStream::UART4_S2:
        while( start.elapsed() < sendTimeout )
        {
            if( LPC3Reg::U4LsrPhys_ & 0x40 )
            {
                break;
            }
            SLEEP_TIME.sleepFor();
        }
        break;
    case UartStream::UART5_S0:
        while( start.elapsed() < sendTimeout )
        {
            if( LPC3Reg::U5LsrPhys_ & 0x40 )
            {
                break;
            }
            SLEEP_TIME.sleepFor();
        }
        break;
    case UartStream::UART6_S3:
        while( start.elapsed() < sendTimeout )
        {
            if( LPC3Reg::U6LsrPhys_ & 0x40 )
            {
                break;
            }
            SLEEP_TIME.sleepFor();
        }
        break;
    case UartStream::UART7_TX2:
        break;
    case UartStream::UART_A0:
        break;
    case UartStream::UART_A1:
        break;
    case UartStream::UART_A2:
        break;
    case UartStream::UART_A3:
        break;
    case UartStream::UART_A4:
        break;
    case UartStream::UART_A5:
        break;
    case UartStream::UART_A6:
        break;
    case UartStream::UART_A7:
        break;
    case UartStream::UART_B0:
        break;
    case UartStream::UART_B1:
        break;
    case UartStream::UART_B2:
        break;
    case UartStream::UART_B3:
        break;
    case UartStream::UART_B4:
        break;
    case UartStream::UART_B5:
        break;
    case UartStream::UART_B6:
        break;
    case UartStream::UART_B7:
        break;
    case UartStream::UART_C0:
        break;
    case UartStream::UART_C1:
        break;
    case UartStream::UART_C2:
        break;
    case UartStream::UART_C3:
        break;
    case UartStream::UART_C4:
        break;
    case UartStream::UART_C5:
        break;
    case UartStream::UART_C6:
        break;
    case UartStream::UART_C7:
        break;
    case UartStream::UART_OTHER:
        break;
    case UartStream::NOT_UART:
        //TODO: Add in a pause here to imitate outgoingt bytes
        break;
    };
}

void UartStream::initializePort( UartId uart )
{
    switch( uart )
    {
    case UartStream::UART1_TX0:
        LPC3Reg::P0MuxClrPhys_ = UART1_SHUTDOWN; // P0.2 mux setup
        LPC3Reg::P0DirSetPhys_ = UART1_SHUTDOWN; // P0.2 direction set (output)
        break;
    case UartStream::UART2_TX1:
        LPC3Reg::P0MuxClrPhys_ = UART2_SHUTDOWN; // P0.3 mux setup
        LPC3Reg::P0DirSetPhys_ = UART2_SHUTDOWN; // P0.3 direction set (output)
        break;
    case UartStream::UART3_S1:
        //LPC3Reg::U3DllPhys_ = 0x0A;
        //LPC3Reg::U3ClkPhys_ = 0x101;
        //LPC3Reg::UartClkCtrl_ |= 0x1;
        break;
    case UartStream::UART4_S2:
        LPC3Reg::PioMuxSetPhys_ = 0x4;
        LPC3Reg::U4DllPhys_ = 0x0A;
        LPC3Reg::U4ClkPhys_ = 0x101;
        LPC3Reg::UartClkModePhys_ = 0x550;
        LPC3Reg::UartClkCtrl_ |= 0x2;
        break;
    case UartStream::UART5_S0:
        LPC3Reg::UartClkCtrl_ |= 0x4;
        break;
    case UartStream::UART6_S3:
        LPC3Reg::UartCtrlPhys_ = 0x20;
        LPC3Reg::P0MuxClrPhys_ = UART6_SHUTDOWN; // P0.5 mux setup
        LPC3Reg::P0DirSetPhys_ = UART6_SHUTDOWN; // P0.5 direction set (output)
        break;
    case UartStream::UART7_TX2:
        LPC3Reg::PioMuxClrPhys_ = 0x8000; // set U7_TX mux instead of LCD
        LPC3Reg::P0MuxClrPhys_ = UART7_SHUTDOWN; // P0.7 mux setup
        LPC3Reg::P0DirSetPhys_ = UART7_SHUTDOWN; // P0.7 direction set (output)
        break;
    case UartStream::UART_A0:
        break;
    case UartStream::UART_A1:
        break;
    case UartStream::UART_A2:
        break;
    case UartStream::UART_A3:
        break;
    case UartStream::UART_A4:
        break;
    case UartStream::UART_A5:
        break;
    case UartStream::UART_A6:
        break;
    case UartStream::UART_A7:
        break;
    case UartStream::UART_B0:
        break;
    case UartStream::UART_B1:
        break;
    case UartStream::UART_B2:
        break;
    case UartStream::UART_B3:
        break;
    case UartStream::UART_B4:
        break;
    case UartStream::UART_B5:
        break;
    case UartStream::UART_B6:
        break;
    case UartStream::UART_B7:
        break;
    case UartStream::UART_C0:
        break;
    case UartStream::UART_C1:
        break;
    case UartStream::UART_C2:
        break;
    case UartStream::UART_C3:
        break;
    case UartStream::UART_C4:
        break;
    case UartStream::UART_C5:
        break;
    case UartStream::UART_C6:
        break;
    case UartStream::UART_C7:
        break;
    case UartStream::UART_OTHER:
        break;
    case UartStream::NOT_UART:
        break;
    }
}

void UartStream::enableUART( void )
{
    switch( uartId_ )
    {
    case UartStream::UART1_TX0:
        LPC3Reg::P0OutpSetPhys_ = UART1_SHUTDOWN;
        break;
    case UartStream::UART2_TX1:
        LPC3Reg::P0OutpSetPhys_ = UART2_SHUTDOWN;
        break;
    case UartStream::UART3_S1:
        // UART 3 does not have shutdown
        break;
    case UartStream::UART4_S2:
        // UART 4 does not have shutdown
        break;
    case UartStream::UART5_S0:
        LPC3Reg::P0OutpSetPhys_ = UART5_SHUTDOWN;
        break;
    case UartStream::UART6_S3:
        LPC3Reg::P0OutpSetPhys_ = UART6_SHUTDOWN;
        break;
    case UartStream::UART7_TX2:
        LPC3Reg::P0OutpSetPhys_ = UART7_SHUTDOWN;
        break;
    case UartStream::UART_A0:
    case UartStream::UART_A1:
    case UartStream::UART_A2:
    case UartStream::UART_A3:
    case UartStream::UART_A4:
    case UartStream::UART_A5:
    case UartStream::UART_A6:
    case UartStream::UART_A7:
    case UartStream::UART_B0:
    case UartStream::UART_B1:
    case UartStream::UART_B2:
    case UartStream::UART_B3:
    case UartStream::UART_B4:
    case UartStream::UART_B5:
    case UartStream::UART_B6:
    case UartStream::UART_B7:
    case UartStream::UART_C0:
    case UartStream::UART_C1:
    case UartStream::UART_C2:
    case UartStream::UART_C3:
    case UartStream::UART_C4:
    case UartStream::UART_C5:
    case UartStream::UART_C6:
    case UartStream::UART_C7:
    case UartStream::UART_OTHER:
    case UartStream::NOT_UART:
        break;
    };
}


void UartStream::disableUART( void )
{
    switch( uartId_ )
    {
    case UartStream::UART1_TX0:
        LPC3Reg::P0OutpClrPhys_ = UART1_SHUTDOWN;
        break;
    case UartStream::UART2_TX1:
        LPC3Reg::P0OutpClrPhys_ = UART2_SHUTDOWN;
        break;
    case UartStream::UART3_S1:
        break;
    case UartStream::UART4_S2:
        break;
    case UartStream::UART5_S0:
        LPC3Reg::P0OutpClrPhys_ = UART5_SHUTDOWN;
        break;
    case UartStream::UART7_TX2:
        LPC3Reg::P0OutpClrPhys_ = UART7_SHUTDOWN;
        break;
    case UartStream::UART6_S3:
    case UartStream::UART_A0:
    case UartStream::UART_A1:
    case UartStream::UART_A2:
    case UartStream::UART_A3:
    case UartStream::UART_A4:
    case UartStream::UART_A5:
    case UartStream::UART_A6:
    case UartStream::UART_A7:
    case UartStream::UART_B0:
    case UartStream::UART_B1:
    case UartStream::UART_B2:
    case UartStream::UART_B3:
    case UartStream::UART_B4:
    case UartStream::UART_B5:
    case UartStream::UART_B6:
    case UartStream::UART_B7:
    case UartStream::UART_C0:
    case UartStream::UART_C1:
    case UartStream::UART_C2:
    case UartStream::UART_C3:
    case UartStream::UART_C4:
    case UartStream::UART_C5:
    case UartStream::UART_C6:
    case UartStream::UART_C7:
    case UartStream::UART_OTHER:
    case UartStream::NOT_UART:
        break;
    };
}

Str UartStream::LookupFilename( const ConfigURI& uartCfg, Logger& logger )
{
    StrValue filename( "/dev/invalid" );
    Slate::ReadOnce( uartCfg, filename, logger );
    return filename.asString();
}

UartStream::Baudrate UartStream::LookupBaudrate( const ConfigURI& baudCfg, Logger& logger )
{
    int rate( 9600 );
    Slate::ReadOnce( baudCfg, Units::BIT_PER_SECOND, rate, logger );
    switch( rate )
    {
    case 50:
        return UartStream::B_50;
    case 75:
        return UartStream::B_75;
    case 110:
        return UartStream::B_110;
    case 134:
        return UartStream::B_134;
    case 150:
        return UartStream::B_150;
    case 200:
        return UartStream::B_200;
    case 300:
        return UartStream::B_300;
    case 600:
        return UartStream::B_600;
    case 1200:
        return UartStream::B_1200;
    case 1800:
        return UartStream::B_1800;
    case 2400:
        return UartStream::B_2400;
    case 4800:
        return UartStream::B_4800;
    case 9600:
        return UartStream::B_9600;
    case 19200:
        return UartStream::B_19200;
    case 38400:
        return UartStream::B_38400;
    case 57600:
        return UartStream::B_57600;
    case 115200:
        return UartStream::B_115200;
    case 230400:
        return UartStream::B_230400;
    /*case 460800: -- speeds not available on some platforms
        return UartStream::B_460800;
    case 921600:
        return UartStream::B_921600;*/
    default:
        logger.syslog( "Using baudrate 9600 instead of ", rate, Syslog::FAULT );
        return UartStream::B_9600;
    }
}
