/*
 * Video On Demand Samples
 *
 * Copyright (C) 2015 Microchip Technology Germany II GmbH & Co. KG
 *
 * 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, see <http://www.gnu.org/licenses/>.
 *
 * You may also obtain this software under a propriety license from Microchip.
 * Please contact Microchip for further information.
 *
 */

/*----------------------------------------------------------*/
/*! \file
 *  \brief  This file contains the CMultiplexer class.
 */
/*----------------------------------------------------------*/
#ifndef _MULTIPLEXER_H_
#define _MULTIPLEXER_H_

//#define TS_STUFFING
//#define UDP_STREAM_TARGET_IP "127.0.0.1"

#include <stdint.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include "Console.h"
#include "ThreadReadHdd.h"
#include "ThreadWriteNetwork.h"

#ifdef UDP_STREAM_TARGET_IP
#include "udp-stream.h"
static bool udpStreamCreated = false;
#endif



typedef enum WriteResult_tag
{
    WriteResult_Success,
    WriteResult_Failed,
    WriteResult_WouldBlock
} WriteResult_t;


/*----------------------------------------------------------*/
/*! \brief Class multiplexing multiple transport streams into
 *         a single driver output.
 */
/*----------------------------------------------------------*/
class CMultiplexer : public CStreamList
{
    int m_hDriver;
    char m_szDriver[64];
    uint64_t m_nBytesStuffed;
    uint32_t m_nStuffingCount;
    uint32_t m_nWriteLen;
    uint8_t *m_pBuf;
    uint32_t m_nBufPos;
    uint8_t m_scratchBuffer[CTsPacket::TS_PACKET_LEN];
    uint8_t m_scratchBufPos;
    bool m_patSent;
    CThreadReadHdd *readThread;
    CThreadWriteNetwork *writeThread;
#ifdef UDP_STREAM_TARGET_IP    
    bool useUdp;
#endif    
    
    static const char *GetErrnoString(int err)
    {
        switch( err )
        {
        case 0:
            return "Nothing stored in errno";
        case 1:
            return "Operation not permitted";
        case 2:
            return "No such file or directory";
        case 3:
            return "No such process";
        case 4:
            return "Interrupted system call";
        case 5:
            return "I/O error";
        case 6:
            return "No such device or address";
        case 7:
            return "Argument list too long";
        case 8:
            return "Exec format error";
        case 9:
            return "Bad file number";
        case 10:
            return "No child processes";
        case 11:
            return "Try again";
        case 12:
            return "Out of memory";
        case 13:
            return "Permission denied";
        case 14:
            return "Bad address";
        case 15:
            return "Block device required";
        case 16:
            return "Device or resource busy";
        case 17:
            return "File exists";
        case 18:
            return "Cross-device link";
        case 19:
            return "No such device";
        case 20:
            return "Not a directory";
        case 21:
            return "Is a directory";
        case 22:
            return "Invalid argument";
        case 23:
            return "File table overflow";
        case 24:
            return "Too many open files";
        case 25:
            return "Not a typewriter";
        case 26:
            return "Text file busy";
        case 27:
            return "File too large";
        case 28:
            return "No space left on device";
        case 29:
            return "Illegal seek";
        case 30:
            return "Read-only file system";
        case 31:
            return "Too many links";
        case 32:
            return "Broken pipe";
        case 33:
            return "Math argument out of domain of func";
        case 34:
            return "Math result not representable";
        default:
            break;
        }
        return "Unknown";
    }    

public:
    /*----------------------------------------------------------*/
    /*! \brief Constructs CMultiplexer instance
    *
    *  \param szDriver - The character device name of isochronous 
    *         MOST device channel. (e.g. "/root/myFifo")
    *         minimal bandwidth.
    */
    /*----------------------------------------------------------*/
    CMultiplexer( const char *szDriver, uint32_t nWriteLen ) : 
        m_nStuffingCount( 0 ), m_nWriteLen( nWriteLen ), m_nBufPos( 0 ), 
        m_scratchBufPos( 0 ), m_patSent( false )
    {
#ifdef UDP_STREAM_TARGET_IP
        useUdp = !udpStreamCreated;            
        if (!udpStreamCreated)
        {
            udpStreamCreated = true;
            UdpStream_Init( UDP_STREAM_TARGET_IP, 1234 );
        }
#endif
        m_pBuf = (uint8_t *)malloc( nWriteLen );
        if ( NULL == m_pBuf )
        {
            ConsolePrintf( PRIO_ERROR, RED"! memory error in CMultiplexer"\
                " constructor"RESETCOLOR"\n");
            throw( -40 );
        }
        strncpy(m_szDriver, szDriver, sizeof(m_szDriver));
        m_hDriver = open( szDriver, O_WRONLY );

        if( -1 == m_hDriver )
        {
            int err = errno;
            ConsolePrintf( PRIO_ERROR, RED"! failed to open driver: '%s', "\
                "ERRNO: %d ('%s')"RESETCOLOR"\n",
                szDriver, err, GetErrnoString( err ));
            throw( -30 );
            return;
        }

        readThread = new CThreadReadHdd();
        readThread->AddStreamList( this );
        
        writeThread = new CThreadWriteNetwork();
        writeThread->AddMultiplexer( this );
    }




    /*----------------------------------------------------------*/
    /*! \brief Destructs CMultiplexer instance
    */
    /*----------------------------------------------------------*/
    ~CMultiplexer()
    {
        readThread->RemoveStreamList( this );
        delete readThread;
        
        writeThread->RemoveMultiplexer( this );
        if( -1 != m_hDriver )
            close( m_hDriver );
    }




    /*----------------------------------------------------------*/
    /*! \brief Returns the name of the character device.
    *  \return The name of the device (e.g. "/dev/mdev0").
    */
    /*----------------------------------------------------------*/
    const char *GetDriverName( void )
    {
        return m_szDriver;
    }




    /*----------------------------------------------------------*/
    /*! \brief Starts to add/change a sub-stream in the
     *        multiplex.
     *  \param MacAddr - The MAC address of the destination device.
     *  \param MacAddr - The full path to transport stream file.
     */
    /*----------------------------------------------------------*/
    void PlayStream( CMacAddr *MacAddr, CSource *pSource )
    {
        CStream *pStream = GetStream( MacAddr );                  // is there already a stream assigned to this MAC?

        if( NULL == pStream )
        {
            pStream = GetStream( NULL );                          // find a stream object having an empty MAC

            if( NULL == pStream )
            {                              // all streams in use?
                ConsolePrintf( PRIO_ERROR, RED"too many streams open"RESETCOLOR );
                return;
            }
            pStream->SetMacAddr( MacAddr );                       // set MAC address of the stream
        }

        pStream->SetSource( pSource );                            // select file to be streamed
        pStream->SetPause( false );                               // switch off pause
    }




    /*----------------------------------------------------------*/
    /*! \brief Changes play position of a stream
    *  \param MacAddr - The MAC address of the destination device.
    *	\param nPos - The new time position of the stream.
    */
    /*----------------------------------------------------------*/
    void SetPos( CMacAddr *MacAddr, uint16_t nPos )
    {
        CStream *pStream = GetStream( MacAddr );
        if( pStream )
            pStream->SetPos( nPos );
    }




    /*----------------------------------------------------------*/
    /*! \brief Pauses a stream
    *  \param MacAddr - The MAC address of the destination device.
    *  \param bPause - true, if the stream shall be paused. false, 
    *         if the stream shall be playing.
    */
    /*----------------------------------------------------------*/
    void SetPause( CMacAddr *MacAddr, bool bPause )
    {
        CStream *pStream = GetStream( MacAddr );
        if( pStream )
            pStream->SetPause( bPause );
    }




    /*----------------------------------------------------------*/
    /*! \brief Pauses a stream
    *  \param MacAddr - The MAC address of the destination device.
    *  \param bRepetition - true, if the stream shall be 
    *         repeated infinitely, false, if the stream shall be 
    *         stopped after reaching the end.
    */
    /*----------------------------------------------------------*/
    void SetRepetition( CMacAddr *MacAddr, bool bRepetition )
    {
        CStream *pStream = GetStream( MacAddr );
        if( pStream )
            pStream->SetRepetition( bRepetition );
    }




    /*----------------------------------------------------------*/
    /*! \brief Stops a stream and releases resources
    *  \param MacAddr - The MAC address of the destination device.
    */
    /*----------------------------------------------------------*/
    void StopStream( CMacAddr *MacAddr )
    {
        CStream *pStream = GetStream( MacAddr );
        if( pStream )
            pStream->SetSource( NULL );
    }




    /*----------------------------------------------------------*/
    /*! \brief Structure holding statistics values.
    *  \note Pass this structure to the GetStatistics method.
    */
    /*----------------------------------------------------------*/
    typedef struct
    {
        uint64_t bytesSent;         /*! The amount of bytes sent with video and audio 
                                    *  data are written into the given buffer. */
        uint64_t bytesStuffed;      /*! The amount of bytes sent with stuffing payload. */
        uint64_t bytesRead;         /*! The amount of bytes read with video and audio 
                                    *  data are written into the given buffer. */
        uint64_t errPcr;            /*! The amount of PCR errors are written into 
                                    *  the given buffer. */
        uint64_t errBufUnderflow;   /*! Number of buffer underflows 
                                    *  occurred since last call. Values higher than zero
                                    *  are indicating that the thread reading stream data
                                    *  from files, has not enough performance */
        uint64_t errBufOverflow;    /*! Number of buffer overflows 
                                    *  occurred since last call. Values higher than zero
                                    *  are indicating that the sending thread does not 
                                    *  get away data fast enough. Reason could be that 
                                    *  the MOST channel is too small, that the CPU is 
                                    *  too small, ... */
        uint64_t errTs;             /*! The amount of TS errors are written into 
                                    *  the given buffer. */
    } MultiplexStats_t;




    /*----------------------------------------------------------*/
    /*! \brief Retrieves the statistics for the stream
    *  \param MacAddr - The MAC address of the destination device.
    *  \param pStats - Pointer to the MultiplexStats_t structure. 
    *                  This will be filled with data by this method.
    */
    /*----------------------------------------------------------*/
    bool GetStatistics( CMacAddr *MacAddr, MultiplexStats_t *pStats )
    {
        CStream *pStream = GetStream( MacAddr );
        if( NULL == pStats )
            return false;

        memset( ( void * )pStats, 0, sizeof( MultiplexStats_t ) );
        if( NULL != pStream )
        {
            pStats->bytesSent = pStream->GetBytesSent();
            pStats->bytesRead = pStream->GetBytesRead();
            pStats->errPcr = pStream->GetErrPcr();
            pStats->errBufUnderflow = pStream->GetErrBufUnderflow();
            pStats->errBufOverflow = pStream->GetErrBufOverflow();
            pStats->errTs = pStream->GetErrTs();
        }

        pStats->bytesStuffed = m_nBytesStuffed;
        m_nBytesStuffed = 0;

        return true;
    }




    /*----------------------------------------------------------*/
    /*! \brief sends some data if possible
    *  \return enumeration, holding the result of this operation
    */
    /*----------------------------------------------------------*/
    WriteResult_t WriteToDriver()
    {
        uint8_t *pBuf;
        if (0 != m_scratchBufPos)
        {
            uint32_t tail = CTsPacket::TS_PACKET_LEN - m_scratchBufPos;
            memcpy(&m_pBuf[m_nBufPos], &m_scratchBuffer[m_scratchBufPos], tail);
            m_nBufPos += tail;
        }
        m_scratchBufPos = 0;
        for( ; m_nBufPos < m_nWriteLen; m_nBufPos += CTsPacket::TS_PACKET_LEN )
        {
            if ( m_nBufPos + CTsPacket::TS_PACKET_LEN <= m_nWriteLen )
            {
                pBuf = m_pBuf + m_nBufPos; //The buffer has enough space to hold a complete transport stream packet
            }
            else
            {
                pBuf = m_scratchBuffer; //The buffer is not full, but there is a little space left, so use scratch buffer
                m_scratchBufPos = m_nWriteLen - m_nBufPos;
            }
            
            if ( !m_patSent && GetPatPacket( pBuf ) )
            {
                m_patSent = true;
            }
            else if( !GetTsPacket( pBuf ) )
            {
#ifdef TS_STUFFING                
                CTsPacket::CreateStuffing( pBuf, m_nStuffingCount++ );
                m_nBytesStuffed += CTsPacket::TS_PACKET_LEN;
#else
                return WriteResult_WouldBlock;
#endif
            }
            if (0 != m_scratchBufPos)
            {
                memcpy(m_pBuf + m_nBufPos, m_scratchBuffer, m_scratchBufPos);
                break;
            }
        }
#ifdef UDP_STREAM_TARGET_IP
        if (useUdp)        
        {
            uint32_t udpSent = 0;
            const uint32_t maxUdpSend = 7 * CTsPacket::TS_PACKET_LEN;
            do
            {
                uint32_t bytesToSend = m_nWriteLen - udpSent;
                if (bytesToSend > maxUdpSend)
                    bytesToSend = maxUdpSend;  
                UdpStream_Send( &m_pBuf[udpSent], bytesToSend );
                        udpSent += bytesToSend;
            }
            while (udpSent < m_nWriteLen);
        }
#endif  
        m_nBufPos = 0;
        m_patSent = false;
        return ( m_nWriteLen == ( uint32_t )write( m_hDriver, m_pBuf, m_nWriteLen ) ) ? WriteResult_Success : WriteResult_Failed;
    }
};




#endif