/** \file
 * \brief LuaBinding for Lua 3
 *
 * See Copyright Notice in im_lib.h
 * $Id: im_lua3.c,v 1.1 2008/10/17 06:10:16 scuri Exp $
 */

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

#include <lua.h>

#include "im.h"
#include "im_lib.h"

#include <cd.h>
#include <cdlua3_private.h>

#include "imlua.h"


/***************************************************************************\
* Globals.                                                                  *
\***************************************************************************/
static int color_tag;
static int imagergb_tag;
static int imagergba_tag;
static int palette_tag;
static int imagemap_tag;
static int channel_tag;

static channel_t channel_info;

#define IMLUA_VERSION "IMLua 1.2"
/***************************************************************************\
* Creation and destruction functions.                                       *
\***************************************************************************/

/***************************************************************************\
* Creates a buffer for a RGB image.                                         *
\***************************************************************************/
static void imlua_createimagergb(void)
{
  lua_Object width, height;
  long int width_i, height_i;
  imagergb_t *imagergb_p;

  width = lua_getparam(1);
  height = lua_getparam(2);
  if (!(lua_isnumber(width) && lua_isnumber(height)))
    lua_error("imCreateImageRGB: invalid dimensions parameter!");
  width_i = (long int) lua_getnumber(width);
  height_i = (long int) lua_getnumber(height);
  if (width_i < 1 || height_i < 1)
    lua_error("imCreateImageRGB: image dimensions should be positive integers!");

  if (lua_getparam(3) != LUA_NOOBJECT)
    lua_error("imCreateImageRGB: too many parameters!");
  
  imagergb_p = (imagergb_t *) malloc(sizeof(imagergb_t));
  if (!imagergb_p) {
    lua_pushnil();
    return;
  }

  imagergb_p->width = width_i;
  imagergb_p->height = height_i;
  imagergb_p->size = width_i*height_i;
  imagergb_p->red = (unsigned char *) malloc(imagergb_p->size);
  imagergb_p->green = (unsigned char *) malloc(imagergb_p->size);
  imagergb_p->blue = (unsigned char *) malloc(imagergb_p->size);
  
  if (!(imagergb_p->red && imagergb_p->green && imagergb_p->blue)) { 
    if (imagergb_p->red) free(imagergb_p->red);
    if (imagergb_p->green) free(imagergb_p->green);
    if (imagergb_p->blue) free(imagergb_p->blue);
    free(imagergb_p);
    lua_pushnil();
    return;
  }

  memset(imagergb_p->red, 255, imagergb_p->size);
  memset(imagergb_p->green, 255, imagergb_p->size);
  memset(imagergb_p->blue, 255, imagergb_p->size);
  
  lua_pushusertag((void *) imagergb_p, imagergb_tag);
}

/***************************************************************************\
* Frees a previously allocated imagergb. We don't free imagergb_p to avoid  *
* problems if the user called killimagergb twice with the same object. The  *
* structure will be freed by a userdata "gc" fallback in LUA 3.0.           *
\***************************************************************************/
static void imlua_killimagergb(void)
{
  lua_Object imagergb;
  imagergb_t *imagergb_p;

  imagergb = lua_getparam(1);
  if (imagergb == LUA_NOOBJECT)
    lua_error("imKillImageRGB: imagergb parameter missing!");
  if (lua_isnil(imagergb))
    lua_error("imKillImageRGB: attempt to kill a NIL imagergb!");
  if (lua_tag(imagergb) != imagergb_tag)
    lua_error("imKillImageRGB: invalid imagergb parameter!");
  imagergb_p = (imagergb_t *) lua_getuserdata(imagergb);
  if (!(imagergb_p->red && imagergb_p->green && imagergb_p->blue)) 
    lua_error("imKillImageRGB: attempt to kill a killed imagergb!");

  if (lua_getparam(2) != LUA_NOOBJECT)
    lua_error("imKillImageRGB: too many parameters!");

  free(imagergb_p->red);
  free(imagergb_p->green);
  free(imagergb_p->blue);
  imagergb_p->red = NULL;
  imagergb_p->green = NULL;
  imagergb_p->blue = NULL;
}

/***************************************************************************\
* Creates a palette as a palette_tag usertag lua_Object. A palette can be   *
* considered and treated as a color table.                                  *
\***************************************************************************/
static void imlua_createpalette(void)
{
  lua_Object size;
  long int size_i;
  palette_t *palette_p;

  size = lua_getparam(1);
  if (!(lua_isnumber(size)))
    lua_error("imCreatePalette: invalid palette parameter!");
  size_i = (long int) lua_getnumber(size);
  if (size_i < 1)
    lua_error("imCreatePalette: palette size should be a positive integer!");

  if (lua_getparam(2) != LUA_NOOBJECT)
    lua_error("imCreatePalette: too many parameters!");

  palette_p = (palette_t *) malloc(sizeof(palette_t));
  if (!palette_p) {
    lua_pushnil();
    return;
  }

  palette_p->size = size_i;
  palette_p->color = (long int *) malloc(palette_p->size * sizeof(long int));
  if (!palette_p->color) {
    free(palette_p);
    lua_pushnil();
    return;
  }

  memset(palette_p->color, 255, palette_p->size * sizeof(long int));
  lua_pushusertag((void *) palette_p, palette_tag);
}

/***************************************************************************\
* Frees a previously allocated palette. We don't free palette_p to prevent  *
* a problem if the user called killpalette twice with the same object. The  *
* structure will be freed by a userdata "gc" fallback in LUA 3.0.           *
\***************************************************************************/
static void imlua_killpalette(void)
{
  lua_Object palette;
  palette_t *palette_p;

  palette = lua_getparam(1);
  if (palette == LUA_NOOBJECT)
    lua_error("imKillPalette: palette parameter missing!");
  if (lua_isnil(palette))
    lua_error("imKillPalette: attempt to kill a NIL palette!");
  if (lua_tag(palette) != palette_tag)
    lua_error("imKillPalette: invalid palette parameter!");
  palette_p = (palette_t *) lua_getuserdata(palette);
  if (!palette_p->color) 
    lua_error("imKillPalette: attempt to kill a killed palette!");

  if (lua_getparam(2) != LUA_NOOBJECT)
    lua_error("imKillPalette: too many parameters!");

  free(palette_p->color);
  palette_p->color = NULL;
}

/***************************************************************************\
* Creates a imagemap as a imagemap_tag usertag lua_Object.                  *
\***************************************************************************/
static void imlua_createimagemap(void)
{
  lua_Object width;
  lua_Object height;

  long int width_i;
  long int height_i;
  imagemap_t *imagemap_p;

  width = lua_getparam(1);
  height = lua_getparam(2);
  if (!(lua_isnumber(width) && lua_isnumber(height)))
    lua_error("imCreateImageMap: invalid dimensions parameter!");
  width_i = (long int) lua_getnumber(width);
  height_i = (long int) lua_getnumber(height);
  if (width_i < 1 || height_i < 1)
    lua_error("imCreateImageMap: imagemap dimensions should be positive integers!");

  if (lua_getparam(3) != LUA_NOOBJECT)
    lua_error("imCreateImageMap: too many parameters!");

  imagemap_p = (imagemap_t *) malloc(sizeof(imagemap_t));
  if (!imagemap_p) {
    lua_pushnil();
    return;
  }

  imagemap_p->size = width_i*height_i;
  imagemap_p->width = width_i;
  imagemap_p->height = height_i;
  imagemap_p->index = (unsigned char *) malloc(imagemap_p->size);
  if (!imagemap_p->index) {
    free(imagemap_p);
    lua_pushnil();
    return;
  }

  memset(imagemap_p->index, 0, imagemap_p->size);
  lua_pushusertag((void *) imagemap_p, imagemap_tag);
}

/***************************************************************************\
* Frees a previously allocated imagemap. We don't free imagemap_p to avoid  *
* problems if the user called killimagemap twice with the same object. The  *
* structure will be freed by a userdata "gc" fallback in LUA 3.0.           *
\***************************************************************************/
static void imlua_killimagemap(void)
{
  lua_Object imagemap;
  imagemap_t *imagemap_p;

  imagemap = lua_getparam(1);
  if (imagemap == LUA_NOOBJECT)
    lua_error("imKillImageMap: imagemap parameter missing!");
  if (lua_isnil(imagemap))
    lua_error("imKillImageMap: attempt to kill a NIL imagemap!");
  if (lua_tag(imagemap) != imagemap_tag)
    lua_error("imKillImageMap: invalid imagemap parameter!");
  imagemap_p = (imagemap_t *) lua_getuserdata(imagemap);
  if (!imagemap_p->index) 
    lua_error("imKillImageMap: attempt to kill a killed imagemap!");
    
  if (lua_getparam(2) != LUA_NOOBJECT)
    lua_error("imKillImageMap: too many parameters!");

  free(imagemap_p->index);
  imagemap_p->index = NULL;
}

/***************************************************************************\
* IM API Functions.                                                         *
\***************************************************************************/

/***************************************************************************\
* imFileFormat                                                              *
\***************************************************************************/
static void imlua_fileformat(void)
{
  lua_Object file;

  char *file_s;
  int format_i;
  int compress_i;

  int err;

  file = lua_getparam(1);
  if (!lua_isstring(file))
    lua_error("imFileFormat: invalid filename parameter!");
  file_s = (char *) lua_getstring(file);

  if (lua_getparam(2) != LUA_NOOBJECT)
    lua_error("imFileFormat: too many parameters!");

  err = imFileFormat(file_s, &format_i);

  compress_i = (format_i & IM_DEFAULT) ? 1 : 0;
  format_i = format_i & 0xFF;
  
  /* if success, return the format */
  if (err == IM_ERR_NONE) {
    lua_pushnumber( format_i);
    lua_pushnumber( compress_i);
  }
  /* if failure, return nil */
  else {
    lua_pushnil();
    lua_pushnil();
  }

  lua_pushnumber( err);
}

/***************************************************************************\
* imFileFormat                                                              *
\***************************************************************************/
static void imlua_imageinfo(void)
{
  lua_Object file;

  char *file_s;
  
  int width, height, image_type, pal_size;
  int err;
  
  file = lua_getparam(1);
  if (!lua_isstring(file))
    lua_error("imFileFormat: invalid filename parameter!");
  file_s = (char *) lua_getstring(file);

  if (lua_getparam(2) != LUA_NOOBJECT)
    lua_error("imFileFormat: too many parameters!");

  err = imImageInfo(file_s, &width, &height, &image_type, &pal_size);
  
  /* if success, return the format */
  if (err == IM_ERR_NONE) {
    lua_pushnumber( width);
    lua_pushnumber( height);
    lua_pushnumber( image_type);
    lua_pushnumber( pal_size);
  }
  /* if failure, return nil */
  else {
    lua_pushnil();
    lua_pushnil();
    lua_pushnil();
    lua_pushnil();
  }

  lua_pushnumber( err);
}

/***************************************************************************\
* imEncodeColor                                                             *
\***************************************************************************/
static void imlua_encodecolor(void)
{
  lua_Object red, green, blue;
  float red_f, green_f, blue_f;
  unsigned char red_i, green_i, blue_i;
  long int color_i;

  red = lua_getparam(1);
  green = lua_getparam(2);
  blue = lua_getparam(3);

  if (lua_getparam(4) != LUA_NOOBJECT)
    lua_error("imEncodeColor: too many parameters!");

  if (!(lua_isnumber(red) && lua_isnumber(green) && lua_isnumber(blue)))
    lua_error("imEncodeColor: invalid color component parameter!");

  red_f = (float)lua_getnumber(red);
  green_f = (float)lua_getnumber(green);
  blue_f = (float)lua_getnumber(blue);

  if (red_f < 0 || red_f > 255 || green_f < 0 || 
      green_f > 255 || blue_f < 0 || blue_f > 255)
    lua_error("imEncodeColor: color components values should be in range [0, 255]!");

  red_i = (unsigned char) (red_f);
  green_i = (unsigned char) (green_f);
  blue_i = (unsigned char) (blue_f);

  color_i = imEncodeColor(red_i, green_i, blue_i);
  lua_pushusertag((void *) color_i, color_tag);
}

/***************************************************************************\
* imDecodeColor                                                             *
\***************************************************************************/
static void imlua_decodecolor(void)
{
  lua_Object color;
  long int color_i;
  unsigned char red_i, green_i, blue_i;

  color = lua_getparam(1);
  if (lua_tag(color) != color_tag)
    lua_error("imDecodeColor: invalid color parameter!");
  color_i = (long int) lua_getuserdata(color);

  if (lua_getparam(2) != LUA_NOOBJECT)
    lua_error("imDecodeColor: too many parameters!");

  imDecodeColor(&red_i, &green_i, &blue_i, color_i);

  lua_pushnumber( red_i);
  lua_pushnumber( green_i);
  lua_pushnumber( blue_i);
}

/***************************************************************************\
* imLoadRGB                                                                 *
\***************************************************************************/
static void imlua_loadrgb(void)
{
  lua_Object file, imagergb;

  imagergb_t *imagergb_p;
  char *file_s;

  int err;

  file = lua_getparam(1);
  if (!lua_isstring(file))
    lua_error("imLoadRGB: invalid filename parameter!");
  file_s = (char *) lua_getstring(file);

  imagergb = lua_getparam(2);
  if (lua_tag(imagergb) != imagergb_tag)
    lua_error("imLoadRGB: invalid imagergb parameter!");
  imagergb_p = (imagergb_t *) lua_getuserdata(imagergb);

  if (lua_getparam(3) != LUA_NOOBJECT)
    lua_error("imLoadRGB: too many parameters!");

  err = imLoadRGB(file_s, imagergb_p->red, imagergb_p->green, imagergb_p->blue);
  lua_pushnumber( err);
}

/***************************************************************************\
* imLoadMap                                                                 *
\***************************************************************************/
static void imlua_loadmap(void)
{
  lua_Object file, imagemap, palette;

  imagemap_t *imagemap_p;
  palette_t *palette_p;
  char *file_s;

  int err;

  file = lua_getparam(1);
  if (!lua_isstring(file))
    lua_error("imLoadMap: invalid filename parameter!");
  file_s = (char *) lua_getstring(file);

  imagemap = lua_getparam(2);
  if (lua_tag(imagemap) != imagemap_tag)
    lua_error("imLoadMap: invalid imagemap parameter!");
  imagemap_p = (imagemap_t *) lua_getuserdata(imagemap);

  palette = lua_getparam(3);
  if (lua_tag(palette) != palette_tag)
    lua_error("imLoadMap: invalid palette parameter!");
  palette_p = (palette_t *) lua_getuserdata(palette);

  if (lua_getparam(4) != LUA_NOOBJECT)
    lua_error("imLoadMap: too many parameters!");

  err = imLoadMap(file_s, imagemap_p->index, palette_p->color);
  lua_pushnumber( err);
}

/***************************************************************************\
* imSaveRGB                                                                 *
\***************************************************************************/
static void imlua_savergb(void)
{
  lua_Object file, imagergb, format, compress;

  imagergb_t *imagergb_p;
  char *file_s;
  int format_i;
  int compress_i;

  int err;

  imagergb = lua_getparam(1);
  if (lua_tag(imagergb) != imagergb_tag)
    lua_error("imSaveRGB: invalid imagergb parameter!");
  imagergb_p = (imagergb_t *) lua_getuserdata(imagergb);

  format = lua_getparam(2);
  if (!lua_isnumber(format))
    lua_error("imSaveRGB: invalid format parameter!");
  format_i = (int) lua_getnumber(format);
  
  compress = lua_getparam(3);
  if (!lua_isnumber(compress))
    lua_error("imSaveRGB: invalid compression parameter!");
  compress_i = (int) lua_getnumber(compress);

  file = lua_getparam(4);
  if (!lua_isstring(file))
    lua_error("imSaveRGB: invalid filename parameter!");
  file_s = (char *) lua_getstring(file);

  if (lua_getparam(5) != LUA_NOOBJECT)
    lua_error("imSaveRGB: too many parameters!");

  err = imSaveRGB(imagergb_p->width, imagergb_p->height, format_i | (compress_i << 8), 
    imagergb_p->red, imagergb_p->green, imagergb_p->blue, file_s);
  lua_pushnumber( err);
}

/***************************************************************************\
* imSaveMap                                                                 *
\***************************************************************************/
static void imlua_savemap(void)
{
  lua_Object file, imagemap, palette, format, compress;

  imagemap_t *imagemap_p;
  palette_t *palette_p;
  char *file_s;
  int format_i;
  int compress_i;

  int err;

  imagemap = lua_getparam(1);
  if (lua_tag(imagemap) != imagemap_tag)
    lua_error("imSaveMap: invalid imagemap  parameter!");
  imagemap_p = (imagemap_t *) lua_getuserdata(imagemap);

  palette = lua_getparam(2);
  if (lua_tag(palette) != palette_tag)
    lua_error("imLoadMap: invalid palette parameter!");
  palette_p = (palette_t *) lua_getuserdata(palette);

  format = lua_getparam(3);
  if (!lua_isnumber(format))
    lua_error("imSaveMap: invalid format parameter!");
  format_i = (int) lua_getnumber(format);
  
  compress = lua_getparam(4);
  if (!lua_isnumber(compress))
    lua_error("imSaveMap: invalid compression parameter!");
  compress_i = (int) lua_getnumber(compress);

  file = lua_getparam(5);
  if (!lua_isstring(file))
    lua_error("imSaveMap: invalid filename parameter!");
  file_s = (char *) lua_getstring(file);

  if (lua_getparam(6) != LUA_NOOBJECT)
    lua_error("imSaveMap: too many parameters!");

  err = imSaveMap(imagemap_p->width, imagemap_p->height, format_i | (compress_i << 8), 
    imagemap_p->index, palette_p->size, palette_p->color, file_s);
  lua_pushnumber( err);
}

/***************************************************************************\
* imRGB2Map                                                                 *
\***************************************************************************/
static void imlua_rgb2map(void)
{
  lua_Object imagergb, imagemap, palette;

  imagemap_t *imagemap_p;
  palette_t *palette_p;
  imagergb_t *imagergb_p;

  imagergb = lua_getparam(1);
  if (lua_tag(imagergb) != imagergb_tag)
    lua_error("imRGB2Map: invalid imagergb parameter!");
  imagergb_p = (imagergb_t *) lua_getuserdata(imagergb);

  imagemap = lua_getparam(2);
  if (lua_tag(imagemap) != imagemap_tag)
    lua_error("imRGB2Map: invalid imagemap parameter!");
  imagemap_p = (imagemap_t *) lua_getuserdata(imagemap);

  palette = lua_getparam(3);
  if (lua_tag(palette) != palette_tag)
    lua_error("imRGB2Map: invalid palette parameter!");
  palette_p = (palette_t *) lua_getuserdata(palette);

  if (lua_getparam(4) != LUA_NOOBJECT)
    lua_error("imRGB2Map: too many parameters!");

  if (imagergb_p->size != imagemap_p->size)
    lua_error("imRGB2Map: images have incompatible dimensions!");
  
  imRGB2Map(imagergb_p->width, imagergb_p->height, imagergb_p->red, 
    imagergb_p->green, imagergb_p->blue, imagemap_p->index, palette_p->size, 
    palette_p->color);
}

/***************************************************************************\
* imMap2RGB                                                                 *
\***************************************************************************/
static void imlua_map2rgb(void)
{
  lua_Object imagergb, imagemap, palette;

  imagemap_t *imagemap_p;
  palette_t *palette_p;
  imagergb_t *imagergb_p;

  imagemap = lua_getparam(1);
  if (lua_tag(imagemap) != imagemap_tag)
    lua_error("imMap2RGB: invalid imagemap parameter!");
  imagemap_p = (imagemap_t *) lua_getuserdata(imagemap);

  palette = lua_getparam(2);
  if (lua_tag(palette) != palette_tag)
    lua_error("imMap2RGB: invalid palette parameter!");
  palette_p = (palette_t *) lua_getuserdata(palette);

  imagergb = lua_getparam(3);
  if (lua_tag(imagergb) != imagergb_tag)
    lua_error("imMap2RGB: invalid imagergb parameter!");
  imagergb_p = (imagergb_t *) lua_getuserdata(imagergb);

  if (lua_getparam(4) != LUA_NOOBJECT)
    lua_error("imMap2RGB: too many parameters!");

  if (imagergb_p->size != imagemap_p->size)
    lua_error("imMap2RGB: images have incompatible dimensions!");
  
  imMap2RGB(imagemap_p->width, imagemap_p->height, imagemap_p->index, palette_p->size, 
    palette_p->color, imagergb_p->red, imagergb_p->green, imagergb_p->blue);
}

/***************************************************************************\
* imMap2Gray                                                                *
\***************************************************************************/
static void imlua_map2gray(void)
{
  lua_Object imagemap, palette, graymap, grays;

  imagemap_t *imagemap_p;
  palette_t *palette_p;
  imagemap_t *graymap_p;
  palette_t *grays_p;

  imagemap = lua_getparam(1);
  if (lua_tag(imagemap) != imagemap_tag)
    lua_error("imMap2Gray: invalid imagemap parameter!");
  imagemap_p = (imagemap_t *) lua_getuserdata(imagemap);

  palette = lua_getparam(2);
  if (lua_tag(palette) != palette_tag)
    lua_error("imMap2Gray: invalid palette parameter!");
  palette_p = (palette_t *) lua_getuserdata(palette);

  graymap = lua_getparam(3);
  if (lua_tag(graymap) != imagemap_tag)
    lua_error("imMap2Gray: invalid graymap parameter!");
  graymap_p = (imagemap_t *) lua_getuserdata(graymap);

  grays = lua_getparam(4);
  if (lua_tag(grays) != palette_tag)
    lua_error("imMap2Gray: invalid grays parameter!");
  grays_p = (palette_t *) lua_getuserdata(grays);

  if (lua_getparam(5) != LUA_NOOBJECT)
    lua_error("imMap2Gray: too many parameters!");

  if (imagemap_p->size != graymap_p->size)
    lua_error("imMap2Gray: images have incompatible dimensions!");
  
  if (grays_p->size < 256)
    lua_error("imMap2Gray: grays palette should be of size 256!");

  imMap2Gray(imagemap_p->width, imagemap_p->height, imagemap_p->index, 
    palette_p->size, palette_p->color, graymap_p->index, grays_p->color);
}

/***************************************************************************\
* imRGB2Gray                                                                *
\***************************************************************************/
static void imlua_rgb2gray(void)
{
  lua_Object imagergb, graymap, grays;

  imagergb_t *imagergb_p;
  imagemap_t *graymap_p;
  palette_t *grays_p;

  imagergb = lua_getparam(1);
  if (lua_tag(imagergb) != imagergb_tag)
    lua_error("imRGB2Gray: invalid imagergb parameter!");
  imagergb_p = (imagergb_t *) lua_getuserdata(imagergb);

  graymap = lua_getparam(2);
  if (lua_tag(graymap) != imagemap_tag)
    lua_error("imRGB2Gray: invalid graymap parameter!");
  graymap_p = (imagemap_t *) lua_getuserdata(graymap);

  grays = lua_getparam(3);
  if (lua_tag(grays) != palette_tag)
    lua_error("imRGB2Gray: invalid grays parameter!");
  grays_p = (palette_t *) lua_getuserdata(grays);

  if (lua_getparam(4) != LUA_NOOBJECT)
    lua_error("imRGB2Gray: too many parameters!");

  if (imagergb_p->size != graymap_p->size)
    lua_error("imRGB2Gray: images have incompatible dimensions!");
  
  if (grays_p->size < 256)
    lua_error("imRGB2Gray: grays palette should be of size 256!");

  imRGB2Gray(imagergb_p->width, imagergb_p->height, imagergb_p->red,
    imagergb_p->green, imagergb_p->blue, graymap_p->index, grays_p->color);
}

/***************************************************************************\
* imStretch.                                                                *
\***************************************************************************/
static void imlua_stretch(void)
{
  lua_Object src, dst;

  imagemap_t *srcmap_p;
  imagemap_t *dstmap_p;
  imagergb_t *srcrgb_p;
  imagergb_t *dstrgb_p;

  src = lua_getparam(1);
  dst = lua_getparam(2);
  if (lua_getparam(3) != LUA_NOOBJECT)
    lua_error("imStretch: too many parameters!");

  if ((lua_tag(src) == imagergb_tag) && (lua_tag(dst) == imagergb_tag)) {
    srcrgb_p = (imagergb_t *) lua_getuserdata(src);
    dstrgb_p = (imagergb_t *) lua_getuserdata(dst);
    imStretch(srcrgb_p->width, srcrgb_p->height, srcrgb_p->red,
      dstrgb_p->width, dstrgb_p->height, dstrgb_p->red);
    imStretch(srcrgb_p->width, srcrgb_p->height, srcrgb_p->green,
      dstrgb_p->width, dstrgb_p->height, dstrgb_p->green);
    imStretch(srcrgb_p->width, srcrgb_p->height, srcrgb_p->blue,
      dstrgb_p->width, dstrgb_p->height, dstrgb_p->blue);
  } 
  else if ((lua_tag(src) == imagemap_tag) && (lua_tag(dst) == imagemap_tag)) {
    srcmap_p = (imagemap_t *) lua_getuserdata(src);
    dstmap_p = (imagemap_t *) lua_getuserdata(dst);
    imStretch(srcmap_p->width, srcmap_p->height, srcmap_p->index,
      dstmap_p->width, dstmap_p->height, dstmap_p->index);
  }
  else {
    lua_error("imStretch: inconsistent parameters!");
  }
}

/***************************************************************************\
* imResize.                                                                *
\***************************************************************************/
static void imlua_resize(void)
{
  lua_Object src, dst;

  imagergb_t *srcrgb_p;
  imagergb_t *dstrgb_p;

  src = lua_getparam(1);
  dst = lua_getparam(2);
  if (lua_getparam(3) != LUA_NOOBJECT)
    lua_error("imResize: too many parameters!");

  if ((lua_tag(src) == imagergb_tag) && (lua_tag(dst) == imagergb_tag)) {
    srcrgb_p = (imagergb_t *) lua_getuserdata(src);
    dstrgb_p = (imagergb_t *) lua_getuserdata(dst);
    imStretch(srcrgb_p->width, srcrgb_p->height, srcrgb_p->red,
      dstrgb_p->width, dstrgb_p->height, dstrgb_p->red);
    imStretch(srcrgb_p->width, srcrgb_p->height, srcrgb_p->green,
      dstrgb_p->width, dstrgb_p->height, dstrgb_p->green);
    imStretch(srcrgb_p->width, srcrgb_p->height, srcrgb_p->blue,
      dstrgb_p->width, dstrgb_p->height, dstrgb_p->blue);
  } 
  else {
    lua_error("imResize: parameters must be of type imagergb_tag!");
  }
}

/***************************************************************************\
* imVersion.                                                                *
\***************************************************************************/
static void imlua_version(void)
{
  if (lua_getparam(1) != LUA_NOOBJECT)
    lua_error("imVersion: too many parameters!");
 
  lua_pushstring(imVersion());
}

/***************************************************************************\
* Fallback implementation.                                                  *
\***************************************************************************/

/***************************************************************************\
* imagemap "settable" fallback.                                             *
\***************************************************************************/
static void imagemapsettable_fb(void)
{
  lua_Object imagemap, index, value;

  imagemap_t *imagemap_p;
  long int index_i;
  long int value_i;

  imagemap = lua_getparam(1);
  index = lua_getparam(2);
  value = lua_getparam(3);

  imagemap_p = (imagemap_t *) lua_getuserdata(imagemap);
  if (!imagemap_p) {
    lua_error("imagemap_tag \"settable\": invalid imagemap_tag object!");
  }

  if (!lua_isnumber(index)) {
    lua_error("imagemap_tag \"settable\": index should be a number!");
  }

  if (!lua_isnumber(value)) {
    lua_error("imagemap_tag \"settable\": value should be a number!");
  }
  
  value_i = (long int) lua_getnumber(value);
  if ((value_i < 0 || value_i > 255)) 
    lua_error("imagemap_tag \"settable\": value should be in range [0, 255]!");

  index_i = (long int) lua_getnumber(index);
  if (index_i < 0 || index_i >= imagemap_p->size)
    lua_error("imagemap_tag \"settable\": index is out of bounds!");

  imagemap_p->index[index_i] = (unsigned char) value_i;
}

/***************************************************************************\
* palette "settable" fallback.                                              *
\***************************************************************************/
static void palettesettable_fb(void)
{
  lua_Object palette, index, color;
  
  palette_t *palette_p;
  long int index_i;
  long int color_i;

  palette = lua_getparam(1);
  index = lua_getparam(2);
  color = lua_getparam(3);

  palette_p = (palette_t *) lua_getuserdata(palette);
  if (!palette_p) {
    lua_error("palette_tag \"settable\": invalid palette_tag object!");
  }

  if (!lua_isnumber(index)) {
    lua_error("palette_tag \"settable\": index should be a number!");
  }

  if (lua_tag(color) != color_tag) 
    lua_error("palette_tag \"settable\": value should be of type color_tag!");
  
  color_i = (long int) lua_getuserdata(color);
  
  index_i = (long int) lua_getnumber(index);
  if (index_i < 0 || index_i >= palette_p->size)
    lua_error("palette_tag \"settable\": index is out of bounds!");

  palette_p->color[index_i] = color_i;
}

/***************************************************************************\
* channel "settable" fallback. This fallback is called when a LUA line like *
* "imagergb.r[y*w + x] = c" is executed. The imagergb "gettable" fallback   *
* fills and returns a channel structure with info about the buffer. This    *
* structure is consulted and the value is assigned where it should.         *
\***************************************************************************/
static void channelsettable_fb(void)
{
  lua_Object channel, index, value;
  
  channel_t *channel_p;
  long int index_i;
  long int value_i;

  channel = lua_getparam(1);
  index = lua_getparam(2);
  value = lua_getparam(3);

  channel_p = (channel_t *) lua_getuserdata(channel);
  if (!channel_p) {
    lua_error("channel_tag \"settable\": invalid channel_tag object!");
  }

  if (!lua_isnumber(index)) {
    lua_error("channel_tag \"settable\": index should be a number!");
  }
  index_i = (long int) lua_getnumber(index);
  if (index_i < 0 || index_i >= channel_p->size) {
    lua_error("channel_tag \"settable\": index is out of bounds!");
  }
  
  if (!lua_isnumber(value)) {
    lua_error("channel_tag \"settable\": value should be a number!");
  }
  value_i = (long int) lua_getnumber(value);
  if ((value_i < 0 || value_i > 255)) {
    lua_error("channel_tag \"settable\": value should be in range [0, 255]!");
  }

  channel_p->value[index_i] = (unsigned char) value_i;
}

/***************************************************************************\
* imagemap "gettable" fallback.                                             *
\***************************************************************************/
static void imagemapgettable_fb(void)
{
  lua_Object imagemap, index;

  imagemap_t *imagemap_p;
  long int index_i;

  imagemap = lua_getparam(1);
  index = lua_getparam(2);

  imagemap_p = (imagemap_t *) lua_getuserdata(imagemap);
  if (!imagemap_p)
    lua_error("imagemap_tag \"gettable\": invalid imagemap_tag object!");

  if (!lua_isnumber(index)) {
    lua_error("imagemap_tag \"gettable\": index should be a number!");
  }

  index_i = (long int) lua_getnumber(index);
  if (index_i < 0 || index_i >= imagemap_p->size)
    lua_error("imagemap_tag \"gettable\": index is out of bounds!");

  lua_pushnumber( imagemap_p->index[index_i]);
}

/***************************************************************************\
* palette "gettable" fallback.                                              *
\***************************************************************************/
static void palettegettable_fb(void)
{
  lua_Object palette, index;
  
  palette_t *palette_p;
  long int index_i;

  palette = lua_getparam(1);
  index = lua_getparam(2);

  palette_p = (palette_t *) lua_getuserdata(palette);
  if (!palette_p)
    lua_error("palette_tag \"gettable\": invalid palette_tag object!");

  if (!lua_isnumber(index)) {
    lua_error("palette_tag \"gettable\": index should be a number!");
  }

  index_i = (long int) lua_getnumber(index);
  if (index_i < 0 || index_i >= palette_p->size)
    lua_error("palette_tag \"gettable\": index is out of bounds!");

  lua_pushusertag((void *) palette_p->color[index_i], color_tag);
}

/***************************************************************************\
* channel "gettable" fallback. This fallback is called when a LUA line like *
* "c = imagergb.r[y*w + x]" is executed. The imagergb "gettable" fallback   *
* fills and returns a channel structure with info about the buffer. This    *
* structure is consulted and the appropriate value is returned.             *
\***************************************************************************/
static void channelgettable_fb(void)
{
  lua_Object channel, index;
  
  channel_t *channel_p;
  long int index_i;

  channel = lua_getparam(1);
  index = lua_getparam(2);

  channel_p = (channel_t *) lua_getuserdata(channel);
  if (!channel_p) {
    lua_error("channel_tag \"gettable\": invalid channel_tag object!");
  }

  if (!lua_isnumber(index)) {
    lua_error("channel_tag \"gettable\": index should be a number!");
  }
  index_i = (long int) lua_getnumber(index);
  if (index_i < 0 || index_i >= channel_p->size) {
    lua_error("channel_tag \"gettable\": index is out of bounds!");
  }
  
  lua_pushnumber( channel_p->value[index_i]);
}

/***************************************************************************\
* imagergb "gettable" fallback. This fallback is called when a LUA line     *
* like "c = imagergb.r[y*w + x]" or "imagergb.r[y*w + x] = c" is executed.  *
* The channel_info global is filled and its address is returned with a      *
* channel_tag usertag lua_Object. The following "gettable" or "settable"    *
* then assigns or returns the appropriate value.                            *
\***************************************************************************/
static void imagergbgettable_fb(void)
{
  lua_Object imagergb, index;
  
  char *index_s;
  imagergb_t *imagergb_p;

  imagergb = lua_getparam(1);
  index = lua_getparam(2);

  imagergb_p = (imagergb_t *) lua_getuserdata(imagergb);
  if (!imagergb_p)
    lua_error("imagergb_tag \"gettable\": invalid imagergb_tag object!");

  if (!lua_isstring(index)) {
    lua_error("imagergb_tag \"gettable\": index should be a channel name!");
  }
  index_s = (char *) lua_getstring(index);

  channel_info.size = imagergb_p->size;
  
  if (*index_s == 'r' || *index_s == 'R') {
    channel_info.value = imagergb_p->red;
  }
  else if (*index_s == 'g' || *index_s == 'G') {
    channel_info.value = imagergb_p->green;
  }
  else if (*index_s == 'b' || *index_s == 'B') {
    channel_info.value = imagergb_p->blue;
  }
  else {
    lua_error("imagergb_tag \"gettable\": index is an invalid channel name!");
  }

  lua_pushusertag((void *) &channel_info, channel_tag);
}

/***************************************************************************\
* imagergba "gettable" fallback. This fallback is called when a LUA line    *
* like "c = imagergba.r[y*w + x]" or "imagergba.r[y*w + x] = c" is executed.*
* The channel_info global is filled and its address is returned with a      *
* channel_tag usertag lua_Object. The following "gettable" or "settable"    *
* then assigns or returns the appropriate value.                            *
\***************************************************************************/
static void imagergbagettable_fb(void)
{
  lua_Object imagergba, index;
  
  char *index_s;
  imagergba_t *imagergba_p;

  imagergba = lua_getparam(1);
  index = lua_getparam(2);

  imagergba_p = (imagergba_t *) lua_getuserdata(imagergba);
  if (!imagergba_p)
    lua_error("imagergba_tag \"gettable\": invalid imagergba_tag object!");

  if (!lua_isstring(index)) {
    lua_error("imagergba_tag \"gettable\": index should be a channel name!");
  }
  index_s = (char *) lua_getstring(index);

  channel_info.size = imagergba_p->size;
  
  if (*index_s == 'r' || *index_s == 'R') {
    channel_info.value = imagergba_p->red;
  }
  else if (*index_s == 'g' || *index_s == 'G') {
    channel_info.value = imagergba_p->green;
  }
  else if (*index_s == 'b' || *index_s == 'B') {
    channel_info.value = imagergba_p->blue;
  }
  else if (*index_s == 'a' || *index_s == 'A') {
    channel_info.value = imagergba_p->alpha;
  }
  else {
    lua_error("imagergba_tag \"gettable\": index is an invalid channel name!");
  }

  lua_pushusertag((void *) &channel_info, channel_tag);
}

/***************************************************************************\
* palette "gc" fallback.                                                    *
\***************************************************************************/
static void palettegc_fb(void)
{
  lua_Object palette;

  palette_t *palette_p;

  palette = lua_getparam(1);
  palette_p = (palette_t *) lua_getuserdata(palette);
  if (!palette_p)
    lua_error("palette_tag \"gc\": invalid palette_tag object!");

  /* if the palette has not been killed, kill it */
  if (palette_p->color) free(palette_p->color);

  /* free the palette_t structure */
  free(palette_p);
}

/***************************************************************************\
* imagergb "gc" fallback.                                                   *
\***************************************************************************/
static void imagergbgc_fb(void)
{
  lua_Object imagergb;

  imagergb_t *imagergb_p;

  imagergb = lua_getparam(1);
  imagergb_p = (imagergb_t *) lua_getuserdata(imagergb);
  if (!imagergb_p)
    lua_error("imagergb_tag \"gc\": invalid imagergb_tag object!");

  /* if the imagergb has not been killed, kill it */
  if (imagergb_p->red) free(imagergb_p->red);
  if (imagergb_p->green) free(imagergb_p->green);
  if (imagergb_p->blue) free(imagergb_p->blue);

  /* free the imagergb_t structure */
  free(imagergb_p);
}

/***************************************************************************\
* imagergba "gc" fallback.                                                   *
\***************************************************************************/
static void imagergbagc_fb(void)
{
  lua_Object imagergba;

  imagergba_t *imagergba_p;

  imagergba = lua_getparam(1);
  imagergba_p = (imagergba_t *) lua_getuserdata(imagergba);
  if (!imagergba_p)
    lua_error("imagergba_tag \"gc\": invalid imagergba_tag object!");

  /* if the imagergba has not been killed, kill it */
  if (imagergba_p->red) free(imagergba_p->red);
  if (imagergba_p->green) free(imagergba_p->green);
  if (imagergba_p->blue) free(imagergba_p->blue);
  if (imagergba_p->alpha) free(imagergba_p->alpha);

  /* free the imagergba_t structure */
  free(imagergba_p);
}

/***************************************************************************\
* imagemap "gc" fallback.                                                   *
\***************************************************************************/
static void imagemapgc_fb(void)
{
  lua_Object imagemap;

  imagemap_t *imagemap_p;

  imagemap = lua_getparam(1);
  imagemap_p = (imagemap_t *) lua_getuserdata(imagemap);
  if (!imagemap_p)
    lua_error("imagemap_tag \"gc\": invalid imagemap_tag object!");

  /* if the imagemap has not been killed, kill it */
  if (imagemap_p->index) free(imagemap_p->index);

  /* free the imagemap_t structure */
  free(imagemap_p);
}

/***************************************************************************\
* Initialization code.                                                      *
\***************************************************************************/

/***************************************************************************\
* Initializes IMLua.                                                        *
\***************************************************************************/
void imlua_open(void)
{
  lua_Object cdlua_tag;
  
  /* check if CD has been initialized */
  cdlua_tag = lua_getglobal("CDLUA_INSTALLED");

  /* get CD defined tags, let CD deal with the user tag objects  */
  if ((cdlua_tag != LUA_NOOBJECT) && (!lua_isnil(cdlua_tag))) {
    cdlua_tag = lua_getglobal("CDLUA_COLOR_TAG");
    color_tag = (int) lua_getnumber(cdlua_tag);
    cdlua_tag = lua_getglobal("CDLUA_IMAGERGB_TAG");
    imagergb_tag = (int) lua_getnumber(cdlua_tag);
    cdlua_tag = lua_getglobal("CDLUA_IMAGERGBA_TAG");
    imagergba_tag = (int) lua_getnumber(cdlua_tag);
    cdlua_tag = lua_getglobal("CDLUA_PALETTE_TAG");
    palette_tag = (int) lua_getnumber(cdlua_tag);
    cdlua_tag = lua_getglobal("CDLUA_IMAGEMAP_TAG");
    imagemap_tag = (int) lua_getnumber(cdlua_tag);
    cdlua_tag = lua_getglobal("CDLUA_CHANNEL_TAG");
    channel_tag = (int) lua_getnumber(cdlua_tag);
  }
  /* define IM own tags and fallbacks  */
  else {
    color_tag     = lua_newtag();
    imagergb_tag  = lua_newtag();
    imagergba_tag = lua_newtag();
    imagemap_tag  = lua_newtag();
    palette_tag   = lua_newtag();
    channel_tag   = lua_newtag();

    /* associate the fallbacks */
    lua_pushcfunction(palettesettable_fb); lua_settagmethod(palette_tag, "settable");
    lua_pushcfunction(channelsettable_fb); lua_settagmethod(channel_tag, "settable");
    lua_pushcfunction(imagemapsettable_fb); lua_settagmethod(imagemap_tag, "settable");
  
    lua_pushcfunction(imagergbgettable_fb); lua_settagmethod(imagergb_tag, "gettable");
    lua_pushcfunction(imagergbagettable_fb); lua_settagmethod(imagergba_tag, "gettable");
    lua_pushcfunction(palettegettable_fb); lua_settagmethod(palette_tag, "gettable");
    lua_pushcfunction(imagemapgettable_fb); lua_settagmethod(imagemap_tag, "gettable");
    lua_pushcfunction(channelgettable_fb); lua_settagmethod(channel_tag, "gettable");

    lua_pushcfunction(imagergbgc_fb); lua_settagmethod(imagergb_tag, "gc");
    lua_pushcfunction(imagergbagc_fb); lua_settagmethod(imagergba_tag, "gc");
    lua_pushcfunction(palettegc_fb); lua_settagmethod(palette_tag, "gc");
    lua_pushcfunction(imagemapgc_fb); lua_settagmethod(imagemap_tag, "gc");
  }

  /* register used tags in global context for other libraries use */
  lua_pushnumber(1.0f); lua_setglobal("IMLUA_INSTALLED");
  lua_pushnumber( color_tag); lua_setglobal("IMLUA_COLOR_TAG");
  lua_pushnumber( imagergb_tag); lua_setglobal("IMLUA_IMAGERGB_TAG");
  lua_pushnumber( imagergba_tag); lua_setglobal("IMLUA_IMAGERGBA_TAG");
  lua_pushnumber( imagemap_tag); lua_setglobal("IMLUA_IMAGEMAP_TAG");
  lua_pushnumber( palette_tag); lua_setglobal("IMLUA_PALETTE_TAG");
  lua_pushnumber( channel_tag); lua_setglobal("IMLUA_CHANNEL_TAG");

  /* registered IM functions */
  lua_register("imDecodeColor",         imlua_decodecolor);
  lua_register("imEncodeColor",         imlua_encodecolor);
  lua_register("imLoadRGB",             imlua_loadrgb);
  lua_register("imLoadMap",             imlua_loadmap);
  lua_register("imSaveRGB",             imlua_savergb);
  lua_register("imSaveMap",             imlua_savemap);
  lua_register("imFileFormat",          imlua_fileformat);
  lua_register("imImageInfo",           imlua_imageinfo);
  lua_register("imRGB2Map",             imlua_rgb2map);
  lua_register("imMap2RGB",             imlua_map2rgb);
  lua_register("imRGB2Gray",            imlua_rgb2gray);
  lua_register("imMap2Gray",            imlua_map2gray);
  lua_register("imVersion",             imlua_version);
  lua_register("imResize",              imlua_resize);
  lua_register("imStretch",             imlua_stretch);
  
  /* creation and destruction functions */
  lua_register("imCreateImageRGB",      imlua_createimagergb);
  lua_register("imCreateImageMap",      imlua_createimagemap);
  lua_register("imCreatePalette",       imlua_createpalette);
  lua_register("imKillImageRGB",        imlua_killimagergb);
  lua_register("imKillImageMap",        imlua_killimagemap);
  lua_register("imKillPalette",         imlua_killpalette);

  /* im constants */
  lua_pushnumber( IM_BMP); lua_setglobal("IM_BMP");
  lua_pushnumber( IM_PCX); lua_setglobal("IM_PCX");
  lua_pushnumber( IM_GIF); lua_setglobal("IM_GIF");
  lua_pushnumber( IM_TIF); lua_setglobal("IM_TIF");
  lua_pushnumber( IM_RAS); lua_setglobal("IM_RAS");
  lua_pushnumber( IM_SGI); lua_setglobal("IM_SGI");
  lua_pushnumber( IM_JPG); lua_setglobal("IM_JPG");
  lua_pushnumber( IM_LED); lua_setglobal("IM_LED");
  lua_pushnumber( IM_TGA); lua_setglobal("IM_TGA");

  lua_pushnumber( 0); lua_setglobal("IM_NONE");
  lua_pushnumber( 1); lua_setglobal("IM_DEFAULT");
  lua_pushnumber( 2); lua_setglobal("IM_COMPRESSED");

  lua_pushnumber( IM_RGB); lua_setglobal("IM_RGB");
  lua_pushnumber( IM_MAP); lua_setglobal("IM_MAP");

  lua_pushnumber( IM_ERR_NONE); lua_setglobal("IM_ERR_NONE");
  lua_pushnumber( IM_ERR_OPEN); lua_setglobal("IM_ERR_OPEN");
  lua_pushnumber( IM_ERR_READ); lua_setglobal("IM_ERR_READ");
  lua_pushnumber( IM_ERR_WRITE); lua_setglobal("IM_ERR_WRITE");
  lua_pushnumber( IM_ERR_FORMAT); lua_setglobal("IM_ERR_FORMAT");
  lua_pushnumber( IM_ERR_TYPE); lua_setglobal("IM_ERR_TYPE");
  lua_pushnumber( IM_ERR_COMP); lua_setglobal("IM_ERR_COMP");
}