// bitset.h
// Řešení IJC-DU1, příklad a), 22.3.2024
// Autor: Roman Nečas, FIT
// Přeloženo: gcc 13.2.1

#ifndef BITSET_H
    #define BITSET_H

    #include <stdio.h>
    #include <stdlib.h>
    #include <assert.h>
    #include <limits.h>
    #include "error.h"
    #include <stdbool.h>

    #define WORD_BITS (sizeof(unsigned long) * CHAR_BIT)  // Number of bits in an unsigned long
    #define WORD_BYTES sizeof(unsigned long)              // Number of bytes in an unsigned long
    #define HEADER_WORD 1                                 // First element is reserved for size

    /* Index type for the bit set. */
    typedef unsigned long bitset_index_t;

    /* Bit set type (for passing as a parameter by reference). */
    typedef unsigned long *bitset_t;

    /* MACROS that are used always */

        /* defines and initializes the variable name */
        #define bitset_create(name, size) \
            static_assert(size > 0 && size < ULONG_MAX, "ERROR: bitset_create: Invalid array size."); \
            bitset_index_t name[(size)/WORD_BITS + HEADER_WORD + (((size) % WORD_BITS) ? 1 : 0)] = {size}
        
        #define bitset_alloc(name, size) \
            static_assert(size > 0 && size < ULONG_MAX, "ERROR: bitset_create: Invalid array size."); \
            bitset_t name = calloc((size)/WORD_BITS + HEADER_WORD + (((size) % WORD_BITS) ? 1 : 0), sizeof(bitset_index_t)); \
            if (name == NULL) error_exit("bitset_alloc: Memory allocation error"); \
            name[0] = size

        /* Frees the memory of a dynamically allocated array. */
        #define bitset_free(name) free(name)

    #ifdef USE_INLINE
        /* INLINE FUNCTIONS */
        /* Returns the declared size of the array stored at index 0 */
        inline bitset_index_t bitset_size(bitset_t name) 
        {
            return name[0];
        }

        /* Sets all bits in the array to 0 (false) or 1 (true) */
        inline void bitset_fill(bitset_t name, bool bool_expr)
         {
            unsigned long i;
            for (i = HEADER_WORD; i < ((bitset_size(name) / WORD_BITS) + HEADER_WORD + (((bitset_size(name)) % WORD_BITS) ? 1 : 0)); i++) 
            {
                name[i] = (bool_expr) ? ~0UL : 0UL;
            }
        }

        /* Sets the specified bit in the array to the value given by the expression */
        inline void bitset_setbit(bitset_t name, bitset_index_t index, bool bool_expr) 
        {
            if (index >= bitset_size(name))
                error_exit("bitset_setbit: Index %lu out of range 0..%lu", (unsigned long)index, (unsigned long)bitset_size(name));
            name[(index/WORD_BITS) + HEADER_WORD] = (bool_expr) ? (name[(index/WORD_BITS) + HEADER_WORD] | (1UL << (index & (WORD_BITS - 1))))
                                                                : (name[(index/WORD_BITS) + HEADER_WORD] & ~(1UL << (index & (WORD_BITS - 1))));
        }

        /* Gets the value of the specified bit, returns 0 or 1 */
        inline char bitset_getbit(bitset_t name, bitset_index_t index) 
        {
            if (index >= bitset_size(name))
                error_exit("bitset_getbit: Index %lu out of range 0..%lu", (unsigned long)index, (unsigned long)bitset_size(name));
            return name[(index/WORD_BITS + HEADER_WORD)] >> (index & (WORD_BITS - 1)) & 1UL;
        }
    #else
        /* MACROS */
        /* Sets all bits in the array to 0 (false) or 1 (true) */
        #define bitset_fill(name, bool_expr) do \
        { \
            unsigned long i; \
            for (i = HEADER_WORD; i < ((bitset_size(name) / WORD_BITS) + HEADER_WORD + (((bitset_size(name)) % WORD_BITS) ? 1 : 0)); i++) \
            { \
                name[i] = (bool_expr) ? ~0UL : 0UL; \
            } \
        } while (0)

        /* Sets the specified bit in the array to the value given by the expression */
        #define bitset_setbit(name, index, bool_expr) do \
        { \
            if ((index) >= bitset_size(name)) error_exit("bitset_setbit: Index %lu out of range 0..%lu", \
                (unsigned long)index, (unsigned long)bitset_size(name)); \
            name[((index)/WORD_BITS) + HEADER_WORD] = (bool_expr) ? (name[((index)/WORD_BITS) + HEADER_WORD] | (1UL << ((index) & (WORD_BITS - 1)))) \
                                                                  : (name[((index)/WORD_BITS) + HEADER_WORD] & ~(1UL << ((index) & (WORD_BITS - 1)))); \
        } while (0)

        /* Gets the value of the specified bit, returns 0 or 1 */
        #define bitset_getbit(name, index) \
            (((index) >= bitset_size(name)) ? (error_exit("bitset_getbit: Index %lu out of range 0..%lu", (unsigned long)index, (unsigned long)bitset_size(name)), 0) \
                                            : ((name[((index)/WORD_BITS + HEADER_WORD)] >> ((index) & (WORD_BITS - 1))) & 1UL))

        /* Returns the declared size of the array stored at index 0 */
        #define bitset_size(name) name[0]

    #endif
#endif
