/*
 *  bit-io.c:       Buffered and bit oriented file I/O
 *
 *  Written by:     Ullrich Hafner
 *
 *  This file is part of FIASCO (Fractal Image And Sequence COdec)
 *  Copyright (C) 1994-2000 Ullrich Hafner
 */

/*
 *  $Date: 2000/06/14 20:49:37 $
 *  $Author: hafner $
 *  $Revision: 5.1 $
 *  $State: Exp $
 */

#define _DEFAULT_SOURCE 1 /* New name for SVID & BSD source defines */
#define _BSD_SOURCE 1   /* Make sure strdup() is in string.h */
#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */

#include "config.h"

#include <string.h>
#include <stdlib.h>

#include "nstring.h"

#include "macros.h"
#include "types.h"
#include "error.h"

#include "misc.h"
#include "bit-io.h"

/*****************************************************************************

                 local constants

*****************************************************************************/

static const unsigned BUFFER_SIZE = 16350;

static const unsigned mask[] = {0x0001, 0x0002, 0x0004, 0x0008,
                                0x0010, 0x0020, 0x0040, 0x0080,
                                0x0100, 0x0200, 0x0400, 0x0800,
                                0x1000, 0x2000, 0x4000, 0x8000};

/*****************************************************************************

                public code

*****************************************************************************/

FILE *
open_file (const char *filename, const char *env_var, openmode_e mode)
/*
 *  Try to open file 'filename' with mode 'mode' (READ_ACCESS, WRITE_ACCESS).
 *  Scan the current directory first and then cycle through the
 *  path given in the environment variable 'env_var', if set.
 *
 *  Return value:
 *  Pointer to open file on success, else NULL.
 */
{
    char       *path;        /* current path */
    FILE       *fp;      /* file pointer of I/O stream */
    char       *ext_filename = NULL; /* full path of file */
    char       *env_path     = NULL; /* path given by 'env_var' */
    char const * const PATH_SEP     = " ;:,"; /* path separation characters */
    char const * const DEFAULT_PATH = "."; /* default for output files */
    const char * const read_mode  = "rb";
    const char * const write_mode = "wb";


    assert (mode == READ_ACCESS || mode == WRITE_ACCESS);

    /*
     *  First check for stdin or stdout
     */
    if (filename == NULL || streq (filename, "-"))
    {
        if (mode == READ_ACCESS)
            return stdin;
        else
            return stdout;
    }

    /*
     *  Try to open 'readonly' file in the current directory
     */
    if (mode == READ_ACCESS && (fp = fopen (filename, read_mode)))
        return fp;

    if (mode == WRITE_ACCESS && strchr (filename, '/')) /* contains path */
        return fopen (filename, write_mode);

    /*
     *  Get value of environment variable 'env_var', if set
     *  else use DEFAULT_PATH ("./")
     */
    if (env_var != NULL)
        env_path = getenv (env_var);
    if (env_path == NULL)
        env_path = strdup (DEFAULT_PATH);
    else
        env_path = strdup (env_path);

    /*
     *  Try to open file in the directory given by the environment
     *  variable env_var - individual path components are separated by PATH_SEP
     */
    path = strtok (env_path, PATH_SEP);
    do
    {
        if (ext_filename)
            Free (ext_filename);
        ext_filename =  Calloc (strlen (path) + strlen (filename) + 2,
                                sizeof (char));
        strcpy (ext_filename, path);
        if (*(ext_filename + strlen (ext_filename) - 1) != '/')
            strcat (ext_filename, "/");
        strcat (ext_filename, filename);
        fp = fopen (ext_filename, mode == READ_ACCESS ? read_mode : write_mode);
    }
    while (fp == NULL && (path = strtok (NULL, PATH_SEP)) != NULL);

    Free (env_path);

    return fp;
}

bitfile_t *
open_bitfile (const char *filename, const char *env_var, openmode_e mode)
/*
 *  Bitfile constructor:
 *  Try to open file 'filename' for buffered bit oriented access with mode
 *  'mode'. Scan the current directory first and then cycle through the path
 *  given in the environment variable 'env_var', if set.
 *
 *  Return value:
 *  Pointer to open bitfile on success,
 *      otherwise the program is terminated.
 */
{
    bitfile_t *bitfile = Calloc (1, sizeof (bitfile_t));

    bitfile->file = open_file (filename, env_var, mode);

    if (bitfile->file == NULL)
        file_error (filename);

    if (mode == READ_ACCESS)
    {
        bitfile->bytepos  = 0;
        bitfile->bitpos   = 0;
        bitfile->mode     = mode;
        bitfile->filename = filename ? strdup (filename) : strdup ("(stdin)");
    }
    else if (mode == WRITE_ACCESS)
    {
        bitfile->bytepos  = BUFFER_SIZE - 1;
        bitfile->bitpos   = 8;
        bitfile->mode     = mode;
        bitfile->filename = filename ? strdup (filename) : strdup ("(stdout)");
    }
    else
        error ("Unknown file access mode '%d'.", mode);

    bitfile->bits_processed = 0;
    bitfile->buffer         = Calloc (BUFFER_SIZE, sizeof (byte_t));
    bitfile->ptr            = bitfile->buffer;

    return bitfile;
}

bool_t
get_bit (bitfile_t *bitfile)
/*
 *  Get one bit from the given stream 'bitfile'.
 *
 *  Return value:
 *   1  H bit
 *   0  L bit
 *
 *  Side effects:
 *  Buffer of 'bitfile' is modified accordingly.
 */
{
    assert (bitfile);

    if (!bitfile->bitpos--)      /* use next byte ? */
    {
        bitfile->ptr++;
        if (!bitfile->bytepos--)      /* no more bytes left in the buffer? */
        {
            /*
             *  Fill buffer with new data
             */
            int bytes = fread (bitfile->buffer, sizeof (byte_t),
                               BUFFER_SIZE, bitfile->file) - 1;
            if (bytes < 0)         /* Error or EOF */
                error ("Can't read next bit from bitfile %s.", bitfile->filename);
            else
                bitfile->bytepos = bytes;

            bitfile->ptr = bitfile->buffer;
        }
        bitfile->bitpos = 7;
    }

    bitfile->bits_processed++;

    return *bitfile->ptr & mask [bitfile->bitpos] ? 1 : 0;
}

unsigned int
get_bits (bitfile_t *bitfile, unsigned bits)
/*
 *  Get #'bits' bits from the given stream 'bitfile'.
 *
 *  Return value:
 *  composed integer value
 *
 *  Side effects:
 *  Buffer of 'bitfile' is modified.
 */
{
    unsigned value = 0;          /* input value */

    while (bits--)
        value = (unsigned) (value << 1) | get_bit (bitfile);

    return value;
}

void
put_bit (bitfile_t *bitfile, unsigned value)
/*
 *  Put the bit 'value' to the bitfile buffer.
 *  The buffer is written to the file 'bitfile->file' if the number of
 *  buffer bytes exceeds 'BUFFER_SIZE'.
 *
 *  No return value.
 *
 *  Side effects:
 *  Buffer of 'bitfile' is modified.
 */
{
    assert (bitfile);

    if (!bitfile->bitpos--)      /* use next byte ? */
    {
        bitfile->ptr++;
        if (!bitfile->bytepos--)      /* no more bytes left ? */
        {
            /*
             *  Write buffer to disk and fill buffer with zeros
             */
            if (fwrite (bitfile->buffer, sizeof (byte_t),
                        BUFFER_SIZE, bitfile->file) != BUFFER_SIZE)
                error ("Can't write next bit of bitfile %s!", bitfile->filename);
            memset (bitfile->buffer, 0, BUFFER_SIZE);
            bitfile->bytepos = BUFFER_SIZE - 1;
            bitfile->ptr     = bitfile->buffer;
        }
        bitfile->bitpos = 7;
    }

    if (value)
        *bitfile->ptr |= mask [bitfile->bitpos];

    bitfile->bits_processed++;
}

void
put_bits (bitfile_t *bitfile, unsigned value, unsigned bits)
/*
 *  Put #'bits' bits of integer 'value' to the bitfile buffer 'bitfile'.
 *
 *  No return value.
 *
 *  Side effects:
 *  Buffer of 'bitfile' is modified.
 */
{
    while (bits--)
        put_bit (bitfile, value & mask [bits]);
}

void
close_bitfile (bitfile_t *bitfile)
/*
 *  Bitfile destructor:
 *  Close 'bitfile', if 'bitfile->mode' == WRITE_ACCESS write bit buffer
 *  to disk.
 *
 *  No return value.
 *
 *  Side effects:
 *  Structure 'bitfile' is discarded.
 */
{
    assert (bitfile);

    if (bitfile->mode == WRITE_ACCESS)
    {
        unsigned bytes = fwrite (bitfile->buffer, sizeof (byte_t),
                                 BUFFER_SIZE - bitfile->bytepos, bitfile->file);
        if (bytes != BUFFER_SIZE - bitfile->bytepos)
            error ("Can't write remaining %d bytes of bitfile "
                   "(only %d bytes written)!",
                   BUFFER_SIZE - bitfile->bytepos, bytes);
    }
    fclose (bitfile->file);
    Free (bitfile->buffer);
    Free (bitfile->filename);
    Free (bitfile);
}

unsigned
bits_processed (const bitfile_t *bitfile)
/*
 *  Return value:
 *  Number of bits processed up to now
 */
{
    return bitfile->bits_processed;
}
