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

gzfilebuf::gzfilebuf(void)
  : file(NULL), mode(0), is_my_fd(0),
    level(Z_DEFAULT_COMPRESSION), strategy(Z_DEFAULT_STRATEGY) { }

gzfilebuf::~gzfilebuf(void)
{
  sync();
  if(is_my_fd) close();
}

gzfilebuf*
gzfilebuf::open(const char* name, int io_mode)
{
  if(is_open()) return NULL;
char  char_mode[3];
char* p = char_mode;
       if(io_mode & ios::in )    { mode  = ios::in;     *p++ = 'r'; }
  else if(io_mode & ios::app)    { mode  = ios::app;    *p++ = 'a'; }
  else                           { mode  = ios::out;    *p++ = 'w'; }
       if(io_mode & ios::binary) { mode |= ios::binary; *p++ = 'b'; }
  *p = 0;

  if((file = gzopen(name, char_mode)) == NULL) return NULL;
  gzsetparams(file, level, strategy);
  is_my_fd = true;
  return this;
}

gzfilebuf*
gzfilebuf::attach(int fd, int io_mode) {
  if(is_open()) return NULL;
char  char_mode[3];
char* p = char_mode;
       if(io_mode & ios::in )    { mode  = ios::in;     *p++ = 'r'; }
  else if(io_mode & ios::app)    { mode  = ios::app;    *p++ = 'a'; }
  else                           { mode  = ios::out;    *p++ = 'w'; }
       if(io_mode & ios::binary) { mode |= ios::binary; *p++ = 'b'; }
  *p = 0;

  if((file = gzdopen(fd, char_mode)) == NULL) return NULL;
  gzsetparams(file, level, strategy);
  is_my_fd = false;
  return this;
}

gzfilebuf*
gzfilebuf::close() {
  if(is_open()) { sync(); gzclose(file); file = NULL; }
  return this;
}

int
gzfilebuf::setlevel(int l)
{
  return gzsetparams(file, level = l, strategy);
}

int
gzfilebuf::setstrategy(int s)
{
  return gzsetparams(file, level, strategy = s);
}


streampos
gzfilebuf::seekoff(streamoff off, ios::seek_dir dir, int which)
{
  return streampos(EOF);
}

int
gzfilebuf::underflow()
{
  // If the file hasn't been opened for reading, error.
  if(!is_open() || !(mode & ios::in)) return EOF;

  // if a buffer doesn't exists, allocate one.
  if(!base()) {
    if((allocate()) == EOF) return EOF;
    setp(0,0);
  } else {
    if(in_avail()) return (unsigned char) *gptr();
    if(out_waiting() && flushbuf() == EOF) return EOF;
  }

  // Attempt to fill the buffer.
int result = fillbuf();
  if(result == EOF) {
    // disable get area
    setg(0, 0, 0);
    return EOF;
  }
  return (unsigned char) *gptr();
}

int
gzfilebuf::overflow(int c)
{
  if(!is_open() || !(mode & ios::out)) return EOF;
  if(!base()) {
    if(allocate() == EOF) return EOF;
    setg(0,0,0);
  } else if(in_avail() || (out_waiting() && flushbuf() == EOF)) return EOF;
int bl = blen();
  setp(base(), base() + bl);
  if(c != EOF) { *pptr() = c; pbump(1); }
  return 0;
}

int
gzfilebuf::sync() {
  if(!is_open())    return EOF;
  if(out_waiting()) return flushbuf();
  return 0;
}

int
gzfilebuf::flushbuf()
{
char* q = pbase();
int   n = pptr() - q;
  if(gzwrite(file, q, n) < n) return EOF;
  setp(0,0);
  return 0;
}

int
gzfilebuf::fillbuf() {
char* p = base();
int   n = blen();
int   r = gzread(file, p, n);
  if(r <= 0) return EOF;
  setg(base(), base(), base() + r);
  return r;
}

basic_gzfstream::basic_gzfstream(void)
  : ios(basic_gzfstream::rdbuf()) { }

basic_gzfstream::~basic_gzfstream(void) { }

void
basic_gzfstream::attach(int fd, int io_mode)
{
  if(!buffer.attach(fd, io_mode)) clear(ios::failbit | ios::badbit);
  else clear();
}

void
basic_gzfstream::open(const char* name, int io_mode)
{
  if(!buffer.open(name, io_mode)) clear(ios::failbit | ios::badbit);
  else clear();
}

void
basic_gzfstream::close()
{
  if(!buffer.close()) clear(ios::failbit | ios::badbit);
}

gzfilebuf* basic_gzfstream::rdbuf() { return &buffer; }
     
gzifstream::gzifstream(void)
  : ios(basic_gzfstream::rdbuf()) { clear(ios::badbit); }

gzifstream::gzifstream(const char* name, int io_mode)
  : ios(basic_gzfstream::rdbuf()) { basic_gzfstream::open(name, io_mode); }

gzifstream::gzifstream(int fd, int io_mode)
  : ios(basic_gzfstream::rdbuf()) { basic_gzfstream::attach(fd, io_mode); }

gzifstream::~gzifstream() { }
     
gzofstream::gzofstream(void)
  : ios(basic_gzfstream::rdbuf()) { clear(ios::badbit); }

gzofstream::gzofstream(const char* name, int io_mode)
  : ios(basic_gzfstream::rdbuf()) { basic_gzfstream::open(name, io_mode); }

gzofstream::gzofstream(int fd, int io_mode)
  : ios(basic_gzfstream::rdbuf()) { basic_gzfstream::attach(fd, io_mode); }

gzofstream::~gzofstream() { }
