/** @file  psBitSet.c
 *
 *  @brief Creates an array of bytes of arbitrary length for storing individual bits.
 *
 *  Bit masks are useful tools for toggling various flags and options. This set of functions module provides
 *  a mechanism to create an array of bits of arbitrary length and manipulate them with basic binary
 *  operations. A print function is also provided to display the entire set of bits in binary format as a
 *  string.
 *
 *  @author Ross Harman, MHPCC
 *  @author Robert DeSonia, MHPCC
 *
 *  @version $Revision: 1.41 $ $Name: not supported by cvs2svn $
 *  @date $Date: 2007-02-13 03:01:24 $
 *
 *  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <math.h>

#include "psBitSet.h"
#include "psMemory.h"
#include "psError.h"
#include "psAbort.h"
#include "psString.h"
#include "psAssert.h"



enum {
    UNKNOWN_OP,
    AND_OP,
    OR_OP,
    XOR_OP,
    NOT_OP
};

static void bitSetFree(psBitSet* inBitSet);

/** Private function to create a mask.
 *
 *  Creates an eight bit mask with the given bit set. All other bits in the byte are zero. The input bit uses
 *  zero-based indexing, and is the cumulitive index within the array, not the localized byte's bit position.
 *
 *  @return  char*: Pointer to byte in which bit is contained.
 */
static char mask(psS32 bit)
{
    char mask = (char)0x01;

    // Ignore splint warning about negative bit shifts
    /* @i@ */
    mask = mask << (bit % 8);

    return mask;
}

static void bitSetFree(psBitSet* inBitSet)
{
    psFree(inBitSet->bits);
}

bool psMemCheckBitSet(psPtr ptr)
{
    PS_ASSERT_PTR(ptr, false);
    return ( psMemGetDeallocator(ptr) == (psFreeFunc)bitSetFree );
}


psBitSet* p_psBitSetAlloc(const char *file,
                          unsigned int lineno,
                          const char *func,
                          long nalloc)
{
    if (nalloc < 0) {
        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
                _("The number of bit in a psBitSet (%ld) must be greater than zero."),
                nalloc);
        return NULL;
    }

    psS32 numBytes = 0;
    psBitSet* newObj = NULL;

    numBytes = ceil(nalloc / 8.0);
    newObj = p_psAlloc(file, lineno, func, sizeof(psBitSet));
    psMemSetDeallocator(newObj, (psFreeFunc) bitSetFree);
    newObj->n = numBytes;

    // Ignore splint warning about releasing pointer members, since they've not been allocated yet
    /* @i@ */
    newObj->bits = psAlloc(sizeof(char) * numBytes);

    memset(newObj->bits, 0, numBytes);

    return newObj;
}


bool psBitSetSet(psBitSet* bitSet,
                      long bit)
{
    unsigned char *byte = NULL;

    if (bitSet == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                _("Can not operate on a NULL psBitSet."));
        return false;
    } else if ( (bit < 0) ||
                (bit > bitSet->n * 8 - 1) ) {
        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
                _("The specified bit position (%ld) is invalid.  Position must be between 0 and %ld."),
                bit, bitSet->n * 8 - 1);
        return false;
    }
    // Variable byte is the byte in the array that contains the bit to be set
    byte = bitSet->bits + bit / 8;
    *byte |= mask(bit);

    return true;
}

bool psBitSetClear(psBitSet* bitSet,
                        long bit)
{
    unsigned char *byte = NULL;

    if (bitSet == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                _("Can not operate on a NULL psBitSet."));
        return false;
    } else if ( (bit < 0) ||
                (bit > bitSet->n * 8 - 1) ) {
        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
                _("The specified bit position (%ld) is invalid.  Position must be between 0 and %ld."),
                bit, bitSet->n * 8 - 1);
        return false;
    }
    // Variable byte is the byte in the array that contains the bit to be set
    byte = bitSet->bits + bit / 8;
    *byte &= ! mask(bit);

    return true;
}

bool psBitSetTest(const psBitSet* bitSet,
                  long bit)
{
    unsigned char *byte = NULL;

    if (bitSet == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                _("Can not operate on a NULL psBitSet."));
        return false;
    } else if ( (bit < 0) ||
                (bit > bitSet->n * 8 - 1) ) {
        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
                _("The specified bit position (%ld) is invalid.  Position must be between 0 and %ld."),
                bit,bitSet->n * 8 - 1);
        return false;
    }

    // Variable byte is the byte in the array that contains the bit to be tested
    byte = bitSet->bits + bit / 8;
    return ((*byte & mask(bit)) != 0);
}

psBitSet* psBitSetOp(psBitSet* outBitSet,
                     const psBitSet* inBitSet1,
                     const char *operator,
                     const psBitSet* inBitSet2)
{
    if (inBitSet1 == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                _("First psBitSet operand can not be NULL."));
        psFree(outBitSet);
        return NULL;
    }
    if (operator == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                _("Specified operator is NULL.  Must specify desired operator."));
        psFree(outBitSet);
        return NULL;
    }
    psS32 i = 0;
    psS32 n = 0;
    unsigned char* outBits = NULL;
    unsigned char* inBits1 = NULL;
    unsigned char* inBits2 = NULL;
    psS32 op = UNKNOWN_OP;

    inBits1 = inBitSet1->bits;


    // parse the operator
    if (strcmp(operator,"AND")==0) {
        op = AND_OP;
    } else if (strcmp(operator,"OR")==0) {
        op = OR_OP;
    } else if (strcmp(operator,"XOR")==0) {
        op = XOR_OP;
    } else if (strcmp(operator,"NOT")==0) {
        op = NOT_OP;
    } else {
        psFree(outBitSet);
        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
                _("Specified operator, %s, is invalid.  Valid operators are AND, OR, and XOR."),
                operator);
        return NULL;
    }

    if (op != NOT_OP) {
        if (inBitSet2 == NULL) {
            psError(PS_ERR_BAD_PARAMETER_NULL, true,
                    _("Second psBitSet operand can not be NULL."));
            psFree(outBitSet);
            return NULL;
        }

        if (inBitSet1->n != inBitSet2->n) {
            psError(PS_ERR_BAD_PARAMETER_SIZE, true,
                    _("The psBitSet operand must be the same size."));
            psFree(outBitSet);
            return NULL;
        }
        inBits2 = inBitSet2->bits;
    }

    if (outBitSet == NULL) {
        outBitSet = psBitSetAlloc(inBitSet1->n*8);
    } else if (outBitSet->n != inBitSet1->n) {
        outBitSet->n = inBitSet1->n;
        outBitSet->bits = psRealloc(outBitSet->bits, inBitSet1->n);
    }

    n = outBitSet->n;
    outBits = outBitSet->bits;

    switch (op) {
    case AND_OP:
        for (i = 0; i < n; i++) {
            outBits[i] = inBits1[i] & inBits2[i];
        }
        break;
    case OR_OP:
        for (i = 0; i < n; i++) {
            outBits[i] = inBits1[i] | inBits2[i];
        }
        break;
    case XOR_OP:
        for (i = 0; i < n; i++) {
            outBits[i] = inBits1[i] ^ inBits2[i];
        }
        break;
    case NOT_OP:
    default:
        for (i = 0; i < n; i++) {
            outBits[i] = ~inBits1[i];
        }
        break;
    }

    return outBitSet;
}

psBitSet* psBitSetNot(psBitSet* outBitSet,
                      const psBitSet* inBitSet)
{
    if (inBitSet == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                _("Operand can not be NULL."));
        psFree(outBitSet);
        return NULL;
    }

    outBitSet = psBitSetOp(outBitSet,inBitSet,"NOT",NULL);

    return outBitSet;
}

psString psBitSetToString(const psBitSet* bitSet)
{
    PS_ASSERT_PTR_NON_NULL(bitSet, NULL);
    psS32 i = 0;
    psS32 numBits = bitSet->n * 8;
    //    char *outString = psAlloc((size_t) numBits + 1);
    psString outString = psStringAlloc(numBits + 1);

    for (i = 0; i < numBits; i++) {
        outString[numBits - i - 1] = psBitSetTest(bitSet, i) ? '1' : '0';
    }

    outString[numBits] = 0;

    return outString;
}
