#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//
#include "proto.h"
#include "bucket.h"


static U8 *buf;
static U32 bufsize, bucket, bits, ptr, writing;

static S32 extend_buffer(void);



S32 init_bucket(U8 *buffer, U32 size) {

  if (buffer == NULL) {       // writing
    buf = malloc(32*1024);
    if (!buf)       return 1;
    writing = 32*1024;
  } else {                    // reading
    buf = buffer;
    writing = 0;
  }
  bufsize = size;
  bits = 0;
  ptr = 0;

  return 0;
}


S32 close_bucket(U8 **out, U32 *size) {

  if (!buf)         return 1;

  if (!writing)     return 0;

  flush_bucket();

  *out = buf;
  *size = ptr;
  return 0;
}



S32 flush_bucket() {

  if (writing) {
    if (bits) {
      if (bits &7)   write_ubits(8 - (bits &7), 0);
      while (bits > 0) {
        buf[ptr++] = bucket>>24;
        bits -= 8;
        bucket <<= 8;
      }
    }
    bucket = 0;

  } else {
    bits = 0;
    bucket = 0;
  }

  return 0;
}


// -------------------------------------------------------------------

U32 read_position(U8 **p) {

  if (p)  *p = buf;
  return ptr;
}


S32 read_ushort(U16 *output) {

  if (ptr+2 > bufsize)  return 1;
  *output = buf[ptr+0] | (buf[ptr+1]<<8);
  ptr += 2;
  bits = 0;
  return 0;
}


S32 read_short(S16 *output) {

  return read_ushort((U16 *)output);
}


S32 read_uint(U32 *output) {

  if (ptr+4 > bufsize)  return 1;
  *output = buf[ptr+0] | (buf[ptr+1]<<8) | (buf[ptr+2]<<16) | (buf[ptr+3]<<24);
  ptr += 4;
  bits = 0;
  return 0;
}


S32 read_int(S32 *output) {

  return read_uint((U32 *)output);
}


S32 read_ubyte(U8 *output) {

  if (ptr+1 > bufsize)  return 1;
  *output = buf[ptr+0];
  ptr += 1;
  bits = 0;
  return 0;
}


S32 read_byte(S8 *output) {

  if (ptr+1 > bufsize)  return 1;
  *output = (S8)buf[ptr+0];
  ptr += 1;
  bits = 0;
  return 0;
}



S32 read_bits(S32 n, S32 *output) {

  if (read_ubits(n, (U32 *)output))  return 1;
  if (n == 32)  return 0;
  n = 32-n;
  *output = (*output<<n)>>n;
  return 0;
}


S32 read_ubits(S32 n, U32 *output) {

  U32 v;

  if (n == 0) {
    *output = 0;
    return 0;
  }

  if (ptr+(n>>3) > bufsize)  return 1;

  if (bits == 0)  bucket = buf[ptr++], bits = 8;

  v = 0;
  while (1) {
    if (n > bits) {
      v |= bucket<<(n-bits);
      n -= bits;
      bucket = buf[ptr++];
      bits = 8;

    } else {
      v |= bucket>>(bits-n);
      bits -= n;
      bucket &= 0xff>>(8-bits);
      *output = v;
      return 0;
    }
  }

  return 1;
}

// -------------------------------------------------------------------

S32 bucket_insert(S32 start, U8 *data, S32 bytes) {

  if (!writing)               return 1;
  if (bytes <= 0)             return 1;

  if (ptr + 4 + bytes > writing-4) {
    U8 *newbuf;
    S32 add;

    add = (bytes+8191) & ~8191;
    newbuf = realloc(buf, writing+add);
    if (!newbuf)   return 1;
    buf = newbuf;
    writing += add;
  }

  memmove(buf+start+bytes, buf+start, writing-(start+bytes));
  memcpy(buf+start, data, bytes);
  ptr += bytes;

  return 0;
}


S32 write_ushort(U16 input) {

  return write_short((S16)input);
}


S32 write_short(S16 input) {

  if (extend_buffer())        return 1;
  flush_bucket();

  buf[ptr++] = input & 0xff;
  buf[ptr++] = (input>>8) & 0xff;

  return 0;
}


S32 write_uint(U32 input) {

  return write_int((S32)input);
}


S32 write_int(S32 input) {

  if (extend_buffer())        return 1;
  flush_bucket();

  buf[ptr++] = input       & 0xff;
  buf[ptr++] = (input>>8)  & 0xff;
  buf[ptr++] = (input>>16) & 0xff;
  buf[ptr++] = (input>>24) & 0xff;

  return 0;
}


S32 write_ubyte(U8 input) {

  return write_byte((S8)input);
}


S32 write_byte(S8 input) {

  if (extend_buffer())        return 1;
  flush_bucket();

  buf[ptr++] = input;

  return 0;
}


S32 write_ubits(S32 n, U32 input) {

  return write_bits(n, (S32)input);
}


S32 write_bits(S32 n, S32 input) {

  if (n == 0)                 return 0;
  if (extend_buffer())        return 1;

  if (n < 32)  input = input & ~(0xffffffff<<n);

  while (1) {
    if (n == 0)
      return 0;

    else if (bits + n > 32) {
      S32 thistime;

      thistime = 32-bits;
      bucket |= input>>(n-thistime);
      input &= ~(0xffffffff<<(n-thistime));
      n -= thistime;

      buf[ptr++] = (bucket>>24) & 0xff;
      buf[ptr++] = (bucket>>16) & 0xff;
      buf[ptr++] = (bucket>>8 ) & 0xff;
      buf[ptr++] =  bucket      & 0xff;
      bits = 0;
      bucket = 0;

    } else {
      bucket |= input<<(32-bits-n);
      bits += n;
      return 0;
    }
  }

  return 1;
}


S32 write_position(U32 newpos, U32 *oldpos) {

  flush_bucket();
  *oldpos = ptr;
  ptr = newpos;

  return 0;
}

// -------------------------------------------------------------------

S32 extend_buffer() {

  if (ptr >= writing-4) {
    U8 *newbuf;
    newbuf = realloc(buf, writing+32*1024);
    if (!newbuf)   return 1;
    buf = newbuf;
    writing += 32*1024;
  }

  return 0;
}
