/*
 * Compressed texture demo.  Written by Daniel Borca.
 * This program is in the public domain.
 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <glad/glad.h>
#include "glut_wrap.h"

#include "readtex.c" /* I know, this is a hack. */
#define TEXTURE_FILE DEMOS_DATA_DIR "tree2.rgba"


static float Rot = 0.0;
static GLboolean Anim = 1;

typedef struct {
        GLubyte *data;
        GLuint size;
        GLenum format;
        GLuint w, h;

        GLenum TC;

        GLubyte *cData;
        GLuint cSize;
        GLenum cFormat;
} TEXTURE;

static TEXTURE *Tx, t1, t2, t3;
static GLboolean fxt1, dxtc, s3tc;


static const char *TextureName (GLenum TC)
{
 switch (TC) {
        case GL_RGB:
             return "RGB";
        case GL_RGBA:
             return "RGBA";
        case GL_COMPRESSED_RGB:
             return "COMPRESSED_RGB";
        case GL_COMPRESSED_RGBA:
             return "COMPRESSED_RGBA";
        case GL_COMPRESSED_RGB_FXT1_3DFX:
             return "GL_COMPRESSED_RGB_FXT1_3DFX";
        case GL_COMPRESSED_RGBA_FXT1_3DFX:
             return "GL_COMPRESSED_RGBA_FXT1_3DFX";
        case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
             return "GL_COMPRESSED_RGB_S3TC_DXT1_EXT";
        case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
             return "GL_COMPRESSED_RGBA_S3TC_DXT1_EXT";
        case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
             return "GL_COMPRESSED_RGBA_S3TC_DXT3_EXT";
        case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
             return "GL_COMPRESSED_RGBA_S3TC_DXT5_EXT";
        case GL_RGB_S3TC:
             return "GL_RGB_S3TC";
        case GL_RGB4_S3TC:
             return "GL_RGB4_S3TC";
        case GL_RGBA_S3TC:
             return "GL_RGBA_S3TC";
        case GL_RGBA4_S3TC:
             return "GL_RGBA4_S3TC";
        case 0:
             return "Invalid format";
        default:
             return "Unknown format";
 }
}


static void
PrintString(const char *s)
{
   while (*s) {
      glutBitmapCharacter(GLUT_BITMAP_8_BY_13, (int) *s);
      s++;
   }
}


static void Idle( void )
{
   float t = glutGet(GLUT_ELAPSED_TIME) * 0.001;  /* in seconds */
   Rot = t * 360 / 4;  /* 1 rotation per 4 seconds */
   glutPostRedisplay();
}


static void Display( void )
{
   /* draw background gradient */
   glDisable(GL_TEXTURE_2D);
   glBegin(GL_POLYGON);
   glColor3f(1.0, 0.0, 0.2); glVertex2f(-1.5, -1.0);
   glColor3f(1.0, 0.0, 0.2); glVertex2f( 1.5, -1.0);
   glColor3f(0.0, 0.0, 1.0); glVertex2f( 1.5,  1.0);
   glColor3f(0.0, 0.0, 1.0); glVertex2f(-1.5,  1.0);
   glEnd();

   glPushMatrix();
   glRotatef(Rot, 0, 0, 1);

   glEnable(GL_TEXTURE_2D);
   glEnable(GL_BLEND);

   glBegin(GL_POLYGON);
   glTexCoord2f(0, 1);  glVertex2f(-1, -0.5);
   glTexCoord2f(1, 1);  glVertex2f( 1, -0.5);
   glTexCoord2f(1, 0);  glVertex2f( 1,  0.5);
   glTexCoord2f(0, 0);  glVertex2f(-1,  0.5);
   glEnd();

   glPopMatrix();

   glDisable(GL_TEXTURE_2D);

   /* info */
   glDisable(GL_BLEND);
   glColor4f(1, 1, 1, 1);

   glRasterPos3f(-1.2, -0.7, 0);
   PrintString("Selected: ");
   PrintString(TextureName(Tx->TC));
   if (Tx->cData) {
      char tmp[64];
      glRasterPos3f(-1.2, -0.8, 0);
      PrintString("Internal: ");
      PrintString(TextureName(Tx->cFormat));
      glRasterPos3f(-1.2, -0.9, 0);
      PrintString("Size    : ");
      sprintf(tmp, "%d (%d%% of %d)", Tx->cSize, Tx->cSize * 100 / Tx->size, Tx->size);
      PrintString(tmp);
   }

   glutSwapBuffers();
}


static void Reshape( int width, int height )
{
   glViewport( 0, 0, width, height );
   glMatrixMode( GL_PROJECTION );
   glLoadIdentity();
   glOrtho( -1.5, 1.5, -1.0, 1.0, -1.0, 1.0 );
   glMatrixMode( GL_MODELVIEW );
   glLoadIdentity();
}


static void ReInit( GLenum TC, TEXTURE *Tx )
{
   GLint rv, v;

   if ((Tx->TC == TC) && (Tx->cData != NULL)) {
      glCompressedTexImage2DARB(GL_TEXTURE_2D, /* target */
	                        0,             /* level */
	                        Tx->cFormat,   /* real format */
	                        Tx->w,         /* original width */
	                        Tx->h,         /* original height */
	                        0,             /* border */
	                        Tx->cSize,     /* compressed size*/
	                        Tx->cData);    /* compressed data*/
   } else {
      glTexImage2D(GL_TEXTURE_2D,    /* target */
                   0,                /* level */
                   TC,               /* internal format */
                   Tx->w, Tx->h,     /* width, height */
                   0,                /* border */
                   Tx->format,       /* texture format */
                   GL_UNSIGNED_BYTE, /* texture type */
                   Tx->data);        /* the texture */


      v = 0;
      glGetTexLevelParameteriv(GL_TEXTURE_2D, 0,
                               GL_TEXTURE_INTERNAL_FORMAT, &v);
      printf("Requested internal format = 0x%x, actual = 0x%x\n", TC, v);

      if (0) {
         GLint r, g, b, a, l, i;
         glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_RED_SIZE, &r);
         glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_GREEN_SIZE, &g);
         glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_BLUE_SIZE, &b);
         glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_ALPHA_SIZE, &a);
         glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_LUMINANCE_SIZE, &l);
         glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTENSITY_SIZE, &i);
         printf("Compressed Bits per R: %d  G: %d  B: %d  A: %d  L: %d  I: %d\n",
                r, g, b, a, l, i);
      }

      /* okay, now cache the compressed texture */
      Tx->TC = TC;
      if (Tx->cData != NULL) {
         free(Tx->cData);
         Tx->cData = NULL;
      }
      glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED_ARB, &rv);
      if (rv) {
         glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, (GLint *)&Tx->cFormat);
         glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB, (GLint *)&Tx->cSize);
         if ((Tx->cData = malloc(Tx->cSize)) != NULL) {
            glGetCompressedTexImageARB(GL_TEXTURE_2D, 0, Tx->cData);
         }
      }
   }
}


static void Init( void )
{
   /* HEIGHT * WIDTH + 1 (for trailing '\0') */
   static char pattern[8 * 32 + 1] = {"\
                                \
    MMM    EEEE   SSS    AAA    \
   M M M  E      S   S  A   A   \
   M M M  EEEE    SS    A   A   \
   M M M  E         SS  AAAAA   \
   M   M  E      S   S  A   A   \
   M   M   EEEE   SSS   A   A   \
                                "
      };

   GLuint i, j;

   GLubyte (*texture1)[8 * 32][4];
   GLubyte (*texture2)[256][256][4];

   t1.w = 32;
   t1.h = 8;
   t1.size = t1.w * t1.h * 4;
   t1.data = malloc(t1.size);
   t1.format = GL_RGBA;
   t1.TC = GL_RGBA;

   texture1 = (GLubyte (*)[8 * 32][4])t1.data;
   for (i = 0; i < sizeof(pattern) - 1; i++) {
       switch (pattern[i]) {
              default:
              case ' ':
                   (*texture1)[i][0] = 255;
                   (*texture1)[i][1] = 255;
                   (*texture1)[i][2] = 255;
                   (*texture1)[i][3] = 64;
                   break;
              case 'M':
                   (*texture1)[i][0] = 255;
                   (*texture1)[i][1] = 0;
                   (*texture1)[i][2] = 0;
                   (*texture1)[i][3] = 255;
                   break;
              case 'E':
                   (*texture1)[i][0] = 0;
                   (*texture1)[i][1] = 255;
                   (*texture1)[i][2] = 0;
                   (*texture1)[i][3] = 255;
                   break;
              case 'S':
                   (*texture1)[i][0] = 0;
                   (*texture1)[i][1] = 0;
                   (*texture1)[i][2] = 255;
                   (*texture1)[i][3] = 255;
                   break;
              case 'A':
                   (*texture1)[i][0] = 255;
                   (*texture1)[i][1] = 255;
                   (*texture1)[i][2] = 0;
                   (*texture1)[i][3] = 255;
                   break;
       }
   }

   t2.w = 256;
   t2.h = 256;
   t2.size = t2.w * t2.h * 4;
   t2.data = malloc(t2.size);
   t2.format = GL_RGBA;
   t2.TC = GL_RGBA;

   texture2 = (GLubyte (*)[256][256][4])t2.data;
   for (j = 0; j < t2.h; j++) {
      for (i = 0; i < t2.w; i++) {
         (*texture2)[j][i][0] = sqrt(i * j * 255 * 255 / (t2.w * t2.h));
         (*texture2)[j][i][1] = 0;
         (*texture2)[j][i][2] = 0;
         (*texture2)[j][i][3] = 255;
      }
   }

   t3.data = LoadRGBImage(TEXTURE_FILE, (GLint *)&t3.w, (GLint *)&t3.h, &t3.format);
   t3.size = t3.w * t3.h * ((t3.format == GL_RGB) ? 3 : 4);
   t3.TC = GL_RGBA;

   ReInit(GL_RGBA, Tx = &t1);

   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
   glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
   glEnable(GL_TEXTURE_2D);

   glEnable(GL_BLEND);
   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}


static void Key( unsigned char key, int x, int y )
{
   (void) x;
   (void) y;
   switch (key) {
      case 27:
         exit(0);
         break;
      case ' ':
         Anim = !Anim;
         if (Anim)
            glutIdleFunc( Idle );
         else
            glutIdleFunc( NULL );
         break;
      case 't':
         if (Tx == &t1) {
            Tx = &t2;
         } else if (Tx == &t2) {
            Tx = &t3;
         } else {
            Tx = &t1;
         }
         ReInit(Tx->TC, Tx);
         break;
      case '9':
         ReInit(GL_RGB, Tx);
         break;
      case '0':
         ReInit(GL_RGBA, Tx);
         break;
      case '1':
         ReInit(GL_COMPRESSED_RGB, Tx);
         break;
      case '2':
         ReInit(GL_COMPRESSED_RGBA, Tx);
         break;
      case '3':
         if (fxt1) ReInit(GL_COMPRESSED_RGB_FXT1_3DFX, Tx);
         break;
      case '4':
         if (fxt1) ReInit(GL_COMPRESSED_RGBA_FXT1_3DFX, Tx);
         break;
      case '5':
         if (dxtc) ReInit(GL_COMPRESSED_RGB_S3TC_DXT1_EXT, Tx);
         break;
      case '6':
         if (dxtc) ReInit(GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, Tx);
         break;
      case '7':
         if (dxtc) ReInit(GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, Tx);
         break;
      case '8':
         if (dxtc) ReInit(GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, Tx);
         break;
      case 'a':
         if (s3tc) ReInit(GL_RGB_S3TC, Tx);
         break;
      case 's':
         if (s3tc) ReInit(GL_RGB4_S3TC, Tx);
         break;
      case 'd':
         if (s3tc) ReInit(GL_RGBA_S3TC, Tx);
         break;
      case 'f':
         if (s3tc) ReInit(GL_RGBA4_S3TC, Tx);
         break;
   }
   glutPostRedisplay();
}


int main( int argc, char *argv[] )
{
   float gl_version;
   GLint num_formats;
   GLint i;
   GLint formats[64];


   glutInit( &argc, argv );
   glutInitWindowPosition( 0, 0 );
   glutInitWindowSize( 400, 300 );

   glutInitDisplayMode( GLUT_RGB | GLUT_DOUBLE );

   if (glutCreateWindow(argv[0]) <= 0) {
      printf("Couldn't create window\n");
      exit(0);
   }

   gladLoadGL();
   gl_version = atof( (const char *) glGetString( GL_VERSION ) );
   if ( (gl_version < 1.3) 
	&& !glutExtensionSupported("GL_ARB_texture_compression") ) {
      printf("Sorry, GL_ARB_texture_compression not supported\n");
      exit(0);
   }
   if (glutExtensionSupported("GL_3DFX_texture_compression_FXT1")) {
      fxt1 = GL_TRUE;
   }
   if (glutExtensionSupported("GL_EXT_texture_compression_s3tc")) {
      dxtc = GL_TRUE;
   }
   if (glutExtensionSupported("GL_S3_s3tc")) {
      s3tc = GL_TRUE;
   }

   glGetIntegerv( GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB, & num_formats );
   
   (void) memset( formats, 0, sizeof( formats ) );
   glGetIntegerv( GL_COMPRESSED_TEXTURE_FORMATS_ARB, formats );

   printf( "The following texture formats are supported:\n" );
   for ( i = 0 ; i < num_formats ; i++ ) {
      printf( "\t%s\n", TextureName( formats[i] ) );
   }
	
   Init();

   glutReshapeFunc( Reshape );
   glutKeyboardFunc( Key );
   glutDisplayFunc( Display );
   if (Anim)
      glutIdleFunc( Idle );

   glutMainLoop();
   return 0;
}
