#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include "SDL/SDL.h"
#include <OpenEXR/half.h>

SDL_Surface *screen;

enum Format { RGBA8888, RGB888, GRAYSCALE };

Format format;

bool rgba = true;

struct RGB
{
  unsigned int r;
  unsigned int g;
  unsigned int b;

  RGB() {}

  RGB(unsigned short color) 
    : r((color & 0x1F) << 3),
      g(((color & 0x7E0) >> 5) << 2),
      b(((color & 0xF800) >> 11) << 3)
  {
  }

  RGB& operator=(unsigned short color)
  {
    r = (color & 0x1F) << 3;
    g = ((color & 0x7E0) >> 5) << 2;
    b = ((color & 0xF800) >> 11) << 3;

    return *this;
  }
};

RGB operator*(unsigned int s, const RGB& a)
{
  RGB result;
  result.r = a.r * s;
  result.g = a.g * s;
  result.b = a.b * s;
  return result;
}

RGB operator*(const RGB& a, unsigned int s)
{
  RGB result;
  result.r = a.r * s;
  result.g = a.g * s;
  result.b = a.b * s;
  return result;
}

RGB operator/(const RGB& a, unsigned int s)
{
  RGB result;
  result.r = a.r / s;
  result.g = a.g / s;
  result.b = a.b / s;
  return result;
}

RGB operator+(const RGB& a, const RGB& b)
{
  RGB result;
  result.r = a.r + b.r;
  result.g = a.g + b.g;
  result.b = a.b + b.b;
  return result;
}

namespace Math {

template<class T> 
T min (const T& a, const T& b) 
{
  if (a < b)
    return a;
  else
    return b;
}

template<class T> 
T max (const T& a, const T& b) 
{
  if (a > b)
    return a;
  else
    return b;
}

template<class T> 
T mid (const T& a, const T& b, const T& c) 
{
  return max<T>((a), min<T>((b), (c)));
}

} // namespace Math

void render(char* data, int width, int offset)
{   
  if (SDL_MUSTLOCK(screen)) if (SDL_LockSurface(screen) < 0) return;

  if (rgba)
    {
      for (int y = 0; y < 768; ++y)
        for (int x = 0; x < width && x < 1024; ++x)
          {
            ((unsigned char*)screen->pixels)[4*(y * 1024 + x) + 0] = data[offset + 4*(y * width + x) + 0];
            ((unsigned char*)screen->pixels)[4*(y * 1024 + x) + 1] = data[offset + 4*(y * width + x) + 1];
            ((unsigned char*)screen->pixels)[4*(y * 1024 + x) + 2] = data[offset + 4*(y * width + x) + 2];
            ((unsigned char*)screen->pixels)[4*(y * 1024 + x) + 3] = data[offset + 4*(y * width + x) + 3];
          }
    }
  else
    {
      for (int y = 0; y < 768; ++y) // /4 if S3TC is used
        for (int x = 0; x < width && x < 1024; ++x)
          {
            if (0)
              {
                ((unsigned char*)screen->pixels)[4*(y * 1024 + x) + 0] = data[offset + 3*(y * width + x) + 0];
                ((unsigned char*)screen->pixels)[4*(y * 1024 + x) + 1] = data[offset + 3*(y * width + x) + 1];
                ((unsigned char*)screen->pixels)[4*(y * 1024 + x) + 2] = data[offset + 3*(y * width + x) + 2];
                ((unsigned char*)screen->pixels)[4*(y * 1024 + x) + 3] = 0;
              }
            else if (0)
              {
                ((unsigned char*)screen->pixels)[4*(y * 1024 + x) + 0] = data[offset + (y * width + x)];
                ((unsigned char*)screen->pixels)[4*(y * 1024 + x) + 1] = data[offset + (y * width + x)];
                ((unsigned char*)screen->pixels)[4*(y * 1024 + x) + 2] = data[offset + (y * width + x)];
                ((unsigned char*)screen->pixels)[4*(y * 1024 + x) + 3] = 0;
              }
            else if (1)
              { // 16bit
                unsigned short p = *(unsigned short*)(data + offset + 8*(y * width + x));
                
                ((unsigned char*)screen->pixels)[4*(y * 1024 + x) + 0] = (p & 0x1F) << 3;
                ((unsigned char*)screen->pixels)[4*(y * 1024 + x) + 1] = (p & 0x7E0) >> 3;
                ((unsigned char*)screen->pixels)[4*(y * 1024 + x) + 2] = (p & 0xF800) >> 8;
                ((unsigned char*)screen->pixels)[4*(y * 1024 + x) + 3] = 0;
              }
            else if (0)
              { // ???
                unsigned int p = *((unsigned int*)(data + offset + 8*(y * width + x) + 0)) & 0x3FF >> 2;
                
                ((unsigned char*)screen->pixels)[4*(y * 1024 + x) + 0] = p;
                ((unsigned char*)screen->pixels)[4*(y * 1024 + x) + 1] = p;
                ((unsigned char*)screen->pixels)[4*(y * 1024 + x) + 2] = p; 
                ((unsigned char*)screen->pixels)[4*(y * 1024 + x) + 3] = p;
              }
            else if (0)
              {
                half r, g, b, a;
                
                r.setBits(*(unsigned short*)(data + offset + 8*(y * width + x) + 0));
                g.setBits(*(unsigned short*)(data + offset + 8*(y * width + x) + 2));
                b.setBits(*(unsigned short*)(data + offset + 8*(y * width + x) + 4));
                a.setBits(*(unsigned short*)(data + offset + 8*(y * width + x) + 6));

                ((unsigned char*)screen->pixels)[4*(y * 1024 + x) + 0] = static_cast<unsigned char>(Math::mid(0.0f, (float)r, 1.0f) * 255);
                ((unsigned char*)screen->pixels)[4*(y * 1024 + x) + 1] = static_cast<unsigned char>(Math::mid(0.0f, (float)g, 1.0f) * 255);
                ((unsigned char*)screen->pixels)[4*(y * 1024 + x) + 2] = static_cast<unsigned char>(Math::mid(0.0f, (float)b, 1.0f) * 255);
                ((unsigned char*)screen->pixels)[4*(y * 1024 + x) + 3] = static_cast<unsigned char>(Math::mid(0.0f, (float)a, 1.0f) * 255);
              }
            else 
              { 
                // See: http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_compression_s3tc.txt

                // COMPRESSED_RGB_S3TC_DXT1_EXT
                unsigned char c0_lo, c0_hi, c1_lo, c1_hi, bits_0, bits_1, bits_2, bits_3;

                c0_lo  = data[offset + 8*(y * width + x) + 0];
                c0_hi  = data[offset + 8*(y * width + x) + 1];
                c1_lo  = data[offset + 8*(y * width + x) + 2];
                c1_hi  = data[offset + 8*(y * width + x) + 3];
                bits_0 = data[offset + 8*(y * width + x) + 4];
                bits_1 = data[offset + 8*(y * width + x) + 5];
                bits_2 = data[offset + 8*(y * width + x) + 6];
                bits_3 = data[offset + 8*(y * width + x) + 7];

                unsigned short color0 = c0_lo | (c0_hi << 8);
                unsigned short color1 = c1_lo | (c1_hi << 8);

                RGB rgb0 = color0;
                RGB rgb1 = color1;
                unsigned int bits = bits_0 + 256 * (bits_1 + 256 * (bits_2 + 256 * bits_3));

                for(int y1 = 0; y1 < 4; ++y1)
                  for(int x1 = 0; x1 < 4; ++x1)
                    {
                      //code(x,y) = bits[2*(4*y+x)+1..2*(4*y+x)+0]
                      //unsigned int code = (bits & (0x3 << (y1*4+x1))) >> (y1*4+x1);
                      unsigned int code = ((bits >> (2*(4*y1+x1))) & 0x3);
                        
                      RGB rgb;

                      if (color0 > color1 && code == 0)
                        rgb = rgb0;
                      else if (color0 > color1 && code == 1)
                        rgb = rgb1;
                      else if (color0 > color1 && code == 2)
                        rgb = (2*rgb0 + rgb1)/3;
                      else if (color0 > color1 && code == 3)
                        rgb = (rgb1 + 2*rgb1)/3;
                      else if (color0 <= color1 && code == 0)
                        rgb = rgb0;
                      else if (color0 <= color1 && code == 1)
                        rgb = rgb1;
                      else if (color0 <= color1 && code == 2)
                        rgb = (rgb0+rgb1)/2;
                      else if (color0 <= color1 && code == 3)
                        rgb = 0;
                      else
                        assert(0);
                    
                      ((unsigned char*)screen->pixels)[4*((4*y + y1) * 1024 + 4*x + x1) + 0] = rgb.r;
                      ((unsigned char*)screen->pixels)[4*((4*y + y1) * 1024 + 4*x + x1) + 1] = rgb.g;
                      ((unsigned char*)screen->pixels)[4*((4*y + y1) * 1024 + 4*x + x1) + 2] = rgb.b;
                      ((unsigned char*)screen->pixels)[4*((4*y + y1) * 1024 + 4*x + x1) + 3] = 0;
                    }
              }
          }
    }

  if (SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen);

  SDL_UpdateRect(screen, 0, 0, 1024, 768); 
}


// Entry point
int main(int argc, char *argv[])
{
  if ( SDL_Init(SDL_INIT_VIDEO) < 0 ) 
    {
      fprintf(stderr, "Unable to init SDL: %s\n", SDL_GetError());
      exit(1);
    }

  atexit(SDL_Quit);
    
  screen = SDL_SetVideoMode(1024, 768, 32, SDL_SWSURFACE);
  
  // If we fail, return error.
  if ( screen == NULL ) 
    {
      fprintf(stderr, "Unable to set 640x480 video: %s\n", SDL_GetError());
      exit(1);
    }

  int width = 256;
  int offset = 0;
  int filesize = 0;
  int blocksize = 4096;
  char* data = 0;
  
  std::cout << "OK" << std::endl;
  FILE* in = fopen(argv[1], "r");

  std::cout << "OK" << std::endl;
  
  int res;
  do {
    //std::cout << filesize << std::endl;
    data = (char*)realloc(data, filesize + blocksize);
    res = fread(data + filesize, 1, blocksize, in);
    filesize += res;
  } while (res > 0);

  std::cout << "Filesize: " << filesize << std::endl;
  fclose(in);

  int rotation = 0;
  bool rotate = true;
  // Main loop: loop forever.
  while (1)
    {
      rotation = ((rotation + 1) % 8);

      if (rotate)
        render(data, width, offset + rotation);
      else
        render(data, width, offset);

      SDL_Event event;
      while (SDL_PollEvent(&event)) 
        {
          switch (event.type) 
            {
            case SDL_KEYDOWN:
              break;

            case SDL_KEYUP:
              switch(event.key.keysym.sym)
                {
                case SDLK_SPACE:
                  rotate = !rotate;
                  break;

                case SDLK_r:
                  rgba = !rgba;
                  std::cout << "RGBA: " << rgba << std::endl;
                  break;

                case SDLK_l:
                  std::cout << "New Width: " << std::flush;
                  std::cin >> width;
                  break;

                case SDLK_t:
                  width -= 1;
                  break;
                  
                case SDLK_n:
                  width += 1;
                  break;


                case SDLK_p:
                  width -= 32;
                  break;
                  
                case SDLK_y:
                  width += 32;
                  break;

                case SDLK_f:
                  offset -= width*768 * 3;
                  break;

                case SDLK_d:
                  offset += width*768 * 3;
                  break;
                  
                case SDLK_i:
                  offset += 512*512;
                  break;

                case SDLK_u:
                  offset -= 512*512;
                  break;

                case SDLK_a:
                  offset -= 1;
                  break;
                case SDLK_o:
                  offset += 1;
                  break;

                default:
                  break;

                }
              std ::cout << "Width: " << width << " Offset: " << offset << std::endl;


              if (event.key.keysym.sym == SDLK_ESCAPE)
                return 0;
              break;

            case SDL_QUIT:
              return(0);
            }
        }
    }
  return 0;
}

/* EOF */

