/*
 * Copyright 2011 castLabs, Berlin
 *
 * Licensed under the Apache License, Version 2.0 (the License);
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an AS IS BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.googlecode.mp4parser.boxes.mp4.objectdescriptors;

import com.coremedia.iso.Hex;
import com.coremedia.iso.IsoTypeWriter;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;


//
//GetAudioObjectType()
//{
//audioObjectType; 5 uimsbf
//if (audioObjectType == 31) {
//audioObjectType = 32 + audioObjectTypeExt; 6 uimsbf
//}
//return audioObjectType;
//}
//AudioSpecificConfig ()
//{
//audioObjectType = GetAudioObjectType();
//samplingFrequencyIndex; 4 bslbf
//if ( samplingFrequencyIndex == 0xf ) {
//samplingFrequency; 24 uimsbf
//}
//channelConfiguration; 4 bslbf
//sbrPresentFlag = -1;
//psPresentFlag = -1;
//if ( audioObjectType == 5 ||
//audioObjectType == 29 ) {
//extensionAudioObjectType = 5;
//sbrPresentFlag = 1;
//if ( audioObjectType == 29 ) {
//psPresentFlag = 1;
//}
//extensionSamplingFrequencyIndex; 4 uimsbf
//if ( extensionSamplingFrequencyIndex == 0xf )
//extensionSamplingFrequency; 24 uimsbf
//audioObjectType = GetAudioObjectType();
//if ( audioObjectType == 22 )
//extensionChannelConfiguration; 4 uimsbf
//}
//else {
//extensionAudioObjectType = 0;
//}
//switch (audioObjectType) {
//case 1:
//case 2:
//case 3:
//case 4:
//case 6:
//case 7:
//case 17:
//case 19:
//case 20:
//case 21:
//case 22:
//case 23:
//GASpecificConfig();
//break:
//case 8:
//CelpSpecificConfig();
//break;
//case 9:
//HvxcSpecificConfig();
//break:
//case 12:
//TTSSpecificConfig();
//break;
//case 13:
//case 14:
//case 15:
//case 16:
//StructuredAudioSpecificConfig();
//break;
//case 24:
//ErrorResilientCelpSpecificConfig();
//break;
//case 25:
//ErrorResilientHvxcSpecificConfig();
//break;
//case 26:
//case 27:
//ParametricSpecificConfig();
//break;
// case 28:
//SSCSpecificConfig();
//break;
//case 30:
//sacPayloadEmbedding; 1 uimsbf
//SpatialSpecificConfig();
//break;
//case 32:
//case 33:
//case 34:
//MPEG_1_2_SpecificConfig();
//break;
//case 35:
//DSTSpecificConfig();
//break;
//case 36:
//fillBits; 5 bslbf
//ALSSpecificConfig();
//break;
//case 37:
//case 38:
//SLSSpecificConfig();
//break;
//case 39:
//ELDSpecificConfig(channelConfiguration);
//break:
//case 40:
//case 41:
//SymbolicMusicSpecificConfig();
//break;
//default:
///* reserved */
//}
//switch (audioObjectType) {
//case 17:
//case 19:
//case 20:
//case 21:
//case 22:
//case 23:
//case 24:
//case 25:
//case 26:
//case 27:
//case 39:
//epConfig; 2 bslbf
//if ( epConfig == 2 || epConfig == 3 ) {
//ErrorProtectionSpecificConfig();
//}
//if ( epConfig == 3 ) {
//directMapping; 1 bslbf
//if ( ! directMapping ) {
///* tbd */
//}
//}
//}
//if ( extensionAudioObjectType != 5 && bits_to_decode() >= 16 ) {
//syncExtensionType; 11 bslbf
//if (syncExtensionType == 0x2b7) {
//        extensionAudioObjectType = GetAudioObjectType();
//if ( extensionAudioObjectType == 5 ) {
//sbrPresentFlag; 1 uimsbf
//if (sbrPresentFlag == 1) {
//extensionSamplingFrequencyIndex; 4 uimsbf
//if ( extensionSamplingFrequencyIndex == 0xf ) {
//extensionSamplingFrequency; 24 uimsbf
//}
//if ( bits_to_decode() >= 12 ) {
//syncExtensionType; 11 bslbf
//if (syncExtesionType == 0x548) {
//psPresentFlag; 1 uimsbf
//}
//}
//}
//}
//if ( extensionAudioObjectType == 22 ) {
//sbrPresentFlag; 1 uimsbf
//if (sbrPresentFlag == 1) {
//extensionSamplingFrequencyIndex; 4 uimsbf
//if ( extensionSamplingFrequencyIndex == 0xf ) {
//extensionSamplingFrequency; 24 uimsbf
//}
//}
//extensionChannelConfiguration; 4 uimsbf
//}
//}
//}
//}
//        }
//
// TFCodingType
//0x0 AAC scaleable
//0x1 BSAC
//0x2 TwinVQ
//0x3 AAC non scaleable (i.e. multichannel)
//
// class TFSpecificConfig( uint(4) samplingFrequencyIndex, uint(4) channelConfiguration ) {
//uint(2) TFCodingType;
//uint(1) frameLength;
//uint(1) dependsOnCoreCoder;
//if (dependsOnCoreCoder == 1){
//uint(14)coreCoderDelay
//}
//if (TFCodingType==BSAC) {
//uint(11) lslayer_length
//}
//uint (1) extensionFlag;
//if (channelConfiguration == 0 ){
//program_config_element();
//}
//if (extensionFlag==1){
//<to be defined in mpeg4 phase 2>
//}
//}
//
//program_config_element()
//{
//element_instance_tag 4 uimsbf
//profile 2 uimsbf
//sampling_frequency_index 4 uimsbf
//num_front_channel_elements 4 uimsbf
//num_side_channel_elements 4 uimsbf
//num_back_channel_elements 4 uimsbf
// num_lfe_channel_elements 2 uimsbf
//num_assoc_data_elements 3 uimsbf
//num_valid_cc_elements 4 uimsbf
//mono_mixdown_present 1 uimsbf
//if ( mono_mixdown_present == 1 )
//mono_mixdown_element_number 4 uimsbf
//stereo_mixdown_present 1 uimsbf
//if ( stereo_mixdown_present == 1 )
//stereo_mixdown_element_number 4 uimsbf
//matrix_mixdown_idx_present 1 uimsbf
//if ( matrix_mixdown_idx_present == 1 ) {
//matrix_mixdown_idx 2 uimsbf
//pseudo_surround_enable 1 uimsbf
//}
//for ( i = 0; i < num_front_channel_elements; i++) {
//front_element_is_cpe[i]; 1 bslbf
//front_element_tag_select[i]; 4 uimsbf
//}
//for ( i = 0; i < num_side_channel_elements; i++) {
//side_element_is_cpe[i]; 1 bslbf
//side_element_tag_select[i]; 4 uimsbf
//}
//for ( i = 0; i < num_back_channel_elements; i++) {
//back_element_is_cpe[i]; 1 bslbf
//back_element_tag_select[i]; 4 uimsbf
//}
//for ( i = 0; i < num_lfe_channel_elements; i++)
//lfe_element_tag_select[i]; 4 uimsbf
//for ( i = 0; i < num_assoc_data_elements; i++)
//assoc_data_element_tag_select[i]; 4 uimsbf
//for ( i = 0; i < num_valid_cc_elements; i++) {
//cc_element_is_ind_sw[i]; 1 uimsbf
//valid_cc_element_tag_select[i]; 4 uimsbf
//}
//byte_alignment()
//comment_field_bytes 8 uimsbf
//for ( i = 0; i < comment_field_bytes; i++)
//comment_field_data[i]; 8 uimsbf
//}

@Descriptor(tags = 0x5, objectTypeIndication = 0x40)
public class AudioSpecificConfig extends BaseDescriptor {
    public static Map<Integer, Integer> samplingFrequencyIndexMap = new HashMap<Integer, Integer>();
    public static Map<Integer, String> audioObjectTypeMap = new HashMap<Integer, String>();

    static {
        // sampling_frequency_index sampling frequeny
//0x0 96000
//0x1 88200
//0x2 64000
//0x3 48000
//0x4 44100
//0x5 32000
//0x6 24000
//0x7 22050
//0x8 16000
//0x9 12000
//0xa 11025
//0xb 8000
//0xc reserved
//0xd reserved
//0xe reserved
//0xf reserved
        samplingFrequencyIndexMap.put(0x0, 96000);
        samplingFrequencyIndexMap.put(0x1, 88200);
        samplingFrequencyIndexMap.put(0x2, 64000);
        samplingFrequencyIndexMap.put(0x3, 48000);
        samplingFrequencyIndexMap.put(0x4, 44100);
        samplingFrequencyIndexMap.put(0x5, 32000);
        samplingFrequencyIndexMap.put(0x6, 24000);
        samplingFrequencyIndexMap.put(0x7, 22050);
        samplingFrequencyIndexMap.put(0x8, 16000);
        samplingFrequencyIndexMap.put(0x9, 12000);
        samplingFrequencyIndexMap.put(0xa, 11025);
        samplingFrequencyIndexMap.put(0xb, 8000);

        /* audioObjectType IDs
          0 Null
        1 AAC main X X
        2 AAC LC X X X X X X X
        3 AAC SSR X X
        4 AAC LTP X X X X
        5 SBR X X
        6 AAC Scalable X X X X
        7 TwinVQ X X X
        8 CELP X X X X X X
        9 HVXC X X X X X
        10 (reserved)
        11 (reserved)
        12 TTSI X X X X X X
        13 Main synthetic X X
        14 Wavetable synthesis X* X*
        15 General MIDI X* X*
        16 Algorithmic Synthesis and Audio FX X* X*
        17 ER AAC LC X X X
        18 (reserved)
        19 ER AAC LTP X X
        20 ER AAC Scalable X X X
        21 ER TwinVQ X X
        22 ER BSAC X X
        23 ER AAC LD X X X X
        24 ER CELP X X X
        25 ER HVXC X X
        26 ER HILN X
        27 ER Parametric X
        28 SSC
        29 PS X
        30 MPEG Surround
        31 (escape)
        32 Layer-1
        33 Layer-2
        34 Layer-3
        35 DST
        36 ALS
        37 SLS
        38 SLS non-core
        39 ER AAC ELD
        40 SMR Simple
        41 SMR Main
        */
        audioObjectTypeMap.put(1, "AAC main");
        audioObjectTypeMap.put(2, "AAC LC");
        audioObjectTypeMap.put(3, "AAC SSR");
        audioObjectTypeMap.put(4, "AAC LTP");
        audioObjectTypeMap.put(5, "SBR");
        audioObjectTypeMap.put(6, "AAC Scalable");
        audioObjectTypeMap.put(7, "TwinVQ");
        audioObjectTypeMap.put(8, "CELP");
        audioObjectTypeMap.put(9, "HVXC");
        audioObjectTypeMap.put(10, "(reserved)");
        audioObjectTypeMap.put(11, "(reserved)");
        audioObjectTypeMap.put(12, "TTSI");
        audioObjectTypeMap.put(13, "Main synthetic");
        audioObjectTypeMap.put(14, "Wavetable synthesis");
        audioObjectTypeMap.put(15, "General MIDI");
        audioObjectTypeMap.put(16, "Algorithmic Synthesis and Audio FX");
        audioObjectTypeMap.put(17, "ER AAC LC");
        audioObjectTypeMap.put(18, "(reserved)");
        audioObjectTypeMap.put(19, "ER AAC LTP");
        audioObjectTypeMap.put(20, "ER AAC Scalable");
        audioObjectTypeMap.put(21, "ER TwinVQ");
        audioObjectTypeMap.put(22, "ER BSAC");
        audioObjectTypeMap.put(23, "ER AAC LD");
        audioObjectTypeMap.put(24, "ER CELP");
        audioObjectTypeMap.put(25, "ER HVXC");
        audioObjectTypeMap.put(26, "ER HILN");
        audioObjectTypeMap.put(27, "ER Parametric");
        audioObjectTypeMap.put(28, "SSC");
        audioObjectTypeMap.put(29, "PS");
        audioObjectTypeMap.put(30, "MPEG Surround");
        audioObjectTypeMap.put(31, "(escape)");
        audioObjectTypeMap.put(32, "Layer-1");
        audioObjectTypeMap.put(33, "Layer-2");
        audioObjectTypeMap.put(34, "Layer-3");
        audioObjectTypeMap.put(35, "DST");
        audioObjectTypeMap.put(36, "ALS");
        audioObjectTypeMap.put(37, "SLS");
        audioObjectTypeMap.put(38, "SLS non-core");
        audioObjectTypeMap.put(39, "ER AAC ELD");
        audioObjectTypeMap.put(40, "SMR Simple");
        audioObjectTypeMap.put(41, "SMR Main");

        /* profileLevelIds
       0x00 Reserved for ISO use -
     0x01 Main Audio Profile L1
     0x02 Main Audio Profile L2
     0x03 Main Audio Profile L3
     0x04 Main Audio Profile L4
     0x05 Scalable Audio Profile L1
     0x06 Scalable Audio Profile L2
     0x07 Scalable Audio Profile L3
     0x08 Scalable Audio Profile L4
     0x09 Speech Audio Profile L1
     0x0A Speech Audio Profile L2
     0x0B Synthetic Audio Profile L1
     0x0C Synthetic Audio Profile L2
     0x0D Synthetic Audio Profile L3
     0x0E High Quality Audio Profile L1
     0x0F High Quality Audio Profile L2
     0x10 High Quality Audio Profile L3
     0x11 High Quality Audio Profile L4
     0x12 High Quality Audio Profile L5
     0x13 High Quality Audio Profile L6
     0x14 High Quality Audio Profile L7
     0x15 High Quality Audio Profile L8
     0x16 Low Delay Audio Profile L1
     0x17 Low Delay Audio Profile L2
     0x18 Low Delay Audio Profile L3
     0x19 Low Delay Audio Profile L4
     0x1A Low Delay Audio Profile L5
     0x1B Low Delay Audio Profile L6
     0x1C Low Delay Audio Profile L7
     0x1D Low Delay Audio Profile L8
     0x1E Natural Audio Profile L1
     0x1F Natural Audio Profile L2
     0x20 Natural Audio Profile L3
     0x21 Natural Audio Profile L4
     0x22 Mobile Audio Internetworking Profile L1
     0x23 Mobile Audio Internetworking Profile L2
     0x24 Mobile Audio Internetworking Profile L3
     0x25 Mobile Audio Internetworking Profile L4
     0x26 Mobile Audio Internetworking Profile L5
     0x27 Mobile Audio Internetworking Profile L6
     0x28 AAC Profile L1
     0x29 AAC Profile L2
     0x2A AAC Profile L4
     0x2B AAC Profile L5
     0x2C High Efficiency AAC Profile L2
     0x2D High Efficiency AAC Profile L3
     0x2E High Efficiency AAC Profile L4
     0x2F High Efficiency AAC Profile L5
     0x30 High Efficiency AAC v2 Profile L2
     0x31 High Efficiency AAC v2 Profile L3
     0x32 High Efficiency AAC v2 Profile L4
     0x33 High Efficiency AAC v2 Profile L5
     0x34 Low Delay AAC Profile L1
     0x35 Baseline MPEG Surround Profile (see ISO/IEC
     23003-1)
     L1
     0x36 Baseline MPEG Surround Profile (see ISO/IEC
     23003-1)
     L2
     0x37 Baseline MPEG Surround Profile (see ISO/IEC
     23003-1)
     L3
     0x38 Baseline MPEG Surround Profile (see ISO/IEC
     23003-1)
     L4
     0c39 Baseline MPEG Surround Profile (see ISO/IEC
     23003-1)
     L5
     0x3A Baseline MPEG Surround Profile (see ISO/IEC
     23003-1)
     L6
     0x3B - 0x7F reserved for ISO use -
     0x80 - 0xFD user private -
     0xFE no audio profile specified -
     0xFF no audio capability required -

        */
    }

    public ELDSpecificConfig eldSpecificConfig;
    public int audioObjectType;
    public int originalAudioObjectType;
    public int samplingFrequencyIndex;
    public int samplingFrequency;
    public int channelConfiguration;
    public int extensionAudioObjectType;
    public int origExtensionAudioObjectType;
    public boolean sbrPresentFlag;
    public boolean psPresentFlag;
    public int extensionSamplingFrequencyIndex = -1;
    public int extensionSamplingFrequency;
    public int extensionChannelConfiguration;
    public int sacPayloadEmbedding;
    public int fillBits;
    public int epConfig;
    public int directMapping;
    public int syncExtensionType = -1;
    public int innerSyncExtensionType = -1;
    public int outerSyncExtensionType = -1;
    //GASpecificConfig
    public int frameLengthFlag;
    public int dependsOnCoreCoder;
    public int coreCoderDelay;
    public int extensionFlag;
    public int layerNr;
    public int numOfSubFrame;
    public int layer_length;
    public boolean aacSectionDataResilienceFlag;
    public boolean aacScalefactorDataResilienceFlag;
    public boolean aacSpectralDataResilienceFlag;
    public int extensionFlag3;
    public boolean gaSpecificConfig;
    //ParametricSpecificConfig
    public int isBaseLayer;
    public int paraMode;
    public int paraExtensionFlag;
    public int hvxcVarMode;
    public int hvxcRateMode;
    public int erHvxcExtensionFlag;
    public int var_ScalableFlag;
    public int hilnQuantMode;
    public int hilnMaxNumLine;
    public int hilnSampleRateCode;
    public int hilnFrameLength;
    public int hilnContMode;
    public int hilnEnhaLayer;
    public int hilnEnhaQuantMode;
    public boolean parametricSpecificConfig;
    byte[] configBytes;

    public AudioSpecificConfig() {
        tag = 0x5;
    }

    boolean parsed = false;

    @Override
    public void parseDetail(ByteBuffer bb) throws IOException {
        parsed = true;
        ByteBuffer configBytes = bb.slice();
        configBytes.limit(sizeOfInstance);
        bb.position(bb.position() + sizeOfInstance);

        //copy original bytes to internal array for constructing codec config strings (todo until writing of the config is supported)
        this.configBytes = new byte[sizeOfInstance];
        configBytes.get(this.configBytes);
        configBytes.rewind();

        BitReaderBuffer bitReaderBuffer = new BitReaderBuffer(configBytes);
        originalAudioObjectType = audioObjectType = getAudioObjectType(bitReaderBuffer);
        samplingFrequencyIndex = bitReaderBuffer.readBits(4);

        if (samplingFrequencyIndex == 0xf) {
            samplingFrequency = bitReaderBuffer.readBits(24);
        }

        channelConfiguration = bitReaderBuffer.readBits(4);

        if (audioObjectType == 5 ||
                audioObjectType == 29) {
            extensionAudioObjectType = 5;
            sbrPresentFlag = true;
            if (audioObjectType == 29) {
                psPresentFlag = true;
            }
            extensionSamplingFrequencyIndex = bitReaderBuffer.readBits(4);
            if (extensionSamplingFrequencyIndex == 0xf)
                extensionSamplingFrequency = bitReaderBuffer.readBits(24);
            audioObjectType = getAudioObjectType(bitReaderBuffer);
            if (audioObjectType == 22)
                extensionChannelConfiguration = bitReaderBuffer.readBits(4);
        } else {
            extensionAudioObjectType = 0;
        }

        switch (audioObjectType) {
            case 1:
            case 2:
            case 3:
            case 4:
            case 6:
            case 7:
            case 17:
            case 19:
            case 20:
            case 21:
            case 22:
            case 23:
                parseGaSpecificConfig(samplingFrequencyIndex, channelConfiguration, audioObjectType, bitReaderBuffer);
                //GASpecificConfig();
                break;
            case 8:
                throw new UnsupportedOperationException("can't parse CelpSpecificConfig yet");
                //CelpSpecificConfig();
                //break;
            case 9:
                throw new UnsupportedOperationException("can't parse HvxcSpecificConfig yet");
                //HvxcSpecificConfig();
                //break;
            case 12:
                throw new UnsupportedOperationException("can't parse TTSSpecificConfig yet");
                //TTSSpecificConfig();
                //break;
            case 13:
            case 14:
            case 15:
            case 16:
                throw new UnsupportedOperationException("can't parse StructuredAudioSpecificConfig yet");
                //StructuredAudioSpecificConfig();
                //break;
            case 24:
                throw new UnsupportedOperationException("can't parse ErrorResilientCelpSpecificConfig yet");
                //ErrorResilientCelpSpecificConfig();
                //break;
            case 25:
                throw new UnsupportedOperationException("can't parse ErrorResilientHvxcSpecificConfig yet");
                //ErrorResilientHvxcSpecificConfig();
                //break;
            case 26:
            case 27:
                parseParametricSpecificConfig(samplingFrequencyIndex, channelConfiguration, audioObjectType, bitReaderBuffer);
                //ParametricSpecificConfig();
                break;
            case 28:
                throw new UnsupportedOperationException("can't parse SSCSpecificConfig yet");
                //SSCSpecificConfig();
                //break;
            case 30:
                sacPayloadEmbedding = bitReaderBuffer.readBits(1);
                throw new UnsupportedOperationException("can't parse SpatialSpecificConfig yet");
                //SpatialSpecificConfig();
                //break;
            case 32:
            case 33:
            case 34:
                throw new UnsupportedOperationException("can't parse MPEG_1_2_SpecificConfig yet");
                //MPEG_1_2_SpecificConfig();
                //break;
            case 35:
                throw new UnsupportedOperationException("can't parse DSTSpecificConfig yet");
                //DSTSpecificConfig();
                //break;
            case 36:
                fillBits = bitReaderBuffer.readBits(5);
                throw new UnsupportedOperationException("can't parse ALSSpecificConfig yet");
                //ALSSpecificConfig();
                //break;
            case 37:
            case 38:
                throw new UnsupportedOperationException("can't parse SLSSpecificConfig yet");
                //SLSSpecificConfig();
                //break;
            case 39:
                eldSpecificConfig = new ELDSpecificConfig(channelConfiguration, bitReaderBuffer);
                break;
            case 40:
            case 41:
                throw new UnsupportedOperationException("can't parse SymbolicMusicSpecificConfig yet");
                //SymbolicMusicSpecificConfig();
                //break;
            default:
                /* reserved */
        }

        switch (audioObjectType) {
            case 17:
            case 19:
            case 20:
            case 21:
            case 22:
            case 23:
            case 24:
            case 25:
            case 26:
            case 27:
            case 39:
                epConfig = bitReaderBuffer.readBits(2);
                if (epConfig == 2 || epConfig == 3) {
                    throw new UnsupportedOperationException("can't parse ErrorProtectionSpecificConfig yet");
                    //ErrorProtectionSpecificConfig();
                }
                if (epConfig == 3) {
                    directMapping = bitReaderBuffer.readBits(1);
                    if (directMapping == 0) {
                        /* tbd */
                        throw new RuntimeException("not implemented");
                    }
                }
        }

        if (extensionAudioObjectType != 5 && bitReaderBuffer.remainingBits() >= 16) {
            outerSyncExtensionType = syncExtensionType = bitReaderBuffer.readBits(11);
            if (syncExtensionType == 0x2b7) {
                extensionAudioObjectType = getAudioObjectType(bitReaderBuffer);
                if (extensionAudioObjectType == 5) {
                    sbrPresentFlag = bitReaderBuffer.readBool();
                    if (sbrPresentFlag) {
                        extensionSamplingFrequencyIndex = bitReaderBuffer.readBits(4);
                        if (extensionSamplingFrequencyIndex == 0xf) {
                            extensionSamplingFrequency = bitReaderBuffer.readBits(24);
                        }
                        if (bitReaderBuffer.remainingBits() >= 12) {
                            innerSyncExtensionType = syncExtensionType = bitReaderBuffer.readBits(11); //10101001000
                            if (syncExtensionType == 0x548) {
                                psPresentFlag = bitReaderBuffer.readBool();
                            }
                        }
                    }
                }
                if (extensionAudioObjectType == 22) {
                    sbrPresentFlag = bitReaderBuffer.readBool();
                    if (sbrPresentFlag) {
                        extensionSamplingFrequencyIndex = bitReaderBuffer.readBits(4);
                        if (extensionSamplingFrequencyIndex == 0xf) {
                            extensionSamplingFrequency = bitReaderBuffer.readBits(24);
                        }
                    }
                    extensionChannelConfiguration = bitReaderBuffer.readBits(4);
                }
            }
        }
    }

    private int gaSpecificConfigSize() {
        int n = 0;
        n += 1; // frameLengthFlag = in.readBits(1);
        n += 1; //dependsOnCoreCoder = in.readBits(1);
        if (dependsOnCoreCoder == 1) {
            n += 14; // coreCoderDelay = in.readBits(14);
        }
        n += 1; // extensionFlag = in.readBits(1);
        if (channelConfiguration == 0) {
            throw new UnsupportedOperationException("can't parse program_config_element yet");
            //program_config_element ();
        }
        if ((audioObjectType == 6) || (audioObjectType == 20)) {
            n += 3; // layerNr = in.readBits(3);
        }
        if (extensionFlag == 1) {
            if (audioObjectType == 22) {
                n += 5; // numOfSubFrame = in.readBits(5);
                n += 11; //layer_length = in.readBits(11);
            }
            if (audioObjectType == 17 || audioObjectType == 19 ||
                    audioObjectType == 20 || audioObjectType == 23) {
                n += 1; // aacSectionDataResilienceFlag = in.readBool();
                n += 1; //aacScalefactorDataResilienceFlag = in.readBool();
                n += 1; //aacSpectralDataResilienceFlag = in.readBool();
            }
            n += 1; //extensionFlag3 = in.readBits(1);
            if (extensionFlag3 == 1) {
                throw new RuntimeException("Not implemented");
            }
        }
        return n;
    }

    int getContentSize() {
        int sizeInBits = 5;
        if (originalAudioObjectType > 30) {
            sizeInBits += 6; // extended type
        }
        sizeInBits += 4; // samplingFrequencyIndex
        if (samplingFrequencyIndex == 0xf) {
            sizeInBits += 24;
        }
        sizeInBits += 4; // channelConfiguration
        if (audioObjectType == 5 ||
                audioObjectType == 29) {
            sizeInBits += 4; // extensionSamplingFrequencyIndex = bitReaderBuffer.readBits(4);
            if (extensionSamplingFrequencyIndex == 0xf) {
                // extensionSamplingFrequency = bitReaderBuffer.readBits(24);
                sizeInBits += 24;
            }
        }

        if (audioObjectType == 22) {
            sizeInBits += 4; //   extensionChannelConfiguration
        }


        if (gaSpecificConfig) {
            sizeInBits += gaSpecificConfigSize();
        }
        if (outerSyncExtensionType >= 0) {
            sizeInBits += 11; //outerSyncExtensionType = syncExtensionType = bitReaderBuffer.readBits(11);
            if (outerSyncExtensionType == 0x2b7) {
                sizeInBits += 5;
                if (extensionAudioObjectType > 30) {
                    sizeInBits += 6; // extended type
                } // extensionAudioObjectType = getAudioObjectType(bitReaderBuffer);
                if (extensionAudioObjectType == 5) {
                    sizeInBits += 1;
                    if (sbrPresentFlag) {
                        sizeInBits += 4; // extensionSamplingFrequencyIndex = bitReaderBuffer.readBits(4);
                        if (extensionSamplingFrequencyIndex == 0xf) {
                            sizeInBits += 24; // extensionSamplingFrequency = bitReaderBuffer.readBits(24);
                        }
                        if (innerSyncExtensionType >= 0) {
                            sizeInBits += 11;   // innerSyncExtensionType = syncExtensionType = bitReaderBuffer.readBits(11); //10101001000
                            if (innerSyncExtensionType == 0x548) {
                                sizeInBits += 1; // psPresentFlag = bitReaderBuffer.readBool();
                            }
                        }
                    }
                }
                if (extensionAudioObjectType == 22) {
                    sizeInBits += 1; //sbrPresentFlag = bitReaderBuffer.readBool();
                    if (sbrPresentFlag) {
                        sizeInBits += 4; // extensionSamplingFrequencyIndex = bitReaderBuffer.readBits(4);
                        if (extensionSamplingFrequencyIndex == 0xf) {
                            sizeInBits += 24; //extensionSamplingFrequency = bitReaderBuffer.readBits(24);
                        }
                    }
                    sizeInBits += 4; //extensionChannelConfiguration = bitReaderBuffer.readBits(4);
                }
            }
        }
        return (int) Math.ceil(((double) sizeInBits) / 8);
    }

    public ByteBuffer serialize() {
        ByteBuffer out = ByteBuffer.allocate(getSize());
        IsoTypeWriter.writeUInt8(out, tag);
        writeSize(out, getContentSize());
        out.put(serializeConfigBytes());
        return (ByteBuffer) out.rewind();
    }

    private ByteBuffer serializeConfigBytes() {
        ByteBuffer out = ByteBuffer.wrap(new byte[getContentSize()]);
        BitWriterBuffer bitWriterBuffer = new BitWriterBuffer(out);
        writeAudioObjectType(originalAudioObjectType, bitWriterBuffer);
        bitWriterBuffer.writeBits(samplingFrequencyIndex, 4);

        if (samplingFrequencyIndex == 0xf) {
            bitWriterBuffer.writeBits(samplingFrequency, 24);
        }

        bitWriterBuffer.writeBits(channelConfiguration, 4);

        if (audioObjectType == 5 ||
                audioObjectType == 29) {
            extensionAudioObjectType = 5;
            sbrPresentFlag = true;
            if (audioObjectType == 29) {
                psPresentFlag = true;
            }
            bitWriterBuffer.writeBits(extensionSamplingFrequencyIndex, 4);
            if (extensionSamplingFrequencyIndex == 0xf)
                bitWriterBuffer.writeBits(extensionSamplingFrequency, 24);
            writeAudioObjectType(audioObjectType, bitWriterBuffer);
            if (audioObjectType == 22)
                bitWriterBuffer.writeBits(extensionChannelConfiguration, 4);
        }
        switch (audioObjectType) {
            case 1:
            case 2:
            case 3:
            case 4:
            case 6:
            case 7:
            case 17:
            case 19:
            case 20:
            case 21:
            case 22:
            case 23:
                writeGaSpecificConfig(bitWriterBuffer);
                //GASpecificConfig();
                break;
            case 8:
                throw new UnsupportedOperationException("can't write CelpSpecificConfig yet");
                //CelpSpecificConfig();
                //break;
            case 9:
                throw new UnsupportedOperationException("can't write HvxcSpecificConfig yet");
                //HvxcSpecificConfig();
                //break;
            case 12:
                throw new UnsupportedOperationException("can't write TTSSpecificConfig yet");
                //TTSSpecificConfig();
                //break;
            case 13:
            case 14:
            case 15:
            case 16:
                throw new UnsupportedOperationException("can't write StructuredAudioSpecificConfig yet");
                //StructuredAudioSpecificConfig();
                //break;
            case 24:
                throw new UnsupportedOperationException("can't write ErrorResilientCelpSpecificConfig yet");
                //ErrorResilientCelpSpecificConfig();
                //break;
            case 25:
                throw new UnsupportedOperationException("can't write ErrorResilientHvxcSpecificConfig yet");
                //ErrorResilientHvxcSpecificConfig();
                //break;
            case 26:
            case 27:
                throw new UnsupportedOperationException("can't write parseParametricSpecificConfig yet");
                // parseParametricSpecificConfig(samplingFrequencyIndex, channelConfiguration, audioObjectType, bitWriterBuffer);
                // ParametricSpecificConfig();
                // break;
            case 28:
                throw new UnsupportedOperationException("can't write SSCSpecificConfig yet");
                //SSCSpecificConfig();
                //break;
            case 30:
                bitWriterBuffer.writeBits(sacPayloadEmbedding, 1);
                throw new UnsupportedOperationException("can't write SpatialSpecificConfig yet");
                //SpatialSpecificConfig();
                //break;
            case 32:
            case 33:
            case 34:
                throw new UnsupportedOperationException("can't write MPEG_1_2_SpecificConfig yet");
                //MPEG_1_2_SpecificConfig();
                //break;
            case 35:
                throw new UnsupportedOperationException("can't write DSTSpecificConfig yet");
                //DSTSpecificConfig();
                //break;
            case 36:
                bitWriterBuffer.writeBits(fillBits, 5);
                throw new UnsupportedOperationException("can't write ALSSpecificConfig yet");
                //ALSSpecificConfig();
                //break;
            case 37:
            case 38:
                throw new UnsupportedOperationException("can't write SLSSpecificConfig yet");
                //SLSSpecificConfig();
                //break;
            case 39:
                throw new UnsupportedOperationException("can't write ELDSpecificConfig yet");
                //eldSpecificConfig = new ELDSpecificConfig(channelConfiguration, bitWriterBuffer);
                // break;
            case 40:
            case 41:
                throw new UnsupportedOperationException("can't parse SymbolicMusicSpecificConfig yet");
                //SymbolicMusicSpecificConfig();
                //break;
            default:
                /* reserved */
        }

        switch (audioObjectType) {
            case 17:
            case 19:
            case 20:
            case 21:
            case 22:
            case 23:
            case 24:
            case 25:
            case 26:
            case 27:
            case 39:
                bitWriterBuffer.writeBits(epConfig, 2);
                if (epConfig == 2 || epConfig == 3) {
                    throw new UnsupportedOperationException("can't parse ErrorProtectionSpecificConfig yet");
                    //ErrorProtectionSpecificConfig();
                }
                if (epConfig == 3) {
                    bitWriterBuffer.writeBits(directMapping, 1);
                    if (directMapping == 0) {
                        /* tbd */
                        throw new RuntimeException("not implemented");
                    }
                }
        }

        if (outerSyncExtensionType >= 0) {
            bitWriterBuffer.writeBits(outerSyncExtensionType, 11);
            if (outerSyncExtensionType == 0x2b7) {// 695
                writeAudioObjectType(extensionAudioObjectType, bitWriterBuffer);
                if (extensionAudioObjectType == 5) {
                    bitWriterBuffer.writeBool(sbrPresentFlag);
                    if (sbrPresentFlag) {
                        bitWriterBuffer.writeBits(extensionSamplingFrequencyIndex, 4);
                        if (extensionSamplingFrequencyIndex == 0xf) {
                            bitWriterBuffer.writeBits(extensionSamplingFrequency, 24);
                        }
                        if (innerSyncExtensionType >= 0) {
                            bitWriterBuffer.writeBits(innerSyncExtensionType, 11); //10101001000
                            if (syncExtensionType == 0x548) {
                                bitWriterBuffer.writeBool(psPresentFlag);
                            }
                        }
                    }
                }
                if (extensionAudioObjectType == 22) {
                    bitWriterBuffer.writeBool(sbrPresentFlag);
                    if (sbrPresentFlag) {
                        bitWriterBuffer.writeBits(extensionSamplingFrequencyIndex, 4);
                        if (extensionSamplingFrequencyIndex == 0xf) {
                            bitWriterBuffer.writeBits(extensionSamplingFrequency, 24);
                        }
                    }
                    bitWriterBuffer.writeBits(extensionChannelConfiguration, 4);
                }
            }
        }

        return (ByteBuffer) out.rewind();
    }

    private void writeAudioObjectType(int audioObjectType, BitWriterBuffer bitWriterBuffer) {
        if (audioObjectType >= 32) {
            bitWriterBuffer.writeBits(31, 5);
            bitWriterBuffer.writeBits(audioObjectType - 32, 6);
        } else {
            bitWriterBuffer.writeBits(audioObjectType, 5);
        }
    }

    private int getAudioObjectType(BitReaderBuffer in) throws IOException {
        int audioObjectType = in.readBits(5);
        if (audioObjectType == 31) {
            audioObjectType = 32 + in.readBits(6);
        }
        return audioObjectType;
    }

    private void parseGaSpecificConfig(int samplingFrequencyIndex, int channelConfiguration, int audioObjectType, BitReaderBuffer in) throws IOException {
//    GASpecificConfig (samplingFrequencyIndex,
//            channelConfiguration,
//            audioObjectType)
//    {
        frameLengthFlag = in.readBits(1);
        dependsOnCoreCoder = in.readBits(1);
        if (dependsOnCoreCoder == 1) {
            coreCoderDelay = in.readBits(14);
        }
        extensionFlag = in.readBits(1);
        if (channelConfiguration == 0) {
            throw new UnsupportedOperationException("can't parse program_config_element yet");
            //program_config_element ();
        }
        if ((audioObjectType == 6) || (audioObjectType == 20)) {
            layerNr = in.readBits(3);
        }
        if (extensionFlag == 1) {
            if (audioObjectType == 22) {
                numOfSubFrame = in.readBits(5);
                layer_length = in.readBits(11);
            }
            if (audioObjectType == 17 || audioObjectType == 19 ||
                    audioObjectType == 20 || audioObjectType == 23) {
                aacSectionDataResilienceFlag = in.readBool();
                aacScalefactorDataResilienceFlag = in.readBool();
                aacSpectralDataResilienceFlag = in.readBool();
            }
            extensionFlag3 = in.readBits(1);
            if (extensionFlag3 == 1) {
                throw new RuntimeException("not yet implemented");
            }
        }
//    }
        gaSpecificConfig = true;
    }

    private void writeGaSpecificConfig(BitWriterBuffer out) {
//    GASpecificConfig (samplingFrequencyIndex,
//            channelConfiguration,
//            audioObjectType)
//    {
        out.writeBits(frameLengthFlag, 1); // frameLengthFlag = in.readBits(1);
        out.writeBits(dependsOnCoreCoder, 1); //= in.readBits(1);
        if (dependsOnCoreCoder == 1) {
            out.writeBits(coreCoderDelay, 14); // = in.readBits(14);
        }
        out.writeBits(extensionFlag, 1); // = in.readBits(1);
        if (channelConfiguration == 0) {
            throw new UnsupportedOperationException("can't parse program_config_element yet");
            //program_config_element ();
        }
        if ((audioObjectType == 6) || (audioObjectType == 20)) {
            out.writeBits(layerNr, 3);// = in.readBits(3);
        }
        if (extensionFlag == 1) {
            if (audioObjectType == 22) {
                out.writeBits(numOfSubFrame, 5); // = in.readBits(5);
                out.writeBits(layer_length, 11); // = in.readBits(11);
            }
            if (audioObjectType == 17 || audioObjectType == 19 ||
                    audioObjectType == 20 || audioObjectType == 23) {
                out.writeBool(aacSectionDataResilienceFlag); // = in.readBool();
                out.writeBool(aacScalefactorDataResilienceFlag); // = in.readBool();
                out.writeBool(aacSpectralDataResilienceFlag); // = in.readBool();
            }
            out.writeBits(extensionFlag3, 1); // = in.readBits(1);
            if (extensionFlag3 == 1) {
                throw new RuntimeException("not yet implemented");
            }
        }
    }

    private void parseParametricSpecificConfig(int samplingFrequencyIndex, int channelConfiguration, int audioObjectType, BitReaderBuffer in) throws IOException {
        /*
        ParametricSpecificConfig() {
            isBaseLayer; 1 uimsbf
            if (isBaseLayer) {
                PARAconfig();
            } else {
                HILNenexConfig();
            }
        }
        */
        isBaseLayer = in.readBits(1);
        if (isBaseLayer == 1) {
            parseParaConfig(samplingFrequencyIndex, channelConfiguration, audioObjectType, in);
        } else {
            parseHilnEnexConfig(samplingFrequencyIndex, channelConfiguration, audioObjectType, in);
        }
    }

    private void parseParaConfig(int samplingFrequencyIndex, int channelConfiguration, int audioObjectType, BitReaderBuffer in) throws IOException {
        /*
        PARAconfig()
        {
            PARAmode; 2 uimsbf
            if (PARAmode != 1) {
                ErHVXCconfig();
            }
            if (PARAmode != 0) {
                HILNconfig();
            }
            PARAextensionFlag; 1 uimsbf
            if (PARAextensionFlag) {
                // to be defined in MPEG-4 Phase 3
            }
        }
        */
        paraMode = in.readBits(2);

        if (paraMode != 1) {
            parseErHvxcConfig(samplingFrequencyIndex, channelConfiguration, audioObjectType, in);
        }
        if (paraMode != 0) {
            parseHilnConfig(samplingFrequencyIndex, channelConfiguration, audioObjectType, in);
        }

        paraExtensionFlag = in.readBits(1);
        parametricSpecificConfig = true;
    }

    private void parseErHvxcConfig(int samplingFrequencyIndex, int channelConfiguration, int audioObjectType, BitReaderBuffer in) throws IOException {
        /*
        ErHVXCconfig()
        {
            HVXCvarMode; 1 uimsbf
                HVXCrateMode; 2 uimsbf
                extensionFlag; 1 uimsbf
            if (extensionFlag) {
                var_ScalableFlag; 1 uimsbf
            }
        }
        */
        hvxcVarMode = in.readBits(1);
        hvxcRateMode = in.readBits(2);
        erHvxcExtensionFlag = in.readBits(1);

        if (erHvxcExtensionFlag == 1) {
            var_ScalableFlag = in.readBits(1);
        }
    }

    private void parseHilnConfig(int samplingFrequencyIndex, int channelConfiguration, int audioObjectType, BitReaderBuffer in) throws IOException {
        /*
        HILNconfig()
        {
            HILNquantMode; 1 uimsbf
            HILNmaxNumLine; 8 uimsbf
            HILNsampleRateCode; 4 uimsbf
            HILNframeLength; 12 uimsbf
            HILNcontMode; 2 uimsbf
        }
        */
        hilnQuantMode = in.readBits(1);
        hilnMaxNumLine = in.readBits(8);
        hilnSampleRateCode = in.readBits(4);
        hilnFrameLength = in.readBits(12);
        hilnContMode = in.readBits(2);
    }

    private void parseHilnEnexConfig(int samplingFrequencyIndex, int channelConfiguration, int audioObjectType, BitReaderBuffer in) throws IOException {
        /*
        HILNenexConfig()
        {
            HILNenhaLayer; 1 uimsbf
            if (HILNenhaLayer) {
                HILNenhaQuantMode; 2 uimsbf
            }
        }
        */
        hilnEnhaLayer = in.readBits(1);
        if (hilnEnhaLayer == 1) {
            hilnEnhaQuantMode = in.readBits(2);
        }
    }

    public byte[] getConfigBytes() {
        return serializeConfigBytes().array();
    }

    public int getAudioObjectType() {
        return audioObjectType;
    }

    public void setAudioObjectType(int audioObjectType) {
        this.audioObjectType = audioObjectType;
    }

    public void setOriginalAudioObjectType(int originalAudioObjectType) {
        this.originalAudioObjectType = originalAudioObjectType;
    }

    public int getExtensionAudioObjectType() {
        return extensionAudioObjectType;
    }

    public void setSamplingFrequencyIndex(int samplingFrequencyIndex) {
        this.samplingFrequencyIndex = samplingFrequencyIndex;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder();
        sb.append("AudioSpecificConfig");
        sb.append("{configBytes=").append(Hex.encodeHex(configBytes));
        sb.append(", audioObjectType=").append(audioObjectType).append(" (").append(audioObjectTypeMap.get(audioObjectType)).append(")");
        sb.append(", samplingFrequencyIndex=").append(samplingFrequencyIndex).append(" (").append(samplingFrequencyIndexMap.get(samplingFrequencyIndex)).append(")");
        sb.append(", samplingFrequency=").append(samplingFrequency);
        sb.append(", channelConfiguration=").append(channelConfiguration);
        if (extensionAudioObjectType > 0) {
            sb.append(", extensionAudioObjectType=").append(extensionAudioObjectType).append(" (").append(audioObjectTypeMap.get(extensionAudioObjectType)).append(")");
            sb.append(", sbrPresentFlag=").append(sbrPresentFlag);
            sb.append(", psPresentFlag=").append(psPresentFlag);
            sb.append(", extensionSamplingFrequencyIndex=").append(extensionSamplingFrequencyIndex).append(" (").append(samplingFrequencyIndexMap.get(extensionSamplingFrequencyIndex)).append(")");
            sb.append(", extensionSamplingFrequency=").append(extensionSamplingFrequency);
            sb.append(", extensionChannelConfiguration=").append(extensionChannelConfiguration);
        }
//    sb.append(", sacPayloadEmbedding=").append(sacPayloadEmbedding);
//    sb.append(", fillBits=").append(fillBits);
//    sb.append(", epConfig=").append(epConfig);
//    sb.append(", directMapping=").append(directMapping);
        sb.append(", syncExtensionType=").append(syncExtensionType);
        if (gaSpecificConfig) {
            sb.append(", frameLengthFlag=").append(frameLengthFlag);
            sb.append(", dependsOnCoreCoder=").append(dependsOnCoreCoder);
            sb.append(", coreCoderDelay=").append(coreCoderDelay);
            sb.append(", extensionFlag=").append(extensionFlag);
            sb.append(", layerNr=").append(layerNr);
            sb.append(", numOfSubFrame=").append(numOfSubFrame);
            sb.append(", layer_length=").append(layer_length);
            sb.append(", aacSectionDataResilienceFlag=").append(aacSectionDataResilienceFlag);
            sb.append(", aacScalefactorDataResilienceFlag=").append(aacScalefactorDataResilienceFlag);
            sb.append(", aacSpectralDataResilienceFlag=").append(aacSpectralDataResilienceFlag);
            sb.append(", extensionFlag3=").append(extensionFlag3);
        }
        if (parametricSpecificConfig) {
            sb.append(", isBaseLayer=").append(isBaseLayer);
            sb.append(", paraMode=").append(paraMode);
            sb.append(", paraExtensionFlag=").append(paraExtensionFlag);
            sb.append(", hvxcVarMode=").append(hvxcVarMode);
            sb.append(", hvxcRateMode=").append(hvxcRateMode);
            sb.append(", erHvxcExtensionFlag=").append(erHvxcExtensionFlag);
            sb.append(", var_ScalableFlag=").append(var_ScalableFlag);
            sb.append(", hilnQuantMode=").append(hilnQuantMode);
            sb.append(", hilnMaxNumLine=").append(hilnMaxNumLine);
            sb.append(", hilnSampleRateCode=").append(hilnSampleRateCode);
            sb.append(", hilnFrameLength=").append(hilnFrameLength);
            sb.append(", hilnContMode=").append(hilnContMode);
            sb.append(", hilnEnhaLayer=").append(hilnEnhaLayer);
            sb.append(", hilnEnhaQuantMode=").append(hilnEnhaQuantMode);
        }
        sb.append('}');
        return sb.toString();
    }

    public int getSamplingFrequency() {
        return samplingFrequencyIndex == 0xf ? samplingFrequency : samplingFrequencyIndexMap.get(samplingFrequencyIndex);
    }

    public int getExtensionSamplingFrequency() {
        return extensionSamplingFrequencyIndex == 0xf ? extensionSamplingFrequency : samplingFrequencyIndexMap.get(extensionSamplingFrequencyIndex);
    }

    public void setSamplingFrequency(int samplingFrequency) {
        this.samplingFrequency = samplingFrequency;
    }

    public int getChannelConfiguration() {
        return channelConfiguration;
    }

    public void setChannelConfiguration(int channelConfiguration) {
        this.channelConfiguration = channelConfiguration;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        AudioSpecificConfig that = (AudioSpecificConfig) o;

        if (aacScalefactorDataResilienceFlag != that.aacScalefactorDataResilienceFlag) {
            return false;
        }
        if (aacSectionDataResilienceFlag != that.aacSectionDataResilienceFlag) {
            return false;
        }
        if (aacSpectralDataResilienceFlag != that.aacSpectralDataResilienceFlag) {
            return false;
        }
        if (audioObjectType != that.audioObjectType) {
            return false;
        }
        if (channelConfiguration != that.channelConfiguration) {
            return false;
        }
        if (coreCoderDelay != that.coreCoderDelay) {
            return false;
        }
        if (dependsOnCoreCoder != that.dependsOnCoreCoder) {
            return false;
        }
        if (directMapping != that.directMapping) {
            return false;
        }
        if (epConfig != that.epConfig) {
            return false;
        }
        if (erHvxcExtensionFlag != that.erHvxcExtensionFlag) {
            return false;
        }
        if (extensionAudioObjectType != that.extensionAudioObjectType) {
            return false;
        }
        if (extensionChannelConfiguration != that.extensionChannelConfiguration) {
            return false;
        }
        if (extensionFlag != that.extensionFlag) {
            return false;
        }
        if (extensionFlag3 != that.extensionFlag3) {
            return false;
        }
        if (extensionSamplingFrequency != that.extensionSamplingFrequency) {
            return false;
        }
        if (extensionSamplingFrequencyIndex != that.extensionSamplingFrequencyIndex) {
            return false;
        }
        if (fillBits != that.fillBits) {
            return false;
        }
        if (frameLengthFlag != that.frameLengthFlag) {
            return false;
        }
        if (gaSpecificConfig != that.gaSpecificConfig) {
            return false;
        }
        if (hilnContMode != that.hilnContMode) {
            return false;
        }
        if (hilnEnhaLayer != that.hilnEnhaLayer) {
            return false;
        }
        if (hilnEnhaQuantMode != that.hilnEnhaQuantMode) {
            return false;
        }
        if (hilnFrameLength != that.hilnFrameLength) {
            return false;
        }
        if (hilnMaxNumLine != that.hilnMaxNumLine) {
            return false;
        }
        if (hilnQuantMode != that.hilnQuantMode) {
            return false;
        }
        if (hilnSampleRateCode != that.hilnSampleRateCode) {
            return false;
        }
        if (hvxcRateMode != that.hvxcRateMode) {
            return false;
        }
        if (hvxcVarMode != that.hvxcVarMode) {
            return false;
        }
        if (isBaseLayer != that.isBaseLayer) {
            return false;
        }
        if (layerNr != that.layerNr) {
            return false;
        }
        if (layer_length != that.layer_length) {
            return false;
        }
        if (numOfSubFrame != that.numOfSubFrame) {
            return false;
        }
        if (paraExtensionFlag != that.paraExtensionFlag) {
            return false;
        }
        if (paraMode != that.paraMode) {
            return false;
        }
        if (parametricSpecificConfig != that.parametricSpecificConfig) {
            return false;
        }
        if (psPresentFlag != that.psPresentFlag) {
            return false;
        }
        if (sacPayloadEmbedding != that.sacPayloadEmbedding) {
            return false;
        }
        if (samplingFrequency != that.samplingFrequency) {
            return false;
        }
        if (samplingFrequencyIndex != that.samplingFrequencyIndex) {
            return false;
        }
        if (sbrPresentFlag != that.sbrPresentFlag) {
            return false;
        }
        if (syncExtensionType != that.syncExtensionType) {
            return false;
        }
        if (var_ScalableFlag != that.var_ScalableFlag) {
            return false;
        }
        if (!Arrays.equals(configBytes, that.configBytes)) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        int result = configBytes != null ? Arrays.hashCode(configBytes) : 0;
        result = 31 * result + audioObjectType;
        result = 31 * result + samplingFrequencyIndex;
        result = 31 * result + samplingFrequency;
        result = 31 * result + channelConfiguration;
        result = 31 * result + extensionAudioObjectType;
        result = 31 * result + (sbrPresentFlag ? 1 : 0);
        result = 31 * result + (psPresentFlag ? 1 : 0);
        result = 31 * result + extensionSamplingFrequencyIndex;
        result = 31 * result + extensionSamplingFrequency;
        result = 31 * result + extensionChannelConfiguration;
        result = 31 * result + sacPayloadEmbedding;
        result = 31 * result + fillBits;
        result = 31 * result + epConfig;
        result = 31 * result + directMapping;
        result = 31 * result + syncExtensionType;
        result = 31 * result + frameLengthFlag;
        result = 31 * result + dependsOnCoreCoder;
        result = 31 * result + coreCoderDelay;
        result = 31 * result + extensionFlag;
        result = 31 * result + layerNr;
        result = 31 * result + numOfSubFrame;
        result = 31 * result + layer_length;
        result = 31 * result + (aacSectionDataResilienceFlag ? 1 : 0);
        result = 31 * result + (aacScalefactorDataResilienceFlag ? 1 : 0);
        result = 31 * result + (aacSpectralDataResilienceFlag ? 1 : 0);
        result = 31 * result + extensionFlag3;
        result = 31 * result + (gaSpecificConfig ? 1 : 0);
        result = 31 * result + isBaseLayer;
        result = 31 * result + paraMode;
        result = 31 * result + paraExtensionFlag;
        result = 31 * result + hvxcVarMode;
        result = 31 * result + hvxcRateMode;
        result = 31 * result + erHvxcExtensionFlag;
        result = 31 * result + var_ScalableFlag;
        result = 31 * result + hilnQuantMode;
        result = 31 * result + hilnMaxNumLine;
        result = 31 * result + hilnSampleRateCode;
        result = 31 * result + hilnFrameLength;
        result = 31 * result + hilnContMode;
        result = 31 * result + hilnEnhaLayer;
        result = 31 * result + hilnEnhaQuantMode;
        result = 31 * result + (parametricSpecificConfig ? 1 : 0);
        return result;
    }

    public class ELDSpecificConfig {
        private static final int ELDEXT_TERM = 0x0;//0b0000;
        public boolean frameLengthFlag;
        public boolean aacSectionDataResilienceFlag;
        public boolean aacScalefactorDataResilienceFlag;
        public boolean aacSpectralDataResilienceFlag;
        public boolean ldSbrPresentFlag;
        public boolean ldSbrSamplingRate;
        public boolean ldSbrCrcFlag;

        public ELDSpecificConfig(int channelConfiguration, BitReaderBuffer bitReaderBuffer) {

            this.frameLengthFlag = bitReaderBuffer.readBool();
            this.aacSectionDataResilienceFlag = bitReaderBuffer.readBool();
            this.aacScalefactorDataResilienceFlag = bitReaderBuffer.readBool();
            this.aacSpectralDataResilienceFlag = bitReaderBuffer.readBool();

            this.ldSbrPresentFlag = bitReaderBuffer.readBool();
            if (ldSbrPresentFlag) {
                ldSbrSamplingRate = bitReaderBuffer.readBool();
                ldSbrCrcFlag = bitReaderBuffer.readBool();
                ld_sbr_header(channelConfiguration, bitReaderBuffer);
            }
            int eldExtType;
            while ((eldExtType = bitReaderBuffer.readBits(4)) != ELDEXT_TERM) {
                int eldExtLen = bitReaderBuffer.readBits(4);
                int len = eldExtLen;
                int eldExtLenAdd = 0;
                if (eldExtLen == 15) {
                    eldExtLenAdd = bitReaderBuffer.readBits(8);
                    len += eldExtLenAdd;
                }
                if (eldExtLenAdd == 255) {
                    int eldExtLenAddAdd = bitReaderBuffer.readBits(16);
                    len += eldExtLenAddAdd;
                }
                switch (eldExtType) {
                    /* add future eld extension configs here */
                    default:
                        for (int cnt = 0; cnt < len; cnt++) {
                            int other_byte = bitReaderBuffer.readBits(8);
                        }
                        break;

                }
            }
        }

        public void ld_sbr_header(int channelConfiguration, BitReaderBuffer bitReaderBuffer) {
            int numSbrHeader;
            switch (channelConfiguration) {
                case 1:
                case 2:
                    numSbrHeader = 1;
                    break;
                case 3:
                    numSbrHeader = 2;
                    break;
                case 4:
                case 5:
                case 6:
                    numSbrHeader = 3;
                    break;
                case 7:
                    numSbrHeader = 4;
                    break;
                default:
                    numSbrHeader = 0;
                    break;
            }
            for (int el = 0; el < numSbrHeader; el++) {
                new sbr_header(bitReaderBuffer);
            }
        }


    }

    public class sbr_header {
        public boolean bs_amp_res;
        public int bs_start_freq;
        public int bs_stop_freq;
        public int bs_xover_band;
        public int bs_reserved;
        public boolean bs_header_extra_1;
        public boolean bs_header_extra_2;
        public int bs_freq_scale;
        public boolean bs_alter_scale;
        public int bs_noise_bands;
        public int bs_limiter_bands;
        public int bs_limiter_gains;
        public boolean bs_interpol_freq;
        public boolean bs_smoothing_mode;

        public sbr_header(BitReaderBuffer b) {
            bs_amp_res = b.readBool();
            bs_start_freq = b.readBits(4);
            bs_stop_freq = b.readBits(4);
            bs_xover_band = b.readBits(3);
            bs_reserved = b.readBits(2);
            bs_header_extra_1 = b.readBool();
            bs_header_extra_2 = b.readBool();
            if (bs_header_extra_1) {
                bs_freq_scale = b.readBits(2);
                bs_alter_scale = b.readBool();
                bs_noise_bands = b.readBits(2);
            }
            if (bs_header_extra_2) {
                bs_limiter_bands = b.readBits(2);
                bs_limiter_gains = b.readBits(2);
                bs_interpol_freq = b.readBool();
            }
            bs_smoothing_mode = b.readBool();
        }
    }
}
