/*****************************************************************************/
/* Copyright (c) 2009 NXP B.V.                                               */
/*                                                                           */
/* This library is free software; you can redistribute it and/or             */
/* modify it under the terms of the GNU Lesser General Public                */
/* License as published by the Free Software Foundation, using               */
/* version 2.1 of the License.                                               */
/*                                                                           */
/* This library 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          */
/* Lesser General Public License for more details.                           */
/*                                                                           */
/* You should have received a copy of the GNU Lesser General Public          */
/* License along with this library; if not, write to the Free Software       */
/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA   */
/*                                                                           */
/*****************************************************************************/

/**************************************************************
* INCLUDE FILES                                               *
***************************************************************/
#ifdef HAVE_CONFIG_H
    #include <config.h>
#endif
#include <gst/gst.h>
#include <gst/gstmarshal.h>
#include <gst/video/video.h>
#include "gststb22xframedaudio.h"
#include "gststb22xframedaudio_utils.h"

/**************************************************************
* LOCAL MACROS                                                *
***************************************************************/

/**************************************************************
* LOCAL TYPEDEFS                                              *
***************************************************************/

/**************************************************************
* STATIC FUNCTION PROTOTYPES                                  *
***************************************************************/
static GstStb22xFramedAudioBitBuffer *gst_stb22x_framed_audio_open_bitbuffer(GstBuffer* buffer);

static gint32 gst_stb22x_framed_audio_close_bitbuffer(GstStb22xFramedAudioBitBuffer *bitStream);

static gint32 gst_stb22x_framed_audio_write_bitbuffer(GstStb22xFramedAudioBitBuffer *bitStream,
                                               guint32 data,
                                               gint32 numBit);

static gint32 gst_stb22x_framed_audio_align_bitbuffer(GstStb22xFramedAudioBitBuffer *bitStream);

static gint32 gst_stb22x_framed_audio_bitbuffer_numbit(GstStb22xFramedAudioBitBuffer *bitStream);

static gint32 gst_stb22x_framed_audio_writebyte(GstStb22xFramedAudioBitBuffer *bitStream,
                                                guint32 data,
                                                gint32 numBit);

/**************************************************************
* STATIC DATA                                                 *
***************************************************************/

/**
 * Declare a debug category for this component
 * Enabled by doing export GST_DEBUG=stb22x_framed_audio:x where x=1-5.
 */
GST_DEBUG_CATEGORY_EXTERN (gst_stb22x_framed_audio_debug);
#define GST_CAT_DEFAULT gst_stb22x_framed_audio_debug

/**************************************************************
* FUNCTION IMPLEMENTATION                                     *
***************************************************************/
/******************************************************************************/
/** Create a Bit Stream instance.

    @param size   @b IN Maximum Size of bitsteam
    @param buffer @b IN The buffer to fill.

    @return GstStb22xFramedAudioBitBuffer or NULL;
*/
GstStb22xFramedAudioBitBuffer *gst_stb22x_framed_audio_open_bitbuffer(GstBuffer* buffer)
{
    GstStb22xFramedAudioBitBuffer *bitStream;

    GST_LOG(" ->> Entering");

    bitStream = malloc(sizeof(GstStb22xFramedAudioBitBuffer));
    bitStream->numBit = 0;
    bitStream->currentBit = 0;
    bitStream->size = GST_BUFFER_SIZE(buffer);
    bitStream->data = GST_BUFFER_DATA(buffer);

    GST_LOG(" <<- Leaving");
    return bitStream;
}

/******************************************************************************/
/** Close a BitBuffer Instance

    @param bitStream @b IN Bitstream to close.

    @return  size in bytes of bitstream.
*/
gint32 gst_stb22x_framed_audio_close_bitbuffer(GstStb22xFramedAudioBitBuffer *bitStream)
{
    gint32 bytes;

    GST_LOG(" ->> Entering");

    bytes = GST_STB22X_FRAMED_AUDIO_BIT_TO_BYTE(bitStream->numBit);
    free(bitStream);

    GST_LOG(" <<- Leaving");
    return bytes;
}

/******************************************************************************/
static gint32 gst_stb22x_framed_audio_bitbuffer_numbit(GstStb22xFramedAudioBitBuffer *bitStream)
{
    GST_LOG(" ->> Entering");

    GST_LOG(" <<- Leaving");
    return bitStream->numBit;
}

/******************************************************************************/
static gint32 gst_stb22x_framed_audio_writebyte(GstStb22xFramedAudioBitBuffer *bitStream,
                                                guint32 data,
                                                gint32 numBit)
{
    gint32 numUsed,idx;

    GST_LOG(" ->> Entering");

    idx = (bitStream->currentBit / BYTE_NUMBIT) % bitStream->size;
    numUsed = bitStream->currentBit % BYTE_NUMBIT;

    if (numUsed == 0)
    {
        bitStream->data[idx] = 0;
    }

    bitStream->data[idx] |= (UInt8)((data & ((1<<numBit)-1)) << (BYTE_NUMBIT-numUsed-numBit));
    bitStream->currentBit += numBit;
    bitStream->numBit = bitStream->currentBit;

    GST_LOG(" <<- Leaving");
    return 0;
}

/******************************************************************************/
/** Write bits to a bit buffer.

    @param bitStream @b IN Bitstream to write to.
    @param data      @b IN Data to write.
    @param numBit    @b IN Number of bits to write (size of bitfield).

    @return 0 if succesful and 1 if not
*/
gint32 gst_stb22x_framed_audio_write_bitbuffer(GstStb22xFramedAudioBitBuffer *bitStream,
                                               guint32 data,
                                               gint32 numBit)
{
    gint32 num,maxNum,curNum;
    guint32 bits;

    GST_LOG(" ->> Entering");

    if (numBit == 0)
    {
        return 0;
    }

    /* write bits in packets according to buffer byte boundaries */
    num = 0;
    maxNum = BYTE_NUMBIT - bitStream->currentBit % BYTE_NUMBIT;
    while (num < numBit)
    {
        curNum = GST_STB22X_FRAMED_AUDIO_READ_MIN(numBit-num,maxNum);
        bits = data>>(numBit-num-curNum);
        if (gst_stb22x_framed_audio_writebyte(bitStream, bits, curNum))
        {
            return 1;
        }
        num += curNum;
        maxNum = BYTE_NUMBIT;
    }

    GST_LOG(" <<- Leaving");
    return 0;
}

/******************************************************************************/
/** align the BitBuffer on a byte boundary.

    @param bitStream @b IN Bitstream to write to.
    @param writeFlag @b IN Fill the buffer.

    @return num bits written to byte align this buffer
*/
gint32 gst_stb22x_framed_audio_align_bitbuffer(GstStb22xFramedAudioBitBuffer *bitStream)
{
    gint32 len, i,j;

    GST_LOG(" ->> Entering");

    len = gst_stb22x_framed_audio_bitbuffer_numbit(bitStream);

    j = (8 - (len % 8)) % 8;

    if ((len % 8) == 0) j = 0;

    for( i=0; i<j; i++ )
    {
        gst_stb22x_framed_audio_write_bitbuffer(bitStream, 0, 1);
    }

    GST_LOG(" <<- Leaving");
    return j;
}

/******************************************************************************/
gboolean gst_stb22x_framed_audio_generate_adif_header(GstStb22xFramedAudio *stb22x_framed_audio,
                                                      GstBuffer *codec_data,
                                                      GstBuffer** pAdifHeader)
{
    gint i = 0;
    gint objectType = 0;
    gint freqIndex = 0;
    gint channelConf = 0;
    gint frameLength = 0;
    gint profile = 0;
    UInt16 aacSetupData = 0;

    gint numFrontChannels = 0;
    gint numSideChannels = 0;
    gint numBackChannels = 0;
    gint numLfeChannels = 0;

    guint32 actualFrequency = 0;
    guint32 frameSamples = 0;
    gint dsiSize = 0;
    guint8* dsi = 0;
    gint adifHeaderSize = 0;

    GstBuffer* tmpAdifHeader = NULL;
    GstStb22xFramedAudioBitBuffer* pBitBuffer = NULL;

    GST_LOG(" ->> Entering");

    dsiSize = GST_BUFFER_SIZE(codec_data);
    dsi = GST_BUFFER_DATA(codec_data);

    if(dsiSize > 2)
    {
        GST_ERROR("We have codec_data of size > 2 is this really AAC DSI\n");
    }

    for( i=0; i < dsiSize; i++)
    {
        GST_INFO("DSI Byte %d: 0x%02x", i, dsi[i]);
    }

    /* AAC audio typically has a 16 bit (2 bytes) of data that is use to setup audio decoder.*/
    aacSetupData = (((dsi[0]) << 8) | (dsi[1]));

    /* The format (as specified here http://wiki.multimedia.cx/index.php?title=Understanding_AAC is:
       5 bits: object type
       4 bits: frequency index
       if (frequency index == 15)
           24 bits: frequency
       4 bits: channel configuration
       1 bit: frame length flag
       1 bit: dependsOnCoreCoder
       1 bit: extensionFlag */
    GST_INFO("AAC data: 0x%04x", aacSetupData);

    /* Object type from magic 2 bytes is not same as profile.*/
    objectType = ((aacSetupData & 0xF800) >> 11);
    freqIndex = ((aacSetupData & 0x780) >> 7);
    channelConf = ((aacSetupData & 0x78) >> 3);
    frameLength = ((aacSetupData & 0x4) >> 2);

    GST_INFO("objectType: %d, freqIndex: %d, channelConf: %d, frameLength: %d", objectType, freqIndex, channelConf, frameLength);

    switch(objectType)
    {
        case 0:
            /* NULL object Type.*/
            profile = 3; /* RESERVED.*/
            break;

        case 1:
            /* AAC Main.*/
            profile = 0; /* MAIN.*/
            break;

        case 2:
            /* AAC Low complexity.*/
            profile = 1; /* AAC LC.*/
            break;

        case 3:
            /* AAC SSR.*/
            profile = 2; /* AAC SSR.*/
            break;

        /* AAC Long term prediction.*/
        case 4:
        /* AAC High efficiency .*/
        case 5:
        default:
            /* Lots more available.*/
            profile = 3; /* RESERVED.*/
            break;
    }

    /* What is the AAC encoding frequency.*/
    switch(freqIndex)
    {
        case 0:
            actualFrequency = 96000;
            break;

        case 1:
            actualFrequency = 88200;
            break;

        case 2:
            actualFrequency = 64000;
            break;

        case 3:
            actualFrequency = 48000;
            break;

        case 4:
            actualFrequency = 44100;
            break;

        case 5:
            actualFrequency = 32000;
            break;

        case 6:
            actualFrequency = 24000;
            break;

        case 7:
            actualFrequency = 22050;
            break;

        case 8:
            actualFrequency = 16000;
            break;

        case 9:
            actualFrequency = 12000;
            break;

        case 10:
            actualFrequency = 11025;
            break;

        case 11:
            actualFrequency = 8000;
            break;

        case 12:
            actualFrequency = 7350;
            break;

        case 15:
            /* Actually been supplied - not going to be tested.*/
            actualFrequency = 0;
            break;

        case 13:
        case 14:
        default:
            /* 13/14 not supported and default is unknown.*/
            actualFrequency = 0;
            break;
    }

    /* What kind of audio output do we have (5.1, stereo etc.)*/
    /* Mono indicates is_cpe=0 and stereo indicates is_cpe=1.*/
    switch(channelConf)
    {
        case 1:
            /* 1 channel: front-center, (CPE=0 Mono)*/
            numFrontChannels = 1;
            break;

        case 2:
            /* 2 channels: front-left, front-right, (stereo CPE=1)*/
            numFrontChannels = 1;
            break;

        case 3:
            /* 3 channels: front-center (Channel 1), front-left, front-right(Channel 2 - Stereo).*/
            numFrontChannels = 2;
            break;

        case 4:
            /* 4 channels: front-center(mono), front-left, front-right(stereo), back-center (Mono).*/
            numFrontChannels = 3;
            numBackChannels = 1;
            break;

        case 5:
            /* 5 channels: front-center(mono), front-left, front-right(stereo), back-left, back-right.(stereo)*/
            numFrontChannels = 2;
            numBackChannels = 1;
            break;

        case 6:
            /* 6 channels: front-center(mono), front-left, front-right(stereo), back-left, back-right(stereo), LFE-channel.*/
            numFrontChannels = 2;
            numBackChannels = 1;
            numLfeChannels = 1;
            break;

        case 7:
            /* 8 channels: front-center(mono), front-left, front-right(stereo), side-left, side-right(stereo), back-left, back-right(stereo), LFE-channel(mono).*/
            numFrontChannels = 2;
            numSideChannels = 1;
            numBackChannels = 1;
            numLfeChannels = 1;
            break;

        /* 0: custom configuration (TODO).*/
        case 0:
        default:
            /* Not supported.*/
            break;
    }

    if(channelConf != 2)
    {
        /* TODO support non stereo streams - This may or may not work?.*/
        GST_INFO("Channel Conf of %d not supported (YET)", channelConf);
    }

    /* Frame Samples. */
    switch(frameLength)
    {
        case 1:
            frameSamples = 960;
            break;

        default:
            frameSamples = 1024;
            break;
    }

    /* Now create our header.*/
    tmpAdifHeader = gst_buffer_new_and_alloc(64);
    pBitBuffer = gst_stb22x_framed_audio_open_bitbuffer(tmpAdifHeader);

    /* Now fill in our header.......*/
    /* adif_id.*/
    gst_stb22x_framed_audio_write_bitbuffer(pBitBuffer, 'A', 8);
    gst_stb22x_framed_audio_write_bitbuffer(pBitBuffer, 'D', 8);
    gst_stb22x_framed_audio_write_bitbuffer(pBitBuffer, 'I', 8);
    gst_stb22x_framed_audio_write_bitbuffer(pBitBuffer, 'F', 8);
    /* copyright_id_present.*/
    gst_stb22x_framed_audio_write_bitbuffer(pBitBuffer, 0x0, 1);
    /* original_copy.*/
    gst_stb22x_framed_audio_write_bitbuffer(pBitBuffer, 0x0, 1);
    /* home.*/
    gst_stb22x_framed_audio_write_bitbuffer(pBitBuffer, 0x0, 1);
    /* bitstream_type. (VBR) */
    gst_stb22x_framed_audio_write_bitbuffer(pBitBuffer, 0x1, 1);
    /* bitrate. (VBR) */
    gst_stb22x_framed_audio_write_bitbuffer(pBitBuffer, 0x0, 23);
    /* num_program_config_elements. (We only support 1 Config Element so we set it to 0)*/
    gst_stb22x_framed_audio_write_bitbuffer(pBitBuffer, 0x0, 4);

    /* Our Program Config element Structure......*/

    /* element_instance_tag.*/
    gst_stb22x_framed_audio_write_bitbuffer(pBitBuffer, 0x0, 4);
    /* profile.*/
    gst_stb22x_framed_audio_write_bitbuffer(pBitBuffer, (guint32)profile, 2);
    /* sampling_frequency_index.*/
    gst_stb22x_framed_audio_write_bitbuffer(pBitBuffer, (guint32)freqIndex, 4);
    /* num_front_channels_present.*/
    gst_stb22x_framed_audio_write_bitbuffer(pBitBuffer, (guint32)numFrontChannels, 4);
    /* num_side_channels_present.*/
    gst_stb22x_framed_audio_write_bitbuffer(pBitBuffer, (guint32)numSideChannels, 4);
    /* num_back_channels_present.*/
    gst_stb22x_framed_audio_write_bitbuffer(pBitBuffer, (guint32)numBackChannels, 4);
    /* num_lfe_channels_present.*/
    gst_stb22x_framed_audio_write_bitbuffer(pBitBuffer, (guint32)numLfeChannels, 2);
    /* num_assoc_data_elements.(NOT SUPPORTED)*/
    gst_stb22x_framed_audio_write_bitbuffer(pBitBuffer, 0x0, 3);
    /* num_valid_cc_elements.(NOT SUPPORTED)*/
    gst_stb22x_framed_audio_write_bitbuffer(pBitBuffer, 0x0, 4);
    /* mono_mixdown_present.(NOT SUPPORTED)*/
    gst_stb22x_framed_audio_write_bitbuffer(pBitBuffer, 0x0, 1);
    /* stereo_mixdown_present. (NOT SUPPORTED)*/
    gst_stb22x_framed_audio_write_bitbuffer(pBitBuffer, 0x0, 1);
    /* matrix_mixdown_idx_present. (NOT SUPPORTED)*/
    gst_stb22x_framed_audio_write_bitbuffer(pBitBuffer, 0x0, 1);

    for(i =0; i < numFrontChannels; i++)
    {
        /* is_cpe Only true for >= stereo i.e set to 0 for MONO. */
        gst_stb22x_framed_audio_write_bitbuffer(pBitBuffer, ((channelConf == 1) ? 0x0 : 0x1), 1);
        /* tag_select */
        gst_stb22x_framed_audio_write_bitbuffer(pBitBuffer, 0x0, 4);
    }
    for(i =0; i < numSideChannels; i++)
    {
        /* is_cpe */
        gst_stb22x_framed_audio_write_bitbuffer(pBitBuffer, 0x0, 1);
        /* tag_select */
        gst_stb22x_framed_audio_write_bitbuffer(pBitBuffer, 0x0, 4);
    }
    for(i =0; i < numBackChannels; i++)
    {
        /* is_cpe */
        gst_stb22x_framed_audio_write_bitbuffer(pBitBuffer, 0x0, 1);
        /* tag_select */
        gst_stb22x_framed_audio_write_bitbuffer(pBitBuffer, 0x0, 4);
    }
    for(i =0; i < numLfeChannels; i++)
    {
        /* tag_select */
        gst_stb22x_framed_audio_write_bitbuffer(pBitBuffer, 0x0, 4);
    }

    /* Byte Alignment.....*/
    gst_stb22x_framed_audio_align_bitbuffer(pBitBuffer);
    /* Comment Field Length. (NOT SUPPORTED / NOT REQUIRED)*/
    gst_stb22x_framed_audio_write_bitbuffer(pBitBuffer, 0x0, 8);

    adifHeaderSize = gst_stb22x_framed_audio_close_bitbuffer(pBitBuffer);
    GST_INFO("ADIF header is %d bytes long\n", adifHeaderSize);

    *pAdifHeader = gst_buffer_new_and_alloc(adifHeaderSize);
    memcpy(GST_BUFFER_DATA(*pAdifHeader), GST_BUFFER_DATA(tmpAdifHeader), adifHeaderSize);

    for(i=0; i < GST_BUFFER_SIZE(*pAdifHeader); i++)
    {
        GST_INFO("0x%02x", GST_BUFFER_DATA(*pAdifHeader)[i]);
    }
    gst_buffer_unref(tmpAdifHeader);

    GST_LOG(" <<- Leaving");
    return TRUE;
}
