/**
 *  ReceivedFrame.cpp
 *
 *  Implementation of the ReceivedFrame class
 *
 *  @copyright 2014 - 2017 Copernica BV
 */
#include "includes.h"
#include "heartbeatframe.h"
#include "confirmselectframe.h"
#include "confirmselectokframe.h"
#include "connectionstartokframe.h"
#include "connectionstartframe.h"
#include "connectionsecureframe.h"
#include "connectionsecureokframe.h"
#include "connectionopenokframe.h"
#include "connectionopenframe.h"
#include "connectiontuneokframe.h"
#include "connectiontuneframe.h"
#include "connectioncloseokframe.h"
#include "connectioncloseframe.h"
#include "connectionblockframe.h"
#include "connectionunblockframe.h"
#include "channelopenframe.h"
#include "channelopenokframe.h"
#include "channelflowframe.h"
#include "channelflowokframe.h"
#include "channelcloseokframe.h"
#include "channelcloseframe.h"
#include "exchangedeclareframe.h"
#include "exchangedeclareokframe.h"
#include "exchangedeleteframe.h"
#include "exchangedeleteokframe.h"
#include "exchangebindframe.h"
#include "exchangebindokframe.h"
#include "exchangeunbindframe.h"
#include "exchangeunbindokframe.h"
#include "queuedeclareframe.h"
#include "queuedeclareokframe.h"
#include "queuebindframe.h"
#include "queuebindokframe.h"
#include "queuepurgeframe.h"
#include "queuepurgeokframe.h"
#include "queuedeleteframe.h"
#include "queuedeleteokframe.h"
#include "queueunbindframe.h"
#include "queueunbindokframe.h"
#include "basicqosframe.h"
#include "basicqosokframe.h"
#include "basicconsumeframe.h"
#include "basicconsumeokframe.h"
#include "basiccancelframe.h"
#include "basiccancelokframe.h"
#include "basicpublishframe.h"
#include "basicreturnframe.h"
#include "basicdeliverframe.h"
#include "basicgetframe.h"
#include "basicgetokframe.h"
#include "basicgetemptyframe.h"
#include "basicackframe.h"
#include "basicnackframe.h"
#include "basicrejectframe.h"
#include "basicrecoverasyncframe.h"
#include "basicrecoverframe.h"
#include "basicrecoverokframe.h"
#include "transactionselectframe.h"
#include "transactionselectokframe.h"
#include "transactioncommitframe.h"
#include "transactioncommitokframe.h"
#include "transactionrollbackframe.h"
#include "transactionrollbackokframe.h"
#include "bodyframe.h"
#include "basicheaderframe.h"

#define TYPE_INVALID 0
#define END_OF_FRAME 206

/**
 *  Set up namespace
 */
namespace AMQP {

/**
 *  Constructor
 *  @param  buffer      Binary buffer
 *  @param  max         Max size for a frame
 */
ReceivedFrame::ReceivedFrame(const Buffer &buffer, uint32_t max) : InBuffer(buffer)
{
    // we need enough room for type, channel, the payload size, 
    // the the end-of-frame byte is not yet necessary
    if (buffer.size() < 7) return;
    
    // get the information
    _type = nextUint8();
    _channel = nextUint16();
    _payloadSize = nextUint32();

    // is the frame size bigger than the max frame size?
    if (max > 0 && _payloadSize > max - 8) throw ProtocolException("frame size exceeded");

    // check if the buffer is big enough to contain all data
    if (!complete()) return;

    // buffer is big enough, check for a valid end-of-frame marker
    if ((uint8_t)buffer.byte(_payloadSize+7) == END_OF_FRAME) return;

    // the frame is invalid because it does not end with the expected char
    throw ProtocolException("invalid end of frame marker");
}

/**
 *  Have we received the header of the frame
 *  @return bool
 */
bool ReceivedFrame::header() const
{
    return _buffer.size() >= 7;
}

/**
 *  Is this a complete frame?
 *  @return integer
 */
bool ReceivedFrame::complete() const
{
    return _buffer.size() >= _payloadSize + 8;
}

/**
 *  Process the received frame
 *  @param  connection
 *  @return bool
 */
bool ReceivedFrame::process(ConnectionImpl *connection)
{
    // check the type
    switch (_type)
    {
        case 1:     return processMethodFrame(connection);
        case 2:     return processHeaderFrame(connection);
        case 3:     return BodyFrame(*this).process(connection);
        case 4:     return HeartbeatFrame(*this).process(connection);
        case 8:     return HeartbeatFrame(*this).process(connection);
    }
    
    // this is a problem
    throw ProtocolException("unrecognized frame type " + std::to_string(_type));
}

/**
 *  Process a method frame
 *  @param  connection
 *  @return bool
 */
bool ReceivedFrame::processMethodFrame(ConnectionImpl *connection)
{
    // read the class id from the method
    uint16_t classID = nextUint16();
    
    // construct frame based on method id
    switch (classID)
    {
        case 10:    return processConnectionFrame(connection);
        case 20:    return processChannelFrame(connection);
        case 40:    return processExchangeFrame(connection);
        case 50:    return processQueueFrame(connection);
        case 60:    return processBasicFrame(connection);
        case 85:    return processConfirmFrame(connection);
        case 90:    return processTransactionFrame(connection);
    }

    // this is a problem
    throw ProtocolException("unrecognized method frame class " + std::to_string(classID));
}

/**
 *  Process a connection frame
 *  @param  connection
 *  @return bool
 */
bool ReceivedFrame::processConnectionFrame(ConnectionImpl *connection)
{
    // read the method id from the method
    uint16_t methodID = nextUint16();

    // construct frame based on method id
    switch (methodID)
    {
        case 10:    return ConnectionStartFrame(*this).process(connection);
        case 11:    return ConnectionStartOKFrame(*this).process(connection);
        case 20:    return ConnectionSecureFrame(*this).process(connection);
        case 21:    return ConnectionSecureOKFrame(*this).process(connection);
        case 30:    return ConnectionTuneFrame(*this).process(connection);
        case 31:    return ConnectionTuneOKFrame(*this).process(connection);
        case 40:    return ConnectionOpenFrame(*this).process(connection);
        case 41:    return ConnectionOpenOKFrame(*this).process(connection);
        case 50:    return ConnectionCloseFrame(*this).process(connection);
        case 51:    return ConnectionCloseOKFrame(*this).process(connection);
        case 60:    return ConnectionBlockFrame(*this).process(connection);
        case 61:    return ConnectionUnblockFrame(*this).process(connection);
    }

    // this is a problem
    throw ProtocolException("unrecognized connection frame method " + std::to_string(methodID));
}

/**
 *  Process a channel frame
 *  @param  connection
 *  @return bool
 */
bool ReceivedFrame::processChannelFrame(ConnectionImpl *connection)
{
    // read the method id from the method
    uint16_t methodID = nextUint16();
    
    // construct frame based on method id
    switch (methodID)
    {
        case 10:    return ChannelOpenFrame(*this).process(connection);
        case 11:    return ChannelOpenOKFrame(*this).process(connection);
        case 20:    return ChannelFlowFrame(*this).process(connection);
        case 21:    return ChannelFlowOKFrame(*this).process(connection);
        case 40:    return ChannelCloseFrame(*this).process(connection);
        case 41:    return ChannelCloseOKFrame(*this).process(connection);
    }

    // this is a problem
    throw ProtocolException("unrecognized channel frame method " + std::to_string(methodID));
}

/**
 *  Process an exchange frame
 *  @param  connection
 *  @return bool
 */
bool ReceivedFrame::processExchangeFrame(ConnectionImpl *connection)
{
    // read the method id from the method
    uint16_t methodID = nextUint16();

    // construct frame based on method id
    switch(methodID)
    {
        case 10:    return ExchangeDeclareFrame(*this).process(connection);
        case 11:    return ExchangeDeclareOKFrame(*this).process(connection);
        case 20:    return ExchangeDeleteFrame(*this).process(connection);
        case 21:    return ExchangeDeleteOKFrame(*this).process(connection);
        case 30:    return ExchangeBindFrame(*this).process(connection);
        case 31:    return ExchangeBindOKFrame(*this).process(connection);
        case 40:    return ExchangeUnbindFrame(*this).process(connection);
                    // contrary to the rule of good continuation, exchangeunbindok
                    // has method ID 51, instead of (the expected) 41. This is tested
                    // and it really has ID 51.  
        case 51:    return ExchangeUnbindOKFrame(*this).process(connection);
    }

    // this is a problem
    throw ProtocolException("unrecognized exchange frame method " + std::to_string(methodID));
}

/**
 *  Process a queue frame
 *  @param  connection
 *  @return bool
 */
bool ReceivedFrame::processQueueFrame(ConnectionImpl *connection)
{
    // read the method id from the method
    uint16_t methodID = nextUint16();

    // construct frame based on method id
    switch (methodID)
    {
        case 10:    return QueueDeclareFrame(*this).process(connection);
        case 11:    return QueueDeclareOKFrame(*this).process(connection);
        case 20:    return QueueBindFrame(*this).process(connection);
        case 21:    return QueueBindOKFrame(*this).process(connection);
        case 30:    return QueuePurgeFrame(*this).process(connection);
        case 31:    return QueuePurgeOKFrame(*this).process(connection);
        case 40:    return QueueDeleteFrame(*this).process(connection);
        case 41:    return QueueDeleteOKFrame(*this).process(connection);
        case 50:    return QueueUnbindFrame(*this).process(connection);
        case 51:    return QueueUnbindOKFrame(*this).process(connection);
    }

    // this is a problem
    throw ProtocolException("unrecognized queue frame method " + std::to_string(methodID));
}

/**
 *  Process a basic frame
 *  @param  connection
 *  @return bool
 */
bool ReceivedFrame::processBasicFrame(ConnectionImpl *connection)
{
    // read the method id from the method
    uint16_t methodID = nextUint16();

    // construct frame based on method id
    switch (methodID)
    {
        case 10:    return BasicQosFrame(*this).process(connection);
        case 11:    return BasicQosOKFrame(*this).process(connection);
        case 20:    return BasicConsumeFrame(*this).process(connection);
        case 21:    return BasicConsumeOKFrame(*this).process(connection);
        case 30:    return BasicCancelFrame(*this).process(connection);
        case 31:    return BasicCancelOKFrame(*this).process(connection);
        case 40:    return BasicPublishFrame(*this).process(connection);
        case 50:    return BasicReturnFrame(*this).process(connection);
        case 60:    return BasicDeliverFrame(*this).process(connection);
        case 70:    return BasicGetFrame(*this).process(connection);
        case 71:    return BasicGetOKFrame(*this).process(connection);
        case 72:    return BasicGetEmptyFrame(*this).process(connection);
        case 80:    return BasicAckFrame(*this).process(connection);
        case 90:    return BasicRejectFrame(*this).process(connection);
        case 100:   return BasicRecoverAsyncFrame(*this).process(connection);
        case 110:   return BasicRecoverFrame(*this).process(connection);
        case 111:   return BasicRecoverOKFrame(*this).process(connection);
        case 120:   return BasicNackFrame(*this).process(connection);
    }

    // this is a problem
    throw ProtocolException("unrecognized basic frame method " + std::to_string(methodID));
}

/**
 *  Process a confirm frame
 *  @param  connection
 *  @return bool
 */
bool ReceivedFrame::processConfirmFrame(ConnectionImpl *connection)
{
    // read the method id
    uint16_t methodID = nextUint16();

    // construct frame based on method id
    switch (methodID)
    {
        case 10:    return ConfirmSelectFrame(*this).process(connection);
        case 11:    return ConfirmSelectOKFrame(*this).process(connection);
    }

    // this is a problem
    throw ProtocolException("unrecognized confirm frame method " + std::to_string(methodID));
}

/**
 *  Process a transaction frame
 *  @param  connection
 *  @return bool
 */
bool ReceivedFrame::processTransactionFrame(ConnectionImpl *connection)
{
    // read the method id
    uint16_t methodID = nextUint16();

    // construct frame based on method id
    switch (methodID)
    {
        case 10:    return TransactionSelectFrame(*this).process(connection);
        case 11:    return TransactionSelectOKFrame(*this).process(connection);
        case 20:    return TransactionCommitFrame(*this).process(connection);
        case 21:    return TransactionCommitOKFrame(*this).process(connection);
        case 30:    return TransactionRollbackFrame(*this).process(connection);
        case 31:    return TransactionRollbackOKFrame(*this).process(connection);
    }

    // this is a problem
    throw ProtocolException("unrecognized transaction frame method " + std::to_string(methodID));
}

/**
 *  Process a header frame
 *  @param  connection
 *  @return bool
 */
bool ReceivedFrame::processHeaderFrame(ConnectionImpl *connection)
{
    // read the class id from the method
    uint16_t classID = nextUint16();
    
    // construct a frame based on class id
    switch (classID)
    {
        case 60:    return BasicHeaderFrame(*this).process(connection);
    }

    // this is a problem
    throw ProtocolException("unrecognized header frame class " + std::to_string(classID));
}

/**
 *  End of namespace
 */
}

