/* XRACER (C) 1999-2000 Richard W.M. Jones <rich@annexia.org> and other AUTHORS
 *
 * 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; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Id: texture.c,v 1.4 2000/03/20 21:08:57 rich Exp $
 */

#include "config.h"

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

#include <GL/glu.h>

#include "xracer.h"
#include "xracer-log.h"
#include "xracer-texture.h"
#include "xracer-jpeg.h"

/* Find a value nearest to n which is a power of two.
 * This is taken from the source to Mesa src-glu/mipmap.c.
 */
static GLint
round2(GLint n)
{
  GLint m;

  for(m = 1 ; m < n ; m *= 2);

  if((m - n) <= (n - (m / 2)))
    return m;
  else
    return (m / 2);
} 

/* Load a texture from a file. */
GLuint
xrTextureLoad (const char *filename,
	       const char *filename_alpha,
	       int *width_rtn, int *height_rtn,
	       int flags)
{
  int width, height, components, alpha_components = 0, error;
  GLuint texture;
  GLubyte *image;
  int mipmaps = flags & XR_TEXTURE_MIPMAPS;
  int modulate = flags & XR_TEXTURE_MODULATE;

  /* Load the JPEG file. */
  image = xrJPEGReadFile (filename, &width, &height, &components);

  if (image == NULL)
    {
      xrLogPerror ("error loading texture file: %s", filename);
      return -1;
    }

  /* Number of components in the main texture has to be 1 or 3. */
  xrLogAssert (components == 1 || components == 3);

  /* Alpha component to this texture? */
  if (filename_alpha)
    {
      int alpha_width, alpha_height, i;
      GLubyte *p, *q, *r;
      GLubyte *alpha_image;
      GLubyte *combined_image;

      alpha_image = xrJPEGReadFile (filename_alpha,
				    &alpha_width, &alpha_height,
				    &alpha_components);

      if (alpha_image == NULL)
	{
	  xrLogPerror ("error loading texture file (alpha component): %s",
		       filename_alpha);
	  free (image);
	  return -1;
	}

      /* Alpha component must match main texture image. */
      xrLogAssert (alpha_components == 1 || alpha_components == 3);
      xrLogAssert (alpha_width == width);
      xrLogAssert (alpha_height == height);

      /* Allocate space for the combined image + alpha channel. */
      combined_image = xmalloc (sizeof (GLubyte)
				* (components+1) * width * height);

      /* Create the combined image. */
      p = image;
      q = alpha_image;
      r = combined_image;

      for (i = 0; i < width * height; ++i)
	{
	  *r++ = *p++;		/* R or Luminance */
	  if (components > 1)
	    {
	      *r++ = *p++;	/* G */
	      *r++ = *p++;	/* B */
	    }
	  *r++ = 255 - *q;	/* A - just use red component for now. */
	  q += alpha_components;
	}

      free (image);
      free (alpha_image);
      image = combined_image;
    }

#if 0
  xrLog (LOG_DEBUG,
	 "%s: size = (%d, %d), components = %d, alpha_components = %d",
	 filename, width, height, components, alpha_components);
#endif

  /* Create the texture object. */
  glGenTextures (1, &texture);
  glBindTexture (GL_TEXTURE_2D, texture);

  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

  if (!modulate)
    glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
  else
    glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

  /* Build mipmaps, if requested. */
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

  if (mipmaps && xrMipmapping)
    {
      int type;

      if (components == 1 && alpha_components == 0)
	type = GL_LUMINANCE;
      else if (components == 3 && alpha_components == 0)
	type = GL_RGB;
      else if (components == 1 && alpha_components > 0)
	type = GL_LUMINANCE_ALPHA;
      else if (components == 3 && alpha_components > 0)
	type = GL_RGBA;
      else
	xrLogFatal ("components = %d, alpha_components = %d",
		    components, alpha_components);

      glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
		       GL_LINEAR_MIPMAP_LINEAR);

      if ((error = gluBuild2DMipmaps (GL_TEXTURE_2D,
				      type,
				      width, height,
				      type,
				      GL_UNSIGNED_BYTE, image)) != 0)
	{
	  xrLogFatal ("gluBuild2DMipmaps failed (GL error: %s)",
		      gluErrorString (error));
	}
    }
  else
    {
      /* Scale the texture to something which GL can handle. The
       * logic here is taken from Mesa src-glu/mipmap.c.
       */
      GLint new_width, new_height, maxsize;

      int type;

      if (components == 1 && alpha_components == 0)
	type = GL_LUMINANCE;
      else if (components == 3 && alpha_components == 0)
	type = GL_RGB;
      else if (components == 1 && alpha_components > 0)
	type = GL_LUMINANCE_ALPHA;
      else if (components == 3 && alpha_components > 0)
	type = GL_RGBA;
      else
	xrLogFatal ("components = %d, alpha_components = %d",
		    components, alpha_components);

      glGetIntegerv (GL_MAX_TEXTURE_SIZE, &maxsize);

      new_width = round2 (width);
      new_height = round2 (height);

      if (new_width > maxsize) new_width = maxsize;
      if (new_height > maxsize) new_height = maxsize;

      if (new_width != width || new_height != height)
	{
	  GLubyte *new_image;

	  xrLog (LOG_DEBUG,
		 "no mipmaps: scaling texture "
		 "%s original size %dx%d to new size %dx%d",
		 filename, width, height, new_width, new_height);

	  new_image = xmalloc (sizeof (GLubyte)
			       * (components + alpha_components)
			       * new_width * new_height);

	  if ((error = gluScaleImage (type,
				      width, height,
				      GL_UNSIGNED_BYTE,
				      image,
				      new_width, new_height,
				      GL_UNSIGNED_BYTE,
				      new_image)) != 0)
	    {
	      xrLogFatal("gluScaleImage failed (GL error: %s)",
			 gluErrorString(error));
	    }

	  free (image);
	  image = new_image;
	  width = new_width;
	  height = new_height;
	}

      glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

      glTexImage2D (GL_TEXTURE_2D,
		    0,
		    type,
		    width, height,
		    0,
		    type,
		    GL_UNSIGNED_BYTE,
		    image);
    }

  /* Clean up the image. */
  free (image);

  /* Return width and height, if requested to. */
  if (width_rtn) *width_rtn = width;
  if (height_rtn) *height_rtn = height;

  /* Return the texture object number. */
  return texture;
}

void
xrTextureUnload (GLuint texture)
{
  glDeleteTextures (1, &texture);
}
