/*
 * $Id$
 *
 * DEBUG: section 08    Swap File Bitmap
 * AUTHOR: Harvest Derived
 *
 * SQUID Web Proxy Cache          http://www.squid-cache.org/
 * ----------------------------------------------------------
 *
 *  Squid is the result of efforts by numerous individuals from
 *  the Internet community; see the CONTRIBUTORS file for full
 *  details.   Many organizations have provided support for Squid's
 *  development; see the SPONSORS file for full details.  Squid is
 *  Copyrighted (C) 2001 by the Regents of the University of
 *  California; see the COPYRIGHT file for full details.  Squid
 *  incorporates software developed and/or copyrighted by other
 *  sources; see the CREDITS file for full details.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
 *
 */

#include "squid.h"

/* Number of bits in a long */
#if SIZEOF_LONG == 8
#define LONG_BIT_SHIFT 6
#define BITS_IN_A_LONG 0x40
#define LONG_BIT_MASK  0x3F
#define ALL_ONES (unsigned long) 0xFFFFFFFFFFFFFFFF
#elif SIZEOF_LONG == 4
#define LONG_BIT_SHIFT 5
#define BITS_IN_A_LONG 0x20
#define LONG_BIT_MASK  0x1F
#define ALL_ONES (unsigned long) 0xFFFFFFFF
#else
#define LONG_BIT_SHIFT 5
#define BITS_IN_A_LONG 0x20
#define LONG_BIT_MASK  0x1F
#define ALL_ONES (unsigned long) 0xFFFFFFFF
#endif

#define FM_INITIAL_NUMBER (1<<14)

fileMap *
file_map_create(void)
{
    fileMap *fm = (fileMap *)xcalloc(1, sizeof(fileMap));
    fm->max_n_files = FM_INITIAL_NUMBER;
    fm->nwords = fm->max_n_files >> LONG_BIT_SHIFT;
    debugs(8, 3, "file_map_create: creating space for " << fm->max_n_files << " files");
    debugs(8, 5, "--> " << fm->nwords << " words of " << sizeof(*fm->file_map) << " bytes each");
    fm->file_map = (unsigned long *)xcalloc(fm->nwords, sizeof(*fm->file_map));
    /* XXX account fm->file_map */
    return fm;
}

static void
file_map_grow(fileMap * fm)
{
    int old_sz = fm->nwords * sizeof(*fm->file_map);
    void *old_map = fm->file_map;
    fm->max_n_files <<= 1;
    assert(fm->max_n_files <= (1 << 24));	/* swap_filen is 25 bits, signed */
    fm->nwords = fm->max_n_files >> LONG_BIT_SHIFT;
    debugs(8, 3, "file_map_grow: creating space for " << fm->max_n_files << " files");
    fm->file_map = (unsigned long *)xcalloc(fm->nwords, sizeof(*fm->file_map));
    debugs(8, 3, "copying " << old_sz << " old bytes");
    xmemcpy(fm->file_map, old_map, old_sz);
    xfree(old_map);
    /* XXX account fm->file_map */
}

int
file_map_bit_set(fileMap * fm, int file_number)
{
    unsigned long bitmask = (1L << (file_number & LONG_BIT_MASK));

    while (file_number >= fm->max_n_files)
        file_map_grow(fm);

    fm->file_map[file_number >> LONG_BIT_SHIFT] |= bitmask;

    fm->n_files_in_map++;

    return file_number;
}

/*
 * WARNING: file_map_bit_reset does not perform array bounds
 * checking!  It assumes that 'file_number' is valid, and that the
 * bit is already set.  The caller must verify both of those
 * conditions by calling file_map_bit_test() first.
 */
void
file_map_bit_reset(fileMap * fm, int file_number)
{
    unsigned long bitmask = (1L << (file_number & LONG_BIT_MASK));
    fm->file_map[file_number >> LONG_BIT_SHIFT] &= ~bitmask;
    fm->n_files_in_map--;
}

int
file_map_bit_test(fileMap * fm, int file_number)
{
    unsigned long bitmask = (1L << (file_number & LONG_BIT_MASK));

    if (file_number >= fm->max_n_files)
        return 0;

    /* be sure the return value is an int, not a u_long */
    return (fm->file_map[file_number >> LONG_BIT_SHIFT] & bitmask ? 1 : 0);
}

int
file_map_allocate(fileMap * fm, int suggestion)
{
    int word;
    int bit;
    int count;

    if (suggestion >= fm->max_n_files)
        suggestion = 0;

    if (!file_map_bit_test(fm, suggestion))
        return suggestion;

    word = suggestion >> LONG_BIT_SHIFT;

    for (count = 0; count < fm->nwords; count++) {
        if (fm->file_map[word] != ALL_ONES)
            break;

        word = (word + 1) % fm->nwords;
    }

    for (bit = 0; bit < BITS_IN_A_LONG; bit++) {
        suggestion = ((unsigned long) word << LONG_BIT_SHIFT) | bit;

        if (!file_map_bit_test(fm, suggestion)) {
            return suggestion;
        }
    }

    debugs(8, 3, "growing from file_map_allocate");
    file_map_grow(fm);
    return file_map_allocate(fm, fm->max_n_files >> 1);
}

void
filemapFreeMemory(fileMap * fm)
{
    safe_free(fm->file_map);
    safe_free(fm);
}

#ifdef TEST

#define TEST_SIZE 1<<16
main(argc, argv)
{
    int i;

    fm = file_map_create(TEST_SIZE);

    for (i = 0; i < TEST_SIZE; ++i) {
        file_map_bit_set(i);
        assert(file_map_bit_test(i));
        file_map_bit_reset(i);
    }
}

#endif
