/* -*- mode:c++; coding: koi8-r -*- */

/* $Id: BitSource.cpp,v 1.1 2003/04/14 19:37:57 cher Exp $ */
/* Copyright (C) 2003 Alexander Chernov <cher@unicorn.cmc.msu.ru> */

/*
 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
 License as published by the Free Software Foundation; either
 version 2 of the License, or (at your option) any later version.

 This library 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
 Lesser General Public License for more details.

 See the `COPYING' file for the full terms and conditions.
*/

#include "BitSource.hpp"

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

#include <malloc.h>

BitSource::BitSource(ByteSource *_src)
  throw (BadArgsError)
{
  if (!_src) throw BadArgsError();
  src = _src;
  bits_stored = 0;
  bits = 0;
}

BitSource::~BitSource()
  throw ()
{
  delete src;
}

void
BitSource::read(int n, void *ptr, int offset)
  throw (ReadError,BadArgsError)
{
  if (n <= 0) throw BadArgsError();
  if (!ptr) throw BadArgsError();
  if (offset < 0 || offset > 7) throw BadArgsError();

  unsigned char *cptr = (unsigned char*) ptr;
  if (bits_stored >= n) {
    if (n <= 8 - offset) {
      *cptr |= (bits & ~(0xffu << n)) << offset;
    } else {
      *cptr++ |= bits << offset;
      bits >>= 8 - offset;
      n -= 8 - offset;
      bits_stored -= 8-offset;
      *cptr |= bits & ~(0xffu << n);
    }
    bits_stored -= n;
    bits >>= n;
    return;
  }

  // append the remaining bits
  if (bits_stored > 0) {
    n -= bits_stored;
    if (bits_stored <= 8 - offset) {
      *cptr |= bits << offset;
      offset += bits_stored;
      if (offset == 8) {
        offset = 0;
        cptr++;
      }
    } else {
      *cptr++ |= bits << offset;
      bits_stored -= 8 - offset;
      bits >>= 8 - offset;
      offset = 0;
      *cptr |= bits;
      offset += bits_stored;
    }
    bits_stored = 0;
    bits = 0;
  }

  // read new bits
  int req_size = (n + 7) / 8;
  int op_size = (n + offset + 7) / 8;
  unsigned char *new_bits;
  unsigned char *op_bits;

  new_bits = (unsigned char*) _alloca(req_size);
  op_bits = (unsigned char *) _alloca(req_size + 1);
  memset(op_bits, 0, req_size + 1);
  src->read(req_size, new_bits);

  // move the remainings
  bits_stored = 8 * req_size - n;
  if (bits_stored > 0) {
    bits = new_bits[req_size - 1] >> (8 - bits_stored);
    new_bits[req_size - 1] &= ~(0xffu << (8 - bits_stored));
  }

  // shift new bits
  if (offset > 0) {
    for (int i = 0; i < req_size; i++) {
      op_bits[i] |= new_bits[i] << offset;
      op_bits[i + 1] |= new_bits[i] >> (8 - offset);
    }
  } else {
    for (int i = 0; i < req_size; i++) {
      op_bits[i] = new_bits[i];
    }
  }

  // return new bits
  for (int i = 0; i < op_size; i++) {
    *cptr++ |= op_bits[i];
  }
}

/*
 * Local variables:
 *  compile-command: "make -C .."
 *  c++-font-lock-extra-types: ("[A-Z]\\sw*[a-z]\\sw*")
 * End:
 */
