/*
 * Copyright (C) 2007-2023 Xagasoft, All rights reserved.
 *
 * This file is part of the libbu++ library and is released under the
 * terms of the license contained in the file LICENSE.
 */

#include "bu/bitstring.h"
#include "bu/random.h"
#include <stdio.h>
#include <string.h>

#include "bu/exceptionbase.h"

#define bitsToBytes( iBits ) (((iBits/8)+((iBits%8)?(1):(0))));

Bu::BitString::BitString()
{
    caData = NULL;
    cTopByteMask = 0;
    iBits = iBytes = 0;
}

Bu::BitString::BitString( const Bu::BitString &xSrc )
{
    iBits = xSrc.iBits;
    iBytes = xSrc.iBytes;
    cTopByteMask = xSrc.cTopByteMask;
    caData = new unsigned char[iBytes];
    memcpy( caData, xSrc.caData, iBytes );

    fixup();
}

Bu::BitString::BitString( long iNewBits, bool bFillRandomly )
{
    long j;
    iBits = iNewBits;
    iBytes = bitsToBytes( iNewBits );//(iNewBits/8)+((iNewBits%8)?(1):(0));
    caData = new unsigned char[iBytes];

    setMask();

    if( bFillRandomly )
    {
        // rand() only returns a value up to RAND_MAX (0x7FFF on my system) so
        // I'll just use the low order byte)
        for( j = 0; j < iBytes; j++ )
        {
            caData[j] = (unsigned char)(Bu::Random::rand() & 0xFF);
        }
    }
    else
    {
        clear();
    }

    fixup();
}

Bu::BitString::~BitString()
{
    if( caData != NULL ) delete[] caData;
}

Bu::BitString &Bu::BitString::operator=( const Bu::BitString &xSrc )
{
    if( caData != NULL )
    {
        delete[] caData;
    }
    iBits = xSrc.iBits;
    iBytes = xSrc.iBytes;
    cTopByteMask = xSrc.cTopByteMask;
    caData = new unsigned char[iBytes];
    memcpy( caData, xSrc.caData, iBytes );

    fixup();

    return *this;
}

Bu::BitString Bu::BitString::operator~()
{
    Bu::BitString xRet( *this );

    for( int j = 0; j < xRet.iBytes; j++ )
    {
        xRet.caData[j] = ~xRet.caData[j];
    }

    xRet.fixup();

    return xRet;
}

Bu::BitString Bu::BitString::operator<<( const long iAmt )
{
    if( iAmt == 0 )
    {
        return (*this);
    }
    //int iByteShift = iAmt/8;

    Bu::BitString xSub( getSize() );

    long shft = (iAmt%8);
    long base = (iAmt/8);
    unsigned char lowmask=0;
    for( long j = 0; j < 8-shft; j++ )
    {
        lowmask |= (1<<j);
    }
    for( long j = 0; j < xSub.iBytes; j++ )
    {
        xSub.caData[base+j] = ((caData[j]>>shft)&(lowmask)) | ((caData[j+1]<<(8-shft))&(~lowmask));
    }
    xSub.fixup();

    return xSub;
}

Bu::BitString Bu::BitString::operator>>( const long iAmt )
{
    if( iAmt == 0 )
    {
        return (*this);
    }
    return (*this);
}

void Bu::BitString::shiftLeft( long iAmt )
{
    if( iAmt == 0 )
    {
        return;
    }
    else if( iAmt < 0 )
    {
        shiftRight( -iAmt );
        return;
    }

    long iByteShift = iAmt/8;
    long iBitShift = iAmt%8;

    long j;
    for( j = iBytes-1; j >= 0; j-- )
    {
        caData[j] = (((j-iByteShift)<0)?(0):((caData[j-iByteShift]<<iBitShift))) | (((j-iByteShift-1)<0)?(0):((caData[j-iByteShift-1]>>(8-iBitShift))));
    }

    fixup();
}

void Bu::BitString::shiftRight( long iAmt )
{
    if( iAmt == 0 )
    {
        return;
    }
    else if( iAmt < 0 )
    {
        shiftLeft( -iAmt );
        return;
    }

    long iByteShift = iAmt/8;
    long iBitShift = iAmt%8;

    long j;
    for( j = 0; j < iBytes; j++ )
    {
        caData[j] = (((j+iByteShift)>iBytes)?(0):((caData[j+iByteShift]>>iBitShift))) | (((j+iByteShift+1)>iBytes)?(0):((caData[j+iByteShift+1]<<(8-iBitShift))));
    }

    fixup();
}
/*
long Bu::BitString::bitsToBytes( long iBits )
{
    return (iBits/8)+((iBits%8)?(1):(0));
}
*/
void Bu::BitString::fixup()
{
    if( caData != NULL )
    {
        caData[iBytes-1] &= cTopByteMask;
    }
}

void Bu::BitString::setBit( long iBit, bool bBitState )
{
    if( iBit < 0 || iBit >= iBits )
        throw Bu::ExceptionBase("bit out of range: %d in (0-%d)", iBit, iBits );
    if( bBitState )
    {
        caData[iBit/8] |= (1<<(iBit%8));
    }
    else
    {
        caData[iBit/8] &= ~(1<<(iBit%8));
    }
}

void Bu::BitString::flipBit( long iBit )
{
    caData[iBit/8] ^= (1<<(iBit%8));
}

bool Bu::BitString::getBit( long iBit ) const
{
    if( iBit >= iBits || iBit < 0 ) return false;
    if( (caData[iBit/8] & (1<<(iBit%8))) == 0 )
    {
        return false;
    }
    return true;
}

long Bu::BitString::getBitLength()
{
    return iBits;
}

long Bu::BitString::getSize() const
{
    return iBits;
}

class Bu::BitString Bu::BitString::getSubString( long iLower, long iUpper )
{
    if( iUpper == 0 || iUpper < iLower ) iUpper = iBits;

    Bu::BitString xSub( iUpper-iLower+1 );

    long shft = (iLower%8);
    long base = (iLower/8);
    unsigned char lowmask=0;
    for( long j = 0; j < 8-shft; j++ )
    {
        lowmask |= (1<<j);
    }
    for( long j = 0; j < xSub.iBytes; j++ )
    {
        xSub.caData[j] = ((caData[base+j]>>shft)&(lowmask)) | ((caData[base+j+1]<<(8-shft))&(~lowmask));
    }
    xSub.fixup();

    return xSub;
}

long Bu::BitString::toLong( long iStart, long iSize )
{
    if( iSize < 1 ) iSize = 1;
    if( iSize > 32 ) iSize = 32;
    if( iStart < 0 ) return 0;
    if( iStart+iSize > getSize() ) return 0;

    Bu::BitString tmpo;
    tmpo = getSubString( iStart, iStart+iSize-1 );
    long x = *((long *)tmpo.caData);

    return x;
}
/*
std::string Bu::BitString::toString( bool bAddSpacers )
{
    long iSz = iBits;
    if( bAddSpacers )
    {
        iSz += (iBits/8);
        if( iBits%8 == 0 ) iSz--;
    }
    std::string xStr;

    int bw=0;
    int of=0;
    for( int j = iBits-1; j >= 0; j-- )
    {
        if( getBit( j ) )
        {
            xStr += '1';
        }
        else
        {
            xStr += '0';
        }

        if( bAddSpacers )
        {
            bw++;
            if( bw >= 8 && j < iBits-1 )
            {
                bw = 0;
                of++;
                xStr += ' ';
            }
        }
    }

    return xStr;
}
*/
void Bu::BitString::clear()
{
    if( caData != NULL )
    {
        memset( caData, 0, iBytes );
    }
}

void Bu::BitString::fill()
{
    if( caData != NULL )
    {
        memset( caData, 0xff, iBytes );
    }
}

bool Bu::BitString::setBitLength( long iLength, bool bClear )
{
    return setSize( iLength, bClear );
}

bool Bu::BitString::setSize( long iLength, bool bClear )
{
    // First, if there's nothing, then allocate an empty one.
    if( caData == NULL )
    {
        iBits = iLength;
        iBytes = bitsToBytes( iLength );
        caData = new unsigned char[iBytes];
        memset( caData, 0, iBytes );
        return true;
    }

    // If the new length is the same as the old, don't do anything, but do
    // check to see if we should still clear the data.
    if( iBits != iLength )
    {
        // Ok, we are changing the number if bits, but are we changing the
        // number of bytes?
        long iNewBytes = bitsToBytes( iLength );
        if( iBytes == iNewBytes )
        {
            // No? That makes life easier
            iBits = iLength;
            setMask();
            if( bClear )
            {
                clear();
            }
        }
        else
        {
            // Ok, reallocate and copy...
            iBits = iLength;
//          long iNewBytes = bitsToBytes( iLength );
            if( bClear )
            {
                delete[] caData;
                caData = new unsigned char[iNewBytes];
                memset( caData, 0, iNewBytes );
            }
            else
            {
                unsigned char *tmp = caData;
                caData = new unsigned char[iNewBytes];
                if( iNewBytes < iBytes )
                {
                    memcpy( caData, tmp, iNewBytes );
                }
                else
                {
                    memcpy( caData, tmp, iBytes );
                }
                delete[] tmp;
            }
            iBytes = iNewBytes;
            
            setMask();
        }

    }
    else if( bClear )
    {
        clear();
    }

    return true;
}

void Bu::BitString::setMask()
{
    // This can either mean that there are a multiple of eight bits or
    // zero, if there are zero you're an idiot (zero can't happen, because
    // we would allocate an extra byte and never use it)
    if( (iBits%8 == 0) )
    {
        cTopByteMask = 0xFF;
    }
    else
    {
        cTopByteMask = 0;
        for( long j = 0; j < (iBits%8); j++ )
        {
            cTopByteMask |= (1<<j);
        }
    }
}

void Bu::BitString::randomize()
{
    if( caData != NULL )
    {
        for( int j = 0; j < iBytes; j++ )
        {
            caData[j] = (unsigned char)(Bu::Random::rand() & 0xFF);
        }
        fixup();
    }
}

void Bu::BitString::invert()
{
    if( caData != NULL )
    {
        for( long j = 0; j < iBytes; j++ )
        {
            caData[j] = ~caData[j];
        }
        fixup();
    }
}

long Bu::BitString::getHighestOrderBitPos()
{
    for( long j = iBits-1; j >= 0; j-- )
    {
        if( getBit( j ) )
        {
            return j;
        }
    }

    return -1;
}

Bu::String Bu::BitString::toString()
{
    Bu::String sRet;
    for( int j = iBits-1; j >= 0; j-- )
        sRet.append( getBit( j )?'1':'0' );
    return sRet;
}

/*
bool Bu::BitString::writeToFile( FILE *fh )
{
    fwrite( &iBits, sizeof(long), 1, fh );
    fwrite( caData, sizeof(char), iBytes, fh );
    
    return true;
}

bool Bu::BitString::readFromFile( FILE *fh )
{
    fread( &iBits, sizeof(long), 1, fh );
    
    iBytes = bitsToBytes( iBits );
    if( caData ) delete[] caData;
    caData = new unsigned char[iBytes];

    fread( caData, sizeof(char), iBytes, fh );

    setMask();
    
    fixup();

    return true;
}*/
