/*****************************************************************
 |
 |    AP4 - AC-3 Sync Frame Parser
 |
 |    Copyright 2002-2020 Axiomatic Systems, LLC
 |
 |
 |    This file is part of Bento4/AP4 (MP4 Atom Processing Library).
 |
 |    Unless you have obtained Bento4 under a difference license,
 |    this version of Bento4 is Bento4|GPL.
 |    Bento4|GPL 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, or (at your option)
 |    any later version.
 |
 |    Bento4|GPL 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 Bento4|GPL; see the file COPYING.  If not, write to the
 |    Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
 |    02111-1307, USA.
 |
 ****************************************************************/

/*----------------------------------------------------------------------
 |   includes
 +---------------------------------------------------------------------*/
#include "Ap4BitStream.h"
#include "Ap4Ac3Parser.h"
#include "Ap4Utils.h"

/*----------------------------------------------------------------------+
 |    AP4_Ac3Header::AP4_Ac3Header
 +----------------------------------------------------------------------*/
AP4_Ac3Header::AP4_Ac3Header(const AP4_UI08* bytes)
{
    AP4_BitReader bits(bytes, AP4_AC3_HEADER_SIZE);
    bits.SkipBits(16);                  // sync word
    bits.SkipBits(16);                  // crc1
    m_Fscod       = bits.ReadBits(2);
    m_Frmsizecod  = bits.ReadBits(6);   // frmsizecod
    m_FrameSize   = FRAME_SIZE_CODE_ARY_AC3[m_Fscod][m_Frmsizecod] * 2;
    
    m_Bsid        = bits.ReadBits(5);
    m_Bsmod       = bits.ReadBits(3);
    m_Acmod       = bits.ReadBits(3);
    if((m_Acmod & 0x1) && (m_Acmod != 0x1)){
        bits.SkipBits(2);               //cmixlev
    }
    if(m_Acmod & 0x4){
        bits.SkipBits(2);               //surmixlev
    }
    if(m_Acmod == 0x2){
        bits.SkipBits(2);               //dsurmod
    }
    m_Lfeon         = bits.ReadBit();
    m_ChannelCount  = GLOBAL_CHANNEL_ARY[m_Acmod] + m_Lfeon;
    bits.SkipBits(5);                   // dialnorm
    if(bits.ReadBit()){                 // compre
        bits.SkipBits(8);               // compr
    }
    if(bits.ReadBit()){                 // langcode
        bits.SkipBits(8);               // langcod
    }
    if(bits.ReadBit()){                 // audprodie
        bits.SkipBits(5);               // mixlevel
        bits.SkipBits(2);               // roomtyp
    }
    if (m_Acmod == 0){
        bits.SkipBits(5);               // dialnorm2
        if(bits.ReadBit()){             // compr2e
            bits.SkipBits(8);           // compr2
        }
        if(bits.ReadBit()){             // langcod2e
            bits.SkipBits(8);           // langcod2
        }
        if(bits.ReadBit()){             // audprodi2e
            bits.SkipBits(5);           // mixlevel2
            bits.SkipBits(2);           // roomtyp2
        }
    }
    bits.SkipBits(1);                   // copyrightb
    bits.SkipBits(1);                   // origbs
    if(bits.ReadBit()){                 // timecod1e
        bits.SkipBits(14);              // timecod1
    }
    if(bits.ReadBit()){                 //timecod2e
        bits.SkipBits(14);              // timecod2
    }
    m_Addbsie = bits.ReadBit();
    if (m_Addbsie){
        m_Addbsil = bits.ReadBits(6);
        for (unsigned int idx = 0 ; idx < (m_Addbsil + 1); idx ++){
            m_addbsi[idx] = bits.ReadBits(8);
        }
    } else {
        m_Addbsil = 0;
        AP4_SetMemory(m_addbsi, 0, sizeof (m_addbsi));
    }
    m_HeadSize = (bits.GetBitsRead() / 8) + ((bits.GetBitsRead() % 8 == 0) ? 0: 1);
}

/*----------------------------------------------------------------------+
 |    AP4_Ac3Header::MatchFixed
 |
 |    Check that two fixed headers are the same
 |
 +----------------------------------------------------------------------*/
bool
AP4_Ac3Header::MatchFixed(AP4_Ac3Header& frame, AP4_Ac3Header& next_frame)
{
    return true;
}

/*----------------------------------------------------------------------+
 |    AP4_Ac3Header::Check
 +----------------------------------------------------------------------*/
AP4_Result
AP4_Ac3Header::Check()
{
    if (m_Bsid > 8) {
        return AP4_FAILURE;
    }
    return AP4_SUCCESS;
}

/*----------------------------------------------------------------------+
 |    AP4_Ac3Parser::AP4_Ac3Parser
 +----------------------------------------------------------------------*/
AP4_Ac3Parser::AP4_Ac3Parser() :
m_FrameCount(0)
{
}

/*----------------------------------------------------------------------+
 |    AP4_Ac3Parser::~AP4_Ac3Parser
 +----------------------------------------------------------------------*/
AP4_Ac3Parser::~AP4_Ac3Parser()
{
}

/*----------------------------------------------------------------------+
 |    AP4_Ac3Parser::Reset
 +----------------------------------------------------------------------*/
AP4_Result
AP4_Ac3Parser::Reset()
{
    m_FrameCount = 0;
    
    return AP4_SUCCESS;
}

/*----------------------------------------------------------------------+
 |    AP4_Ac3Parser::Feed
 +----------------------------------------------------------------------*/
AP4_Result
AP4_Ac3Parser::Feed(const AP4_UI08* buffer,
                     AP4_Size*       buffer_size,
                     AP4_Flags       flags)
{
    AP4_Size free_space;
    
    /* update flags */
    m_Bits.m_Flags = flags;
    
    /* possible shortcut */
    if (buffer == NULL ||
        buffer_size == NULL ||
        *buffer_size == 0) {
        return AP4_SUCCESS;
    }
    
    /* see how much data we can write */
    free_space = m_Bits.GetBytesFree();
    if (*buffer_size > free_space) *buffer_size = free_space;
    if (*buffer_size == 0) return AP4_SUCCESS;
    
    /* write the data */
    return m_Bits.WriteBytes(buffer, *buffer_size);
}

/*----------------------------------------------------------------------+
 |    AP4_Ac3Parser::FindHeader
 +----------------------------------------------------------------------*/
AP4_Result
AP4_Ac3Parser::FindHeader(AP4_UI08* header)
{
    AP4_Size available = m_Bits.GetBytesAvailable();
    
    /* look for the sync pattern */
    while (available-- >= AP4_AC3_HEADER_SIZE) {
        m_Bits.PeekBytes(header, 2);  // ac3 header begins with 0x0B77
        
        if( (((header[0] << 8) | header[1]) == AP4_AC3_SYNC_WORD_BIG_ENDIAN) ||
           (((header[0] << 8) | header[1]) == AP4_AC3_SYNC_WORD_LITTLE_ENDIAN) ){
            // Little Endian
            if (((header[0] << 8) | header[1]) == AP4_AC3_SYNC_WORD_LITTLE_ENDIAN) {
                m_LittleEndian = true;
            } else {
                m_LittleEndian = false;
            }
            /* found a sync pattern, read the entire the header */
            m_Bits.PeekBytes(header, AP4_AC3_HEADER_SIZE);
            
            return AP4_SUCCESS;
        } else {
            m_Bits.ReadByte(); // skip
        }
    }
    
    return AP4_ERROR_NOT_ENOUGH_DATA;
}

/*----------------------------------------------------------------------+
 |    AP4_Ac3Parser::FindFrame
 +----------------------------------------------------------------------*/
AP4_Result
AP4_Ac3Parser::FindFrame(AP4_Ac3Frame& frame)
{
    unsigned int   available;
    unsigned char  raw_header[AP4_AC3_HEADER_SIZE];
    AP4_Result     result;
    
    /* align to the start of the next byte */
    m_Bits.ByteAlign();
    
    /* find a frame header */
    result = FindHeader(raw_header);
    if (AP4_FAILED(result)) return result;
    
    if (m_LittleEndian) {
        AP4_ByteSwap16(raw_header, AP4_AC3_HEADER_SIZE);
    }
    
    /* parse the header */
    AP4_Ac3Header ac3_header(raw_header);
    
    /* check the header */
    result = ac3_header.Check();
    if (AP4_FAILED(result)) {
        m_Bits.SkipBytes(2);
        goto fail;
    }
    
    /* check if we have enough data to peek at the next header */
    available = m_Bits.GetBytesAvailable();
    if (available >= ac3_header.m_FrameSize + AP4_AC3_HEADER_SIZE) {
        // enough to peek at the header of the next frame
        unsigned char peek_raw_header[AP4_AC3_HEADER_SIZE];
        
        m_Bits.SkipBytes(ac3_header.m_FrameSize);
        m_Bits.PeekBytes(peek_raw_header, AP4_AC3_HEADER_SIZE);
        m_Bits.SkipBytes(-((int)ac3_header.m_FrameSize));
        
        if (m_LittleEndian) {
            AP4_ByteSwap16(peek_raw_header, AP4_AC3_HEADER_SIZE);
        }
        /* check the header */
        AP4_Ac3Header peek_ac3_header(peek_raw_header);
        result = peek_ac3_header.Check();
        if (AP4_FAILED(result)) {
            m_Bits.SkipBytes(ac3_header.m_FrameSize + 2);
            goto fail;
        }
        
        /* check that the fixed part of this header is the same as the */
        /* fixed part of the previous header                           */
        else if (!AP4_Ac3Header::MatchFixed(ac3_header, peek_ac3_header)) {
            m_Bits.SkipBytes(ac3_header.m_FrameSize + 2);
            goto fail;
        }
    } else if (available < ac3_header.m_FrameSize || (m_Bits.m_Flags & AP4_BITSTREAM_FLAG_EOS) == 0) {
        // not enough for a frame, or not at the end (in which case we'll want to peek at the next header)
        return AP4_ERROR_NOT_ENOUGH_DATA;
    }
    frame.m_Info.m_ChannelCount                 = ac3_header.m_ChannelCount;
    frame.m_Info.m_SampleRate                   = FSCOD_AC3[ac3_header.m_Fscod];
    frame.m_Info.m_FrameSize                    = ac3_header.m_FrameSize;
    frame.m_Info.m_Ac3StreamInfo.fscod          = ac3_header.m_Fscod;
    frame.m_Info.m_Ac3StreamInfo.bsid           = ac3_header.m_Bsid;
    frame.m_Info.m_Ac3StreamInfo.bsmod          = ac3_header.m_Bsmod;
    frame.m_Info.m_Ac3StreamInfo.acmod          = ac3_header.m_Acmod;
    frame.m_Info.m_Ac3StreamInfo.lfeon          = ac3_header.m_Lfeon;
    frame.m_Info.m_Ac3StreamInfo.bit_rate_code  = ac3_header.m_Frmsizecod / 2;
    
    frame.m_LittleEndian = m_LittleEndian;
    
    /* set the frame source */
    frame.m_Source = &m_Bits;
    
    return AP4_SUCCESS;
    
fail:
    /* skip the header and return (only skip the first byte in  */
    /* case this was a false header that hides one just after)  */
    //m_Bits.SkipBytes(-(AP4_ADTS_HEADER_SIZE-1));
    return AP4_ERROR_CORRUPTED_BITSTREAM;
}

/*----------------------------------------------------------------------+
 |    AP4_Ac3Parser::GetBytesFree
 +----------------------------------------------------------------------*/
AP4_Size
AP4_Ac3Parser::GetBytesFree()
{
    return (m_Bits.GetBytesFree());
}

/*----------------------------------------------------------------------+
 |    AP4_Ac3Parser::GetBytesAvailable
 +----------------------------------------------------------------------*/
AP4_Size
AP4_Ac3Parser::GetBytesAvailable()
{
    return (m_Bits.GetBytesAvailable());
}
