/** @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.23 $ $Name: not supported by cvs2svn $
 *  @date $Date: 2005-02-22 19:25:52 $
 *
 *  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
 */

#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 "psCollectionsErrors.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)
{
    if (inBitSet == NULL) {
        return;
    }
    psFree(inBitSet->bits);
}

psBitSet* psBitSetAlloc(psS32 n)
{
    psS32 numBytes = 0;
    psBitSet* newObj = NULL;

    if (n < 0) {
        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
                PS_ERRORTEXT_psBitSet_ALLOC_NEG_SIZE,
                n);
        return NULL;
    }

    numBytes = ceil(n / 8.0);
    newObj = psAlloc(sizeof(psBitSet));
    p_psMemSetDeallocator(newObj, (psFreeFcn) 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;
}

psBitSet* psBitSetSet(psBitSet* inBitSet,
                      psS32 bit)
{
    char *byte = NULL;

    if (inBitSet == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                PS_ERRORTEXT_psBitSet_SET_NULL);
        return inBitSet;
    } else if ( (bit < 0) ||
                (bit > inBitSet->n * 8 - 1) ) {
        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
                PS_ERRORTEXT_psBitSet_BIT_OUTOFRANGE,
                bit,inBitSet->n * 8 - 1);
        return inBitSet;
    }
    // Variable byte is the byte in the array that contains the bit to be set
    byte = inBitSet->bits + bit / 8;
    *byte |= mask(bit);

    return inBitSet;
}

psBitSet* psBitSetClear(psBitSet* inBitSet,
                        psS32 bit)
{
    char *byte = NULL;

    if (inBitSet == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                PS_ERRORTEXT_psBitSet_SET_NULL);
        return inBitSet;
    } else if ( (bit < 0) ||
                (bit > inBitSet->n * 8 - 1) ) {
        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
                PS_ERRORTEXT_psBitSet_BIT_OUTOFRANGE,
                bit,inBitSet->n * 8 - 1);
        return inBitSet;
    }
    // Variable byte is the byte in the array that contains the bit to be set
    byte = inBitSet->bits + bit / 8;
    *byte &= ! mask(bit);

    return inBitSet;
}

psBool psBitSetTest(const psBitSet* inBitSet,
                    psS32 bit)
{
    char *byte = NULL;

    if (inBitSet == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                PS_ERRORTEXT_psBitSet_SET_NULL);
        return false;
    } else if ( (bit < 0) ||
                (bit > inBitSet->n * 8 - 1) ) {
        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
                PS_ERRORTEXT_psBitSet_BIT_OUTOFRANGE,
                bit,inBitSet->n * 8 - 1);
        return false;
    }

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

psBitSet* psBitSetOp(psBitSet* outBitSet,
                     const psBitSet* inBitSet1,
                     char *operator,
                     const psBitSet* inBitSet2)
{
    psS32 i = 0;
    psS32 n = 0;
    char* outBits = NULL;
    char* inBits1 = NULL;
    char* inBits2 = NULL;
    psS32 op = UNKNOWN_OP;

    if (inBitSet1 == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                PS_ERRORTEXT_psBitSet_FIRST_OPERAND_NULL);
        psFree(outBitSet);
        return NULL;
    }
    inBits1 = inBitSet1->bits;

    if (operator == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                PS_ERRORTEXT_psBitSet_OPERATOR_NULL);
        psFree(outBitSet);
        return NULL;
    }

    // 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,
                PS_ERRORTEXT_psBitSet_OPERATOR_INVALID,
                operator);
        return NULL;
    }

    if (op != NOT_OP) {
        if (inBitSet2 == NULL) {
            psError(PS_ERR_BAD_PARAMETER_NULL, true,
                    PS_ERRORTEXT_psBitSet_SECOND_OPERAND_NULL);
            psFree(outBitSet);
            return NULL;
        }

        if (inBitSet1->n != inBitSet2->n) {
            psError(PS_ERR_BAD_PARAMETER_SIZE, true,
                    PS_ERRORTEXT_psBitSet_OPERANDS_SIZE_DIFFER);
            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:
        for (i = 0; i < n; i++) {
            outBits[i] = ~inBits1[i];
        }
        break;
    default:
        psAbort("psBitSetOp",
                "Unexpected error - operator parsed successfully but not valid?");
    }

    return outBitSet;
}

psBitSet* psBitSetNot(psBitSet* outBitSet,
                      const psBitSet* inBitSet)
{
    if (inBitSet == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                PS_ERRORTEXT_psBitSet_OPERAND_NULL);
        psFree(outBitSet);
        return NULL;
    }

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

    if (outBitSet == NULL) {
        psError(PS_ERR_UNKNOWN, false,
                PS_ERRORTEXT_psBitSet_NOT_OP_FAILED);
    }

    return outBitSet;
}

char *psBitSetToString(const psBitSet* inBitSet)
{
    psS32 i = 0;
    psS32 numBits = inBitSet->n * 8;
    char *outString = psAlloc((size_t) numBits + 1);

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

    outString[numBits] = 0;

    return outString;
}
