/*
 * Copyright (C) 2007-2023 Xagasoft, All rights reserved.
 *
 * This file is part of the libbu++ library and is released under the
 * terms of the license contained in the file LICENSE.
 */

#include "bu/fastcgi.h"

#ifndef WIN32
    #include <arpa/inet.h>
#endif

#include <errno.h>
#include <unistd.h>

#include "bu/membuf.h"

#include "bu/sio.h"
using Bu::sio;
using Bu::Fmt;

Bu::FastCgi::FastCgi() :
    pSrv( NULL ),
    bRunning( true )
{
    pSrv = new Bu::ServerSocketTcp( (Bu::ServerSocketTcp::socket_t)STDIN_FILENO, false );
}

Bu::FastCgi::FastCgi( int iPort ) :
    pSrv( NULL ),
    bRunning( true )
{
    pSrv = new Bu::ServerSocketTcp( iPort );
}

Bu::FastCgi::~FastCgi()
{
}

bool Bu::FastCgi::isEmbedded()
{
#ifndef WIN32
    struct sockaddr name;
    socklen_t namelen = sizeof(name);
    if( getpeername( STDIN_FILENO, &name, &namelen ) != 0 &&
        errno == ENOTCONN )
    {
        sio << "errno = " << errno << " (" << strerror( errno ) << ")" <<
            sio.nl;
        sio << "Socket found" << sio.nl;
        return true;
    }
    else
    {
        sio << "errno = " << errno << " (" << strerror( errno ) << ")" <<
            sio.nl;
        sio << "No socket detected, running in standalone mode" << sio.nl;
        return false;
    }
#else
    #warning Bu::FastCgi::isEmbedded IS A STUB for WIN32!!!!
    return false;
#endif
}

void Bu::FastCgi::read( Bu::Socket *pSock, Bu::FastCgi::Record &r )
{
    int iRead = pSock->read( &r, sizeof(Record) );
    if( iRead != sizeof(Record) )
        throw Bu::ExceptionBase("Hey, the size %d is wrong for Record. (%pSock)",
            iRead, strerror( errno ) );
    r.uRequestId = ntohs( r.uRequestId );
    r.uContentLength = ntohs( r.uContentLength );
}

void Bu::FastCgi::write( Bu::Socket *pSock, Bu::FastCgi::Record r )
{
//  sio << "Out -> " << r << sio.nl;
    r.uRequestId = htons( r.uRequestId );
    r.uContentLength = htons( r.uContentLength );
    pSock->write( &r, sizeof(Record) );
}

void Bu::FastCgi::read( Bu::Socket *pSock, Bu::FastCgi::BeginRequestBody &b )
{
    pSock->read( &b, sizeof(BeginRequestBody) );
    b.uRole = ntohs( b.uRole );
}

void Bu::FastCgi::write( Bu::Socket *pSock, Bu::FastCgi::EndRequestBody b )
{
    b.uStatus = htonl( b.uStatus );
    pSock->write( &b, sizeof(b) );
}

uint32_t Bu::FastCgi::readLen( Bu::Socket *pSock, uint16_t &uRead )
{
    uint8_t uByte[4];
    pSock->read( uByte, 1 );
    uRead++;
    if( uByte[0] >> 7 == 0 )
        return uByte[0];
    
    pSock->read( uByte+1, 3 );
    uRead += 3;
    return ((uByte[0]&0x7f)<<24)|(uByte[1]<<16)|(uByte[2]<<8)|(uByte[3]);
}

void Bu::FastCgi::readPair( Bu::Socket *pSock, StrHash &hParams, uint16_t &uRead )
{
    uint32_t uName = readLen( pSock, uRead );
    uint32_t uValue = readLen( pSock, uRead );
    uRead += uName + uValue;
    unsigned char *sName = new unsigned char[uName];
    pSock->read( sName, uName );
    Bu::String fsName( (char *)sName, uName );
    delete[] sName;

    if( uValue > 0 )
    {
        unsigned char *sValue = new unsigned char[uValue];
        pSock->read( sValue, uValue );
        Bu::String fsValue( (char *)sValue, uValue );
        hParams.insert( fsName, fsValue );
        delete[] sValue;
    }
    else
    {
        hParams.insert( fsName, "" );
    }
}

bool Bu::FastCgi::hasChannel( int iChan )
{
    if( aChannel.getSize() < iChan )
        return false;
    if( aChannel[iChan-1] == NULL )
        return false;
    return true;
}

Bu::Formatter &Bu::operator<<( Bu::Formatter &f, const Bu::FastCgi::Record &r )
{
    f << "[Ver=" << (uint32_t)r.uVersion <<
        ", Type=" << (uint32_t)r.uType <<
        ", Req=" << (uint32_t)r.uRequestId <<
        ", clen=" << (uint32_t)r.uContentLength <<
        ", plen=" << (uint32_t)r.uPaddingLength <<
        ", resv=" << (uint32_t)r.uReserved <<
        "]";
    return f;
}

void Bu::FastCgi::run()
{
//  sio << "sizeof(Bu::FastCgi::Record) = " << sizeof(Record) << sio.nl;
    bRunning = true;
    while( bRunning )
    {
        Bu::Socket *pSock = pSrv->accept( 5 );
        if( pSock == NULL )
            continue;

        pSock->setBlocking( true );
//      sio << "Got connection, blocking?  " << pSock->isBlocking() << sio.nl;
        try
        {
            for(;;)
            {
                Record r;
                memset( &r, 0, sizeof(r) );
//              try
//              {
                    read( pSock, r );
//              }
//              catch( Bu::ExceptionBase &e )
//              {
//                  sio << "Error: " << e.what() << ", " << pSock->isOpen() <<
//                      sio.nl;
//                  continue;
//              }
                Channel *pChan = NULL;
                if( r.uRequestId > 0 )
                {
                    if( !hasChannel( r.uRequestId ) &&
                        r.uType != typeBeginRequest )
                    {
                        sio << "Error, stream data without stream." << sio.nl;
                        sio << r << sio.nl;
                        if( r.uContentLength > 0 )
                        {
                            char *buf = new char[r.uContentLength];
                            sio << " (read " << pSock->read( buf, r.uContentLength )
                                << "/" << r.uContentLength << "):" << sio.nl;
                            sio.write( buf, r.uContentLength );
                            sio << sio.nl << sio.nl;
                        }
                    }
                    while( aChannel.getSize() < r.uRequestId )
                        aChannel.append( NULL );
                    if( r.uRequestId > 0 )
                        pChan = aChannel[r.uRequestId-1];
                }

//              sio << "Record (id=" << r.uRequestId << ", len=" << 
//                  r.uContentLength << ", pad=" << 
//                  (unsigned int)r.uPaddingLength << "):  ";
//              fflush( stdout );
                
                switch( (RequestType)r.uType )
                {
                    case typeBeginRequest:
//                      sio << "Begin Request.";
                        {
                            BeginRequestBody b;
                            read( pSock, b );
                            if( pChan != NULL )
                            {
                                sio << "Error!!!" << sio.nl;
                                return;
                            }
                            pChan = aChannel[r.uRequestId-1] = new Channel();
                        }
                        break;

                    case typeParams:
//                      sio << "Params.";
                        if( r.uContentLength == 0 )
                        {
                            pChan->uFlags |= chflgParamsDone;
                        }
                        else
                        {
                            uint16_t uUsed = 0;
                            while( uUsed < r.uContentLength )
                            {
                                readPair( pSock, pChan->hParams, uUsed );
                            }
                        }
                        break;

                    case typeStdIn:
//                      sio << "StdIn.";
                        if( r.uContentLength == 0 )
                        {
                            pChan->uFlags |= chflgStdInDone;
                        }
                        else
                        {
                            char *buf = new char[r.uContentLength];
                            int iTotal = 0;
                            do
                            {
                                size_t iRead = pSock->read(
                                    buf, r.uContentLength-iTotal );
                                iTotal += iRead;
//                              sio << " (read " << iRead << " " << iTotal
//                                  << "/" << r.uContentLength << ")";
                                pChan->sStdIn.append( buf, iRead );
                            } while( iTotal < r.uContentLength );
                            delete[] buf;
                        }
                        break;

                    case typeData:
//                      sio << "Data.";
                        if( r.uContentLength == 0 )
                        {
                            pChan->uFlags |= chflgDataDone;
                        }
                        else
                        {
                            char *buf = new char[r.uContentLength];
                            pSock->read( buf, r.uContentLength );
                            pChan->sData.append( buf, r.uContentLength );
                            delete[] buf;
                        }
                        break;

                    case typeStdOut:
                    case typeStdErr:
                    case typeEndRequest:
                    case typeAbortRequest:
                    case typeGetValuesResult:
//                      sio << "Scary.";
                        // ??? we shouldn't get these.
                        break;

                    case typeGetValues:
                        break;
                }

//              sio << sio.nl;

                if( pChan )
                {
                    if( pChan->uFlags == chflgAllDone )
                    {
//                      sio << "All done, generating output." << sio.nl;
                        Bu::MemBuf mStdOut, mStdErr;
                        int iRet = onRequest(
                            pChan->hParams, pChan->sStdIn,
                            mStdOut, mStdErr
                            );

                        Bu::String &sStdOut = mStdOut.getString();
                        Bu::String &sStdErr = mStdErr.getString();

                        Record rOut;
                        memset( &rOut, 0, sizeof(rOut) );
                        rOut.uVersion = 1;
                        rOut.uRequestId = r.uRequestId;
                        rOut.uPaddingLength = 0;
                        rOut.uType = typeStdOut;
                        if( sStdOut.getSize() > 0 )
                        {
                            for( int iPos = 0; iPos < sStdOut.getSize();
                                 iPos += 65528 )
                            {
                                int iSize = sStdOut.getSize()-iPos;
                                if( iSize > 65528 )
                                    iSize = 65528;
                                rOut.uContentLength = iSize;
                                write( pSock, rOut );
                                pSock->write( sStdOut.getStr()+iPos, iSize );
                            }
                        }
                        rOut.uContentLength = 0;
                        write( pSock, rOut );

                        rOut.uType = typeStdErr;
                        if( sStdErr.getSize() > 0 )
                        {
                            for( int iPos = 0; iPos < sStdErr.getSize();
                                 iPos += 65528 )
                            {
                                int iSize = sStdErr.getSize()-iPos;
                                if( iSize > 65528 )
                                    iSize = 65528;
                                rOut.uContentLength = iSize;
                                write( pSock, rOut );
                                pSock->write( sStdErr.getStr()+iPos, iSize );
                            }
                        }
                        rOut.uContentLength = 0;
                        write( pSock, rOut );

                        rOut.uType = typeEndRequest;
                        rOut.uContentLength = 8;
                        write( pSock, rOut );
                        
                        EndRequestBody b;
                        memset( &b, 0, sizeof(b) );
                        b.uStatus =  iRet;
                        write( pSock, b );

                        delete pChan;
                        aChannel[r.uRequestId-1] = NULL;
                    }
                }
            }
        }
        catch( Bu::ExceptionBase &e )
        {
//          sio << "Bu::SocketException: " << e.what() << sio.nl <<
//              "\tSocket open:  " << pSock->isOpen() << sio.nl;
        }
    }
}

