/*
 *  Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "bitstream_builder.h"

#include <string.h>

namespace webrtc {
BitstreamBuilder::BitstreamBuilder(WebRtc_UWord8* data, const WebRtc_UWord32 dataSize) :
    _data(data),
    _dataSize(dataSize),
    _byteOffset(0),
    _bitOffset(0)
{
    memset(data, 0, dataSize);
}

WebRtc_UWord32
BitstreamBuilder::Length() const
{
    return _byteOffset+ (_bitOffset?1:0);
}

WebRtc_Word32
BitstreamBuilder::Add1Bit(const WebRtc_UWord8 bit)
{
    // sanity
    if(_bitOffset + 1 > 8)
    {
        if(_dataSize < Length()+1)
        {
            // not enough space in buffer
            return -1;
        }
    }
    Add1BitWithoutSanity(bit);
    return 0;
}

void
BitstreamBuilder::Add1BitWithoutSanity(const WebRtc_UWord8 bit)
{
    if(bit & 0x1)
    {
        _data[_byteOffset] += (1 << (7-_bitOffset));
    }

    if(_bitOffset == 7)
    {
        // last bit in byte
        _bitOffset = 0;
        _byteOffset++;
    } else
    {
        _bitOffset++;
    }
}

WebRtc_Word32
BitstreamBuilder::Add2Bits(const WebRtc_UWord8 bits)
{
    // sanity
    if(_bitOffset + 2 > 8)
    {
        if(_dataSize < Length()+1)
        {
            // not enough space in buffer
            return -1;
        }
    }
    Add1BitWithoutSanity(bits >> 1);
    Add1BitWithoutSanity(bits);
    return 0;
}

WebRtc_Word32
BitstreamBuilder::Add3Bits(const WebRtc_UWord8 bits)
{
    // sanity
    if(_bitOffset + 3 > 8)
    {
        if(_dataSize < Length()+1)
        {
            // not enough space in buffer
            return -1;
        }
    }
    Add1BitWithoutSanity(bits >> 2);
    Add1BitWithoutSanity(bits >> 1);
    Add1BitWithoutSanity(bits);
    return 0;
}

WebRtc_Word32
BitstreamBuilder::Add4Bits(const WebRtc_UWord8 bits)
{
    // sanity
    if(_bitOffset + 4 > 8)
    {
        if(_dataSize < Length()+1)
        {
            // not enough space in buffer
            return -1;
        }
    }
    Add1BitWithoutSanity(bits >> 3);
    Add1BitWithoutSanity(bits >> 2);
    Add1BitWithoutSanity(bits >> 1);
    Add1BitWithoutSanity(bits);
    return 0;
}

WebRtc_Word32
BitstreamBuilder::Add5Bits(const WebRtc_UWord8 bits)
{
    // sanity
    if(_bitOffset + 5 > 8)
    {
        if(_dataSize < Length()+1)
        {
            // not enough space in buffer
            return -1;
        }
    }
    Add1BitWithoutSanity(bits >> 4);
    Add1BitWithoutSanity(bits >> 3);
    Add1BitWithoutSanity(bits >> 2);
    Add1BitWithoutSanity(bits >> 1);
    Add1BitWithoutSanity(bits);
    return 0;
}

WebRtc_Word32
BitstreamBuilder::Add6Bits(const WebRtc_UWord8 bits)
{
    // sanity
    if(_bitOffset + 6 > 8)
    {
        if(_dataSize < Length()+1)
        {
            // not enough space in buffer
            return -1;
        }
    }
    Add1BitWithoutSanity(bits >> 5);
    Add1BitWithoutSanity(bits >> 4);
    Add1BitWithoutSanity(bits >> 3);
    Add1BitWithoutSanity(bits >> 2);
    Add1BitWithoutSanity(bits >> 1);
    Add1BitWithoutSanity(bits);
    return 0;
}

WebRtc_Word32
BitstreamBuilder::Add7Bits(const WebRtc_UWord8 bits)
{
    // sanity
    if(_bitOffset + 7 > 8)
    {
        if(_dataSize < Length()+1)
        {
            // not enough space in buffer
            return -1;
        }
    }
    Add1BitWithoutSanity(bits >> 6);
    Add1BitWithoutSanity(bits >> 5);
    Add1BitWithoutSanity(bits >> 4);
    Add1BitWithoutSanity(bits >> 3);
    Add1BitWithoutSanity(bits >> 2);
    Add1BitWithoutSanity(bits >> 1);
    Add1BitWithoutSanity(bits);
    return 0;
}

WebRtc_Word32
BitstreamBuilder::Add8Bits(const WebRtc_UWord8 bits)
{
    // sanity
    if(_dataSize < Length()+1)
    {
        // not enough space in buffer
        return -1;
    }
    if(_bitOffset == 0)
    {
        _data[_byteOffset] = bits;
    } else
    {
        _data[_byteOffset] += (bits >> _bitOffset);
        _data[_byteOffset+1] += (bits << (8-_bitOffset));
    }
    _byteOffset++;
    return 0;
}

WebRtc_Word32
BitstreamBuilder::Add16Bits(const WebRtc_UWord16 bits)
{
    // sanity
    if(_dataSize < Length()+2)
    {
        // not enough space in buffer
        return -1;
    }
    if(_bitOffset == 0)
    {
        _data[_byteOffset] = (WebRtc_UWord8)(bits >> 8);
        _data[_byteOffset+1] = (WebRtc_UWord8)(bits);
    } else
    {
        _data[_byteOffset] += (WebRtc_UWord8)(bits >> (_bitOffset + 8));
        _data[_byteOffset+1] += (WebRtc_UWord8)(bits >> _bitOffset);
        _data[_byteOffset+2] += (WebRtc_UWord8)(bits << (8-_bitOffset));
    }
    _byteOffset += 2;
    return 0;
}

WebRtc_Word32
BitstreamBuilder::Add24Bits(const WebRtc_UWord32 bits)
{
    // sanity
    if(_dataSize < Length()+3)
    {
        // not enough space in buffer
        return -1;
    }
    if(_bitOffset == 0)
    {
        _data[_byteOffset] = (WebRtc_UWord8)(bits >> 16);
        _data[_byteOffset+1] = (WebRtc_UWord8)(bits >> 8);
        _data[_byteOffset+2] = (WebRtc_UWord8)(bits);
    } else
    {
        _data[_byteOffset]   += (WebRtc_UWord8)(bits >> (_bitOffset+16));
        _data[_byteOffset+1] += (WebRtc_UWord8)(bits >> (_bitOffset+8));
        _data[_byteOffset+2] += (WebRtc_UWord8)(bits >> (_bitOffset));
        _data[_byteOffset+3] += (WebRtc_UWord8)(bits << (8-_bitOffset));
    }
    _byteOffset += 3;
    return 0;
}

WebRtc_Word32
BitstreamBuilder::Add32Bits(const WebRtc_UWord32 bits)
{
    // sanity
    if(_dataSize < Length()+4)
    {
        // not enough space in buffer
        return -1;
    }
    if(_bitOffset == 0)
    {
        _data[_byteOffset]   = (WebRtc_UWord8)(bits >> 24);
        _data[_byteOffset+1] = (WebRtc_UWord8)(bits >> 16);
        _data[_byteOffset+2] = (WebRtc_UWord8)(bits >> 8);
        _data[_byteOffset+3] = (WebRtc_UWord8)(bits);
    } else
    {
        _data[_byteOffset]   += (WebRtc_UWord8)(bits >> (_bitOffset+24));
        _data[_byteOffset+1] += (WebRtc_UWord8)(bits >> (_bitOffset+16));
        _data[_byteOffset+2] += (WebRtc_UWord8)(bits >> (_bitOffset+8));
        _data[_byteOffset+3] += (WebRtc_UWord8)(bits >> (_bitOffset));
        _data[_byteOffset+4] += (WebRtc_UWord8)(bits << (8-_bitOffset));
    }
    _byteOffset += 4;
    return 0;
}

// Exp-Golomb codes
/*
    with "prefix" and "suffix" bits and assignment to codeNum ranges (informative)
    Bit string form Range of codeNum
              1                0
            0 1 x0             1..2      2bits-1
          0 0 1 x1 x0          3..6      3bits-1
        0 0 0 1 x2 x1 x0       7..14     4bits-1
      0 0 0 0 1 x3 x2 x1 x0    15..30
    0 0 0 0 0 1 x4 x3 x2 x1 x0 31..62
*/
WebRtc_Word32
BitstreamBuilder::AddUE(const WebRtc_UWord32 value)
{
    // un-rolled on 8 bit base to avoid too deep if else chain
    if(value < 0x0000ffff)
    {
        if(value < 0x000000ff)
        {
            if(value == 0)
            {
                if(AddPrefix(0) != 0)
                {
                    return -1;
                }
            } else if(value < 3)
            {
                if(AddPrefix(1) != 0)
                {
                    return -1;
                }
                AddSuffix(1, value-1);
            } else if(value < 7)
            {
                if(AddPrefix(2) != 0)
                {
                    return -1;
                }
                AddSuffix(2, value-3);
            } else if(value < 15)
            {
                if(AddPrefix(3) != 0)
                {
                    return -1;
                }
                AddSuffix(3, value-7);
            } else if(value < 31)
            {
                if(AddPrefix(4) != 0)
                {
                    return -1;
                }
                AddSuffix(4, value-15);
            } else if(value < 63)
            {
                if(AddPrefix(5) != 0)
                {
                    return -1;
                }
                AddSuffix(5, value-31);
            } else if(value < 127)
            {
                if(AddPrefix(6) != 0)
                {
                    return -1;
                }
                AddSuffix(6, value-63);
            } else
            {
                if(AddPrefix(7) != 0)
                {
                    return -1;
                }
                AddSuffix(7, value-127);
            }
        }else
        {
            if(value < 0x000001ff)
            {
                if(AddPrefix(8) != 0)
                {
                    return -1;
                }
                AddSuffix(8, value-0x000000ff);
            } else if(value < 0x000003ff)
            {
                if(AddPrefix(9) != 0)
                {
                    return -1;
                }
                AddSuffix(9, value-0x000001ff);
            } else if(value < 0x000007ff)
            {
                if(AddPrefix(10) != 0)
                {
                    return -1;
                }
                AddSuffix(10, value-0x000003ff);
            } else if(value < 0x00000fff)
            {
                if(AddPrefix(11) != 0)
                {
                    return -1;
                }
                AddSuffix(1, value-0x000007ff);
            } else if(value < 0x00001fff)
            {
                if(AddPrefix(12) != 0)
                {
                    return -1;
                }
                AddSuffix(12, value-0x00000fff);
            } else if(value < 0x00003fff)
            {
                if(AddPrefix(13) != 0)
                {
                    return -1;
                }
                AddSuffix(13, value-0x00001fff);
            } else if(value < 0x00007fff)
            {
                if(AddPrefix(14) != 0)
                {
                    return -1;
                }
                AddSuffix(14, value-0x00003fff);
            } else
            {
                if(AddPrefix(15) != 0)
                {
                    return -1;
                }
                AddSuffix(15, value-0x00007fff);
            }
        }
    }else
    {
        if(value < 0x00ffffff)
        {
            if(value < 0x0001ffff)
            {
                if(AddPrefix(16) != 0)
                {
                    return -1;
                }
                AddSuffix(16, value-0x0000ffff);
            } else if(value < 0x0003ffff)
            {
                if(AddPrefix(17) != 0)
                {
                    return -1;
                }
                AddSuffix(17, value-0x0001ffff);
            } else if(value < 0x0007ffff)
            {
                if(AddPrefix(18) != 0)
                {
                    return -1;
                }
                AddSuffix(18, value-0x0003ffff);
            } else if(value < 0x000fffff)
            {
                if(AddPrefix(19) != 0)
                {
                    return -1;
                }
                AddSuffix(19, value-0x0007ffff);
            } else if(value < 0x001fffff)
            {
                if(AddPrefix(20) != 0)
                {
                    return -1;
                }
                AddSuffix(20, value-0x000fffff);
            } else if(value < 0x003fffff)
            {
                if(AddPrefix(21) != 0)
                {
                    return -1;
                }
                AddSuffix(21, value-0x001fffff);
            } else if(value < 0x007fffff)
            {
                if(AddPrefix(22) != 0)
                {
                    return -1;
                }
                AddSuffix(22, value-0x003fffff);
            } else
            {
                if(AddPrefix(23) != 0)
                {
                    return -1;
                }
                AddSuffix(23, value-0x007fffff);
            }
        } else
        {
            if(value < 0x01ffffff)
            {
                if(AddPrefix(24) != 0)
                {
                    return -1;
                }
                AddSuffix(24, value-0x00ffffff);
            } else if(value < 0x03ffffff)
            {
                if(AddPrefix(25) != 0)
                {
                    return -1;
                }
                AddSuffix(25, value-0x01ffffff);
            } else if(value < 0x07ffffff)
            {
                if(AddPrefix(26) != 0)
                {
                    return -1;
                }
                AddSuffix(26, value-0x03ffffff);
            } else if(value < 0x0fffffff)
            {
                if(AddPrefix(27) != 0)
                {
                    return -1;
                }
                AddSuffix(27, value-0x07ffffff);
            } else if(value < 0x1fffffff)
            {
                if(AddPrefix(28) != 0)
                {
                    return -1;
                }
                AddSuffix(28, value-0x0fffffff);
            } else if(value < 0x3fffffff)
            {
                if(AddPrefix(29) != 0)
                {
                    return -1;
                }
                AddSuffix(29, value-0x1fffffff);
            } else if(value < 0x7fffffff)
            {
                if(AddPrefix(30) != 0)
                {
                    return -1;
                }
                AddSuffix(30, value-0x3fffffff);
            } else if(value < 0xffffffff)
            {
                if(AddPrefix(31) != 0)
                {
                    return -1;
                }
                AddSuffix(31, value-0x7ffffff);
            } else
            {
                if(AddPrefix(32) != 0)
                {
                    return -1;
                }
                AddSuffix(32, 0); // exactly 0xffffffff
            }
        }
    }
    return 0;
}

WebRtc_Word32
BitstreamBuilder::AddPrefix(const WebRtc_UWord8 numZeros)
{
    // sanity for the sufix too
    WebRtc_UWord32 numBitsToAdd = numZeros * 2 + 1;
    if(((_dataSize - _byteOffset) *8 + 8-_bitOffset) < numBitsToAdd)
    {
        return -1;
    }

    // add numZeros
    for (WebRtc_UWord32 i = 0; i < numZeros; i++)
    {
        Add1Bit(0);
    }
    Add1Bit(1);
    return 0;
}

void
BitstreamBuilder::AddSuffix(const WebRtc_UWord8 numBits, const WebRtc_UWord32 rest)
{
    // most significant bit first
    for(WebRtc_Word32 i = numBits - 1; i >= 0; i--)
    {
        if(( rest >> i) & 0x1)
        {
            Add1Bit(1);
        }else
        {
            Add1Bit(0);
        }
    }
}
} // namespace webrtc
