/*
 * --- T2-COPYRIGHT-NOTE-BEGIN ---
 * This copyright note is auto-generated by ./scripts/Create-CopyPatch.
 * 
 * T2 SDE: misc/lua/lzlib/lzlib.c
 * Copyright (C) 2004 - 2006 The T2 SDE Project
 * 
 * More information can be found in the files COPYING and README.
 * 
 * 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; version 2 of the License. A copy of the
 * GNU General Public License can be found in the file COPYING.
 * --- T2-COPYRIGHT-NOTE-END ---
 */
// compilation: gcc --shared lzlib.c -o lzlib.so -lz
// contains some slightly addapted code pieces from lua's liolib.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <zlib.h>
#include <lua.h>
#include <lauxlib.h>

#define ZLIB_HANDLE "gzFile"

// ------------------- helper functions ------------------------ 

// pushes gzFile operation result string on lua stack

// first value is either true or nil (== operation failed),
// in the latter case a string containing the error message and
// the nummerical error codes (gzerror / errno) are returned
// as additional values

static int pushresult (lua_State *L, gzFile* zf, const char *filename)
{
  int zlib_result;
  const char* zmessage;
  if ((zf) == NULL)
    zlib_result = Z_ERRNO;
  else
    zmessage = gzerror (*zf, &zlib_result);

  if (zlib_result == Z_OK) {
    lua_pushboolean(L, 1);
    return 1;
  } else {

    if (zlib_result == Z_ERRNO) // error is a file io error
      zmessage = strerror(errno);

    lua_pushnil(L);

    if (filename)
      lua_pushfstring(L, "%s: %s", filename, zmessage);
    else
      lua_pushfstring(L, "%s", zmessage);

    lua_pushinteger(L, zlib_result);
    lua_pushinteger(L, errno);

    return 4;
  }
}

// ensures first argument is an open zlib handle and returns it
static gzFile *tozfile (lua_State *L)
{
  gzFile *zf = ((gzFile *) luaL_checkudata(L, 1, ZLIB_HANDLE));

  if (*zf == NULL)
    luaL_error(L, "attempt to use a closed gz file");
  return zf;
}

// ----------------- poor man buffer in C ---------------------

char* content_buffer = NULL;
size_t content_buffer_length = 0;
size_t content_length = 0;

static inline void ResetBuffer ()
{ 
  content_length = 0;
}

static inline void ExtendBufferBySize (size_t min_size)
{
  if (min_size > content_buffer_length) {
    if (content_buffer_length == 0)
      content_buffer = malloc (min_size);
    else
      content_buffer = realloc (content_buffer, min_size);
    content_buffer_length = min_size;
  }
}

static inline void ExtendBuffer ()
{
  ExtendBufferBySize ((content_buffer_length < 256) ? 256 : 2 * content_buffer_length);
  // for testing:  ExtendBufferBySize ((content_buffer_length < 10) ? 10 : 2 + content_buffer_length);
}

static inline void AddToBuffer (char c)
{
  if (content_buffer_length == content_length)
    ExtendBuffer ();
  content_buffer [content_length++] = c;
}

static inline const char* FinishBuffer ()
{
  AddToBuffer ('\0');
  return content_buffer;
}

static inline int BufferFill ()
{
  return content_length;
}

static inline int BufferFree () 
{
  return (content_buffer_length - content_length);
}

// -------------------------- API ------------------------------

static int gz_open (lua_State *L)
{
  const char *filename = luaL_checkstring(L, 1);
  const char *mode = luaL_optstring(L, 2, "r");

  gzFile* zf = (gzFile *) lua_newuserdata(L, sizeof(gzFile));
  luaL_getmetatable(L, ZLIB_HANDLE);
  lua_setmetatable(L, -2);

  *zf = gzopen(filename, mode);

  return (*zf == NULL) ? pushresult(L, NULL, filename) : 1;
}

static int gz_close (lua_State *L)
{
  gzFile *zf = tozfile(L);
  int result = gzclose(*zf);
  *zf = NULL;

  // need to emulate pushresult behavior, because gzerror
  // does not work anymore after closing stream *grrrr*

  if (result == Z_OK) {
    lua_pushboolean (L, 1);
    return 1;
  } else {
    const char* zmessage;
    if (result == Z_ERRNO) // error is a file io error
      zmessage = strerror(errno);
    else
      zmessage = zError(result);

    lua_pushnil(L);
    lua_pushfstring(L, zmessage);
    lua_pushinteger(L, result);
    lua_pushinteger(L, errno);
    return 4;
  }
}


static int __gz_lines_iterator (lua_State *L)
{
  gzFile zf = (gzFile) lua_topointer (L, lua_upvalueindex(1));
  int ch = '\0';
  char* ret;

  if (content_buffer_length == 0)
    ExtendBuffer ();
  else
    ResetBuffer ();

  #ifdef READ_LINE_ONE_BY_ONE

  while ( (ch = gzgetc(zf)) != -1 && ch != '\n') {
    AddToBuffer ((char) ch);
  }

  #else

  do {
    ret = gzgets (zf, content_buffer + BufferFill (), BufferFree ());
    if (ret == Z_NULL)
      break;
    int l = strlen (content_buffer);
    content_length = l;
    if (l > 0) {
      ch = content_buffer[l - 1];
      if (ch != '\n')
	ExtendBuffer ();
      else
	content_buffer[l-1] = '\0';
    }
  } while (ret && ch != '\n');

  #endif

  if (ch == '\n' || BufferFill () > 0) {
    if (ch == '\n')
      
    lua_pushstring (L, FinishBuffer ());
    return 1;
  } else {
    return pushresult(L, &zf, NULL);
  }
}


static int gz_lines (lua_State *L)
{
  gzFile *zf = tozfile(L);
  lua_pushlightuserdata (L, *zf);
  lua_pushcclosure (L, __gz_lines_iterator, 1);
  return 1;
}

static int gz_status (lua_State *L)
{
  gzFile *zf = tozfile(L);
  return pushresult (L, zf, NULL);
}

static int gz_eof (lua_State *L)
{
  gzFile *zf = tozfile(L);
  int eof = gzeof (*zf);
  lua_pushboolean(L, eof);
  if (eof == 1) {
    const char* eof_reason;
    int eof_reason_code;
    eof_reason = gzerror (*zf, &eof_reason_code);
    lua_pushboolean (L, eof_reason_code == Z_STREAM_END);
    lua_pushstring (L, eof_reason);
    return 3;
  }
  return 0;
}

// ------------------- init and registering --------------------

static const luaL_reg R[] = {
  {"open", gz_open},
  {"close", gz_close},
  {"lines", gz_lines},
  {"status", gz_status},
  {"eof", gz_eof},
  {NULL, NULL}
};

static const luaL_Reg zlib[] = {
  {"close", gz_close},
  {"lines", gz_lines},
  {"status", gz_status},
  {"eof", gz_eof},
  {"__gc", gz_close},
  {NULL, NULL}
};

static void createmeta (lua_State *L) {
  luaL_newmetatable(L, ZLIB_HANDLE);  /* create metatable for zlib handles */
  lua_pushvalue(L, -1);  /* push metatable */
  lua_setfield(L, -2, "__index");  /* metatable.__index = metatable */
  luaL_register(L, NULL, zlib);  /* gz file methods */
}

LUALIB_API int luaopen_lzlib (lua_State *L)
{
  createmeta(L);
  luaL_openlib(L, "lzlib", R, 0);
  lua_pushliteral(L,"version");           /** version */
  lua_pushliteral(L,"pre-alpha");
  lua_settable(L,-3);
  return 1;
}
