/// LSU EE 7700-1 (Sp 2009), Graphics Processors  -*- c++ -*-
//
 ///  CPU-Only Demos' Include File

 // $Id:$

 /// Purpose

//  Frame buffer simulation class and support functions.


#ifndef FRAME_BUFFER_H
#define FRAME_BUFFER_H

#define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#include <GL/glext.h>
#include <GL/freeglut.h>
#include <stdarg.h>
#include <deque>
#include <string>
#ifdef MAGICK
#include <Magick++.h>
#endif

// Rename keys so a single namespace can be used for regular (ASCII)
// keys and "special" ones.

#define FB_KEY_F1         ( GLUT_KEY_F1          + 0x100 )
#define FB_KEY_F2         ( GLUT_KEY_F2          + 0x100 )
#define FB_KEY_F3         ( GLUT_KEY_F3          + 0x100 )
#define FB_KEY_F4         ( GLUT_KEY_F4          + 0x100 )
#define FB_KEY_F5         ( GLUT_KEY_F5          + 0x100 )
#define FB_KEY_F6         ( GLUT_KEY_F6          + 0x100 )
#define FB_KEY_F7         ( GLUT_KEY_F7          + 0x100 )
#define FB_KEY_F8         ( GLUT_KEY_F8          + 0x100 )
#define FB_KEY_F9         ( GLUT_KEY_F9          + 0x100 )
#define FB_KEY_F10        ( GLUT_KEY_F10         + 0x100 )
#define FB_KEY_F11        ( GLUT_KEY_F11         + 0x100 )
#define FB_KEY_F12        ( GLUT_KEY_F12         + 0x100 )
#define FB_KEY_LEFT       ( GLUT_KEY_LEFT        + 0x100 )
#define FB_KEY_UP         ( GLUT_KEY_UP          + 0x100 )
#define FB_KEY_RIGHT      ( GLUT_KEY_RIGHT       + 0x100 )
#define FB_KEY_DOWN       ( GLUT_KEY_DOWN        + 0x100 )
#define FB_KEY_PAGE_UP    ( GLUT_KEY_PAGE_UP     + 0x100 )
#define FB_KEY_PAGE_DOWN  ( GLUT_KEY_PAGE_DOWN   + 0x100 )
#define FB_KEY_HOME       ( GLUT_KEY_HOME        + 0x100 )
#define FB_KEY_END        ( GLUT_KEY_END         + 0x100 )
#define FB_KEY_INSERT     ( GLUT_KEY_INSERT      + 0x100 )
#define FB_KEY_DELETE     127

typedef int int32_t;

class pFrame_Buffer;
pFrame_Buffer* frame_buffer_self_ = NULL;

// Print OpenGL string attribute corresponding to token.
//
#define FB_GL_PRINT_STRING(token)                        \
{ const char* const str = (char*) glGetString(token); \
  printf("S %s - ",#token); \
  if ( str ) printf("\"%s\"\n",str); else printf("not available.\n"); \
}

// Return number of seconds since 1970 UTC.
//
double
time_wall_fp()
{
  struct timespec now;
  clock_gettime(CLOCK_REALTIME,&now);
  return now.tv_sec + ((double)now.tv_nsec) * 1e-9;
}

// Basic Frame Buffer Simulation
//
class pFrame_Buffer {
public:
  pFrame_Buffer(int& argc, char** argv)
  {
    frame_buffer_self_ = this;
    width = height = 0;
    buffer = (int32_t*) malloc(0);
    cb_keyboard();
    init_gl(argc, argv);
  }
  ~pFrame_Buffer(){ delete buffer; }

  // Use DISPLAY_FUNC to write frame buffer.
  //
  void show(void (*display_func)(pFrame_Buffer& fb))
  {
    user_display_func = display_func;
    glutDisplayFunc(&cb_display_w);
    glutKeyboardFunc(&cb_keyboard_w);
    glutSpecialFunc(&cb_keyboard_special_w);
    glutMainLoop();
  }

  // Return width and height of frame buffer.
  //
  int get_width() { return width; }
  int get_height() { return height; }

  // Key pressed by user since last call of DISPLAY_FUNC.
  // ASCII value, one of the FB_KEY_XXXX macros below, or 0 if
  // no key pressed.
  //
  int keyboard_key;
  int keyboard_x, keyboard_y;  // Mouse location when key pressed.


  // Return address of frame buffer.
  //
  int32_t* get_buffer() { return buffer; }

  // Print text in frame buffer, starting at upper left.
  // Arguments same as printf.
  //
  void fbprintf(const char* fmt,...)
  {
    va_list ap;
    va_start(ap,fmt);
    const int size = vsnprintf(NULL,0,fmt,ap);
    va_end(ap);
    char* const buffer = (char*) malloc(size+1);
    va_start(ap,fmt);
    vsprintf(buffer,fmt,ap);
    va_end(ap);
    print_list.push_back(buffer);
  }

  // Tell frame buffer object that this is when DISPLAY_FUNC will
  // start rendering computations. Should be placed where application-related
  // code ends and rendering pipeline code starts.
  //
  void render_timing_start()
  {
    render_start = time_wall_fp();
  }

private:
  void init_gl(int& argc, char** argv)
  {
    exe_file_name = argv && argv[0] ? argv[0] : "unknown name";
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE );
    glutInitWindowSize(640,480+status_height);

    std::string title("Frame Buffer Simulator - " + exe_file_name);

    glut_window_id = glutCreateWindow(title.c_str());

  }

  static void cb_display_w(void){ frame_buffer_self_->cb_display(); }
  void cb_display(void)
  {
    if ( keyboard_key == FB_KEY_F12 ) write_img();
    frame_buffer_allocate();
    reset(0xff000000);
    glClearColor(0.5,0.5,0.5,1);
    glClear(GL_COLOR_BUFFER_BIT);
    const double frame_start = time_wall_fp();
    render_timing_start(); // User may call this in display func.
    user_display_func(*this);
    const double end_time = time_wall_fp();
    const double elapsed_time = end_time - frame_start;
    const double render_elapsed_time = end_time - render_start;
    glWindowPos2i(10,height+status_height-20);
    fbprintf
      ("Size %dx%d,  Render Time %.3f ms,  Frame Time %.3f ms,  "
       "Potential Frame Rate %.1f\n",
       width, height,
       render_elapsed_time * 1000, elapsed_time * 1000, 1.0 / elapsed_time);
    char* const str = print_list.back();
    glutBitmapString(GLUT_BITMAP_HELVETICA_12,(unsigned char*)str);
    free(str);
    print_list.pop_back();

    glWindowPos2i(0,0);
    glPixelZoom(1,1);
    glDrawPixels(width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
    glWindowPos2i(10,height-20);

    for ( ; !print_list.empty(); print_list.pop_front() )
      {
        char* const str = print_list.front();
        glutBitmapString(GLUT_BITMAP_HELVETICA_12,(unsigned char*)str);
        free(str);
      }

    cb_keyboard();
    glutSwapBuffers();
  }

  void frame_buffer_allocate()
  {
    const int width_new = glutGet(GLUT_WINDOW_WIDTH);
    const int height_new = glutGet(GLUT_WINDOW_HEIGHT) - status_height;
    if ( height_new < 1 ) return;
    if ( width_new == width && height_new == height ) return;
    const int new_amt = width_new * height_new * sizeof(*buffer) ;
    buffer = (int32_t*) realloc(buffer,new_amt);
    width = width_new;
    height = height_new;
  }

  void reset(int32_t color)
  {
    const int size = width * height;
    for ( int i=0; i<size; i++ ) buffer[i] = color;
  }

  static void cb_keyboard_w(unsigned char key, int x, int y)
  {frame_buffer_self_->cb_keyboard(key,x,y);}
  static void cb_keyboard_special_w(int key, int x, int y)
  {frame_buffer_self_->cb_keyboard(key+0x100,x,y);}
  void cb_keyboard(int key=0, int x=0, int y=0)
  {
    keyboard_key = key;
    keyboard_x = x;
    keyboard_y = y;
    if ( !key ) return;
    glutPostRedisplay();
  }

  void write_img()
  {
    if ( !buffer ) return;
#ifndef MAGICK
    fbprintf("Cannot write image without ImageMagick library.\n");
#else
    std::string image_file_name(exe_file_name + ".png");
    const int width_raw = glutGet(GLUT_WINDOW_WIDTH);
    const int width = width_raw & ~0x3;  // Don't want to deal with padding.
    const int height = glutGet(GLUT_WINDOW_HEIGHT);
    glReadBuffer(GL_FRONT);
    const int size = width_raw * height;  // Note: width might work too.
    char* const pbuffer = (char*) malloc(size * 3);
    glReadPixels(0,0,width,height,GL_RGB,GL_UNSIGNED_BYTE,pbuffer);
    Magick::Image image(width,height,"RGB",Magick::CharPixel,pbuffer);
    image.flip();
    image.write(image_file_name);
    free(pbuffer);
    fbprintf("***  Wrote screenshot to %s  ***\n",image_file_name.c_str());
#endif
  }

private:
  static const int status_height = 26;
  std::string exe_file_name;
  double render_start;
  int width;
  int height;  // Height of simulated frame buffer, not displayed window.
  int32_t *buffer;
  int glut_window_id;
  void (*user_display_func)(pFrame_Buffer& frame_buffer);
  std::deque<char*> print_list;
};

#endif