#include <assert.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <GL/glew.h>
#include <GL/glu.h>
#include "readtex.h"
#include "skybox.h"


static int
load(GLenum target, const char *filename,
     GLboolean flipTB, GLboolean flipLR)
{
   GLint w, h;
   GLenum format;
   GLubyte *img = LoadRGBImage( filename, &w, &h, &format );
   if (!img) {
      printf("Error: couldn't load texture image %s\n", filename);
      return 0;
   }
   assert(format == GL_RGB);

   printf("Load cube face 0x%x: %s %d x %d\n", target, filename, w, h);

   /* <sigh> the way the texture cube mapping works, we have to flip
    * images to make things look right.
    */
   if (flipTB) {
      const int stride = 3 * w;
      GLubyte temp[3*1024];
      int i;
      for (i = 0; i < h / 2; i++) {
         memcpy(temp, img + i * stride, stride);
         memcpy(img + i * stride, img + (h - i - 1) * stride, stride);
         memcpy(img + (h - i - 1) * stride, temp, stride);
      }
   }
   if (flipLR) {
      const int stride = 3 * w;
      GLubyte temp[3];
      GLubyte *row;
      int i, j;
      for (i = 0; i < h; i++) {
         row = img + i * stride;
         for (j = 0; j < w / 2; j++) {
            int k = w - j - 1;
            temp[0] = row[j*3+0];
            temp[1] = row[j*3+1];
            temp[2] = row[j*3+2];
            row[j*3+0] = row[k*3+0];
            row[j*3+1] = row[k*3+1];
            row[j*3+2] = row[k*3+2];
            row[k*3+0] = temp[0];
            row[k*3+1] = temp[1];
            row[k*3+2] = temp[2];
         }
      }
   }

   gluBuild2DMipmaps(target, GL_RGB, w, h, format, GL_UNSIGNED_BYTE, img);
   free(img);
   return 1;
}


GLuint
LoadSkyBoxCubeTexture(const char *filePosX,
                      const char *fileNegX,
                      const char *filePosY,
                      const char *fileNegY,
                      const char *filePosZ,
                      const char *fileNegZ)
{
   GLuint tex;

   glGenTextures(1, &tex);
   glBindTexture(GL_TEXTURE_CUBE_MAP, tex);

   glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
   glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER,
                   GL_LINEAR_MIPMAP_NEAREST);

   if (!load(GL_TEXTURE_CUBE_MAP_POSITIVE_X, filePosX, GL_TRUE, GL_TRUE))
      return 0;
   if (!load(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, fileNegX, GL_TRUE, GL_TRUE))
      return 0;
   if (!load(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, filePosY, 1+GL_FALSE, GL_TRUE))
      return 0;
   if (!load(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, fileNegY, 1+GL_FALSE, GL_TRUE))
      return 0;
   if (!load(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, filePosZ, GL_TRUE, GL_TRUE))
      return 0;
   if (!load(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, fileNegZ, GL_TRUE, GL_TRUE))
      return 0;

   return tex;
}


#define eps1 0.99
#define br   20.0  /* box radius */

void
DrawSkyBoxCubeTexture(GLuint tex)
{
   struct vertex {
      float x, y, z, s, t, r;
   };

   static const struct vertex verts[24] = {
      /* +X side */
      { br, -br, -br,  1.0, -eps1, -eps1 },
      { br, -br,  br,  1.0, -eps1,  eps1 },
      { br,  br,  br,  1.0,  eps1,  eps1 },
      { br,  br, -br,  1.0,  eps1, -eps1 },

      /* -X side */
      { -br,  br, -br,  -1.0,  eps1, -eps1 },
      { -br,  br,  br,  -1.0,  eps1,  eps1 },
      { -br, -br,  br,  -1.0, -eps1,  eps1 },
      { -br, -br, -br,  -1.0, -eps1, -eps1 },

      /* +Y side */
      {  br,  br, -br,   eps1, 1.0, -eps1 },
      {  br,  br,  br,   eps1, 1.0,  eps1 },
      { -br,  br,  br,  -eps1, 1.0,  eps1 },
      { -br,  br, -br,  -eps1, 1.0, -eps1 },

      /* -Y side */
      { -br, -br, -br,  -eps1, -1.0, -eps1 },
      { -br, -br,  br,  -eps1, -1.0,  eps1 },
      {  br, -br,  br,   eps1, -1.0,  eps1 },
      {  br, -br, -br,   eps1, -1.0, -eps1 },

      /* +Z side */
      {  br, -br, br,   eps1, -eps1, 1.0 },
      { -br, -br, br,  -eps1, -eps1, 1.0 },
      { -br,  br, br,  -eps1,  eps1, 1.0 },
      {  br,  br, br,   eps1,  eps1, 1.0 },

      /* -Z side */
      {  br,  br, -br,   eps1,  eps1, -1.0 },
      { -br,  br, -br,  -eps1,  eps1, -1.0 },
      { -br, -br, -br,  -eps1, -eps1, -1.0 },
      {  br, -br, -br,   eps1, -eps1, -1.0 },
   };

   static GLuint vbo = 0;

   if (!vbo ) {
      glGenBuffersARB(1, &vbo);
      glBindBufferARB(GL_ARRAY_BUFFER_ARB, vbo);
      glBufferDataARB(GL_ARRAY_BUFFER_ARB, sizeof(verts), verts,
                      GL_STATIC_DRAW_ARB);
   }
   else {
      glBindBufferARB(GL_ARRAY_BUFFER_ARB, vbo);
   }

   glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

   glVertexPointer(3, GL_FLOAT, sizeof(struct vertex),
                   (void *) offsetof(struct vertex, x));
   glTexCoordPointer(3, GL_FLOAT, sizeof(struct vertex),
                     (void *) offsetof(struct vertex, s));
   glEnableClientState(GL_VERTEX_ARRAY);
   glEnableClientState(GL_TEXTURE_COORD_ARRAY);

   glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
   glEnable(GL_TEXTURE_CUBE_MAP);

   glDisable(GL_BLEND);

   glDrawArrays(GL_QUADS, 0, 24);

   glDisable(GL_TEXTURE_CUBE_MAP);

   glDisableClientState(GL_VERTEX_ARRAY);
   glDisableClientState(GL_TEXTURE_COORD_ARRAY);

   glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
}