/**
 * Random rendering, to check for crashes, hangs, etc.
 *
 * Brian Paul
 * 21 June 2007
 */


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

static int Win;
static GLboolean Anim = GL_TRUE;
static int Width = 200, Height = 200;
static int DB = 0;
static int MinVertexCount = 0, MaxVertexCount = 1000;
static int Count = 0;

struct vertex
{
   int type;
   float v[4];
};

static int BufferSize = 10000;
static struct vertex *Vbuffer = NULL;
static int Vcount, Vprim;

enum {
   BEGIN,
   END,
   VERTEX2,
   VERTEX3,
   VERTEX4,
   COLOR3,
   COLOR4,
   TEX2,
   TEX3,
   TEX4,
   SECCOLOR3,
   NORMAL3
};



/**
 * This can be called from within gdb after a crash:
 * (gdb) call ReportState()
 */
static void
ReportState(void)
{
   static const struct {
      GLenum token;
      char *str;
      GLenum type;
   } state [] = {
      { GL_ALPHA_TEST, "GL_ALPHA_TEST", GL_INT },
      { GL_BLEND, "GL_BLEND", GL_INT },
      { GL_CLIP_PLANE0, "GL_CLIP_PLANE0", GL_INT },
      { GL_DEPTH_TEST, "GL_DEPTH_TEST", GL_INT },
      { GL_LIGHTING, "GL_LIGHTING", GL_INT },
      { GL_LINE_WIDTH, "GL_LINE_WIDTH", GL_FLOAT },
      { GL_POINT_SIZE, "GL_POINT_SIZE", GL_FLOAT },
      { GL_SHADE_MODEL, "GL_SHADE_MODEL", GL_INT },
      { GL_SCISSOR_TEST, "GL_SCISSOR_TEST", GL_INT },
      { 0, NULL, 0 }
   };

   GLint i;

   for (i = 0; state[i].token; i++) {
      if (state[i].type == GL_INT) {
         GLint v;
         glGetIntegerv(state[i].token, &v);
         printf("%s = %d\n", state[i].str, v);
      }
      else {
         GLfloat v;
         glGetFloatv(state[i].token, &v);
         printf("%s = %f\n", state[i].str, v);
      }
   }
}

static void
PrintVertex(const char *f, const struct vertex *v, int sz)
{
   int i;
   printf("%s(", f);
   for (i = 0; i < sz; i++) {
      printf("%g%s", v->v[i], (i == sz-1) ? "" : ", ");
   }
   printf(");\n");
}

/**
 * This can be called from within gdb after a crash:
 * (gdb) call ReportState()
 */
static void
LastPrim(void)
{
   int i;
   for (i = 0; i < Vcount; i++) {
      switch (Vbuffer[i].type) {
      case BEGIN:
         printf("glBegin(%d);\n", (int) Vbuffer[i].v[0]);
         break;
      case END:
         printf("glEnd();\n");
         break;
      case VERTEX2:
         PrintVertex("glVertex2f", Vbuffer + i, 2);
         break;
      case VERTEX3:
         PrintVertex("glVertex3f", Vbuffer + i, 3);
         break;
      case VERTEX4:
         PrintVertex("glVertex4f", Vbuffer + i, 4);
         break;
      case COLOR3:
         PrintVertex("glColor3f", Vbuffer + i, 3);
         break;
      case COLOR4:
         PrintVertex("glColor4f", Vbuffer + i, 4);
         break;
      case TEX2:
         PrintVertex("glTexCoord2f", Vbuffer + i, 2);
         break;
      case TEX3:
         PrintVertex("glTexCoord3f", Vbuffer + i, 3);
         break;
      case TEX4:
         PrintVertex("glTexCoord4f", Vbuffer + i, 4);
         break;
      case SECCOLOR3:
         PrintVertex("glSecondaryColor3f", Vbuffer + i, 3);
         break;
      case NORMAL3:
         PrintVertex("glNormal3f", Vbuffer + i, 3);
         break;
      default:
         abort();
      }
   }
}


static int
RandomInt(int max)
{
   if (max == 0)
      return 0;
   return rand() % max;
}

static float
RandomFloat(float min, float max)
{
   int k = rand() % 10000;
   float x = min + (max - min) * k / 10000.0;
   return x;
}

/*
 * Return true if random number in [0,1] is <= percentile.
 */
static GLboolean
RandomChoice(float percentile)
{
   return RandomFloat(0.0, 1.0) <= percentile;
}

static void
RandomStateChange(void)
{
   int k = RandomInt(19);
   switch (k) {
   case 0:
      glEnable(GL_BLEND);
      break;
   case 1:
      glDisable(GL_BLEND);
      break;
   case 2:
      glEnable(GL_ALPHA_TEST);
      break;
   case 3:
      glEnable(GL_ALPHA_TEST);
      break;
   case 4:
      glEnable(GL_DEPTH_TEST);
      break;
   case 5:
      glEnable(GL_DEPTH_TEST);
      break;
   case 6:
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      break;
   case 7:
      glPointSize(10.0);
      break;
   case 8:
      glPointSize(1.0);
      break;
   case 9:
      glLineWidth(10.0);
      break;
   case 10:
      glLineWidth(1.0);
      break;
   case 11:
      glEnable(GL_LIGHTING);
      break;
   case 12:
      glDisable(GL_LIGHTING);
      break;
   case 13:
      glEnable(GL_SCISSOR_TEST);
      break;
   case 14:
      glDisable(GL_SCISSOR_TEST);
      break;
   case 15:
      glEnable(GL_CLIP_PLANE0);
      break;
   case 16:
      glDisable(GL_CLIP_PLANE0);
      break;
   case 17:
      glShadeModel(GL_FLAT);
      break;
   case 18:
      glShadeModel(GL_SMOOTH);
      break;
   }
}


static void
RandomPrimitive(void)
{
   int i;
   int len = MinVertexCount + RandomInt(MaxVertexCount - MinVertexCount);

   Vprim = RandomInt(10);

   glBegin(Vprim);
   Vbuffer[Vcount].type = BEGIN;
   Vbuffer[Vcount].v[0] = Vprim;
   Vcount++;

   for (i = 0; i < len; i++) {
      int k = RandomInt(9);
      Vbuffer[Vcount].v[0] = RandomFloat(-3, 3);
      Vbuffer[Vcount].v[1] = RandomFloat(-3, 3);
      Vbuffer[Vcount].v[2] = RandomFloat(-3, 3);
      Vbuffer[Vcount].v[3] = RandomFloat(-3, 3);
      switch (k) {
      case 0:
         glVertex2fv(Vbuffer[Vcount].v);
         Vbuffer[Vcount].type = VERTEX2;
         break;
      case 1:
         glVertex3fv(Vbuffer[Vcount].v);
         Vbuffer[Vcount].type = VERTEX3;
         break;
      case 2:
         glVertex4fv(Vbuffer[Vcount].v);
         Vbuffer[Vcount].type = VERTEX4;
         break;
      case 3:
         glColor3fv(Vbuffer[Vcount].v);
         Vbuffer[Vcount].type = COLOR3;
         break;
      case 4:
         glColor4fv(Vbuffer[Vcount].v);
         Vbuffer[Vcount].type = COLOR4;
         break;
      case 5:
         glTexCoord2fv(Vbuffer[Vcount].v);
         Vbuffer[Vcount].type = TEX2;
         break;
      case 6:
         glTexCoord3fv(Vbuffer[Vcount].v);
         Vbuffer[Vcount].type = TEX3;
         break;
      case 7:
         glTexCoord4fv(Vbuffer[Vcount].v);
         Vbuffer[Vcount].type = TEX4;
         break;
      case 8:
         glSecondaryColor3fv(Vbuffer[Vcount].v);
         Vbuffer[Vcount].type = SECCOLOR3;
         break;
      case 9:
         glNormal3fv(Vbuffer[Vcount].v);
         Vbuffer[Vcount].type = NORMAL3;
         break;
      default:
         abort();
      }
      Vcount++;

      if (Vcount >= BufferSize - 2) {
         /* reset */
         Vcount = 0;
      }
   }

   Vbuffer[Vcount++].type = END;

   glEnd();
}


static void
RandomDraw(void)
{
   int i;
   GLboolean dlist = RandomChoice(0.1);
   if (dlist)
      glNewList(1, GL_COMPILE);
   for (i = 0; i < 3; i++) {
      RandomStateChange();
   }
   RandomPrimitive();

   if (dlist) {
      glEndList();
      glCallList(1);
   }
}


static void
Idle(void)
{
   glutPostRedisplay();
}


static void
Draw(void)
{
#if 1
   RandomDraw();
   Count++;
#else
   /* cut & paste temp code here */
#endif

   assert(glGetError() == 0);

   if (DB)
      glutSwapBuffers();
   else
      glFinish();
}


static void
Reshape(int width, int height)
{
   Width = width;
   Height = height;
   glViewport(0, 0, width, height);
   glScissor(20, 20, Width-40, Height-40);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   glFrustum(-1.0, 1.0, -1.0, 1.0, 5.0, 25.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
   glTranslatef(0.0, 0.0, -15.0);
}


static void
Key(unsigned char key, int x, int y)
{
   (void) x;
   (void) y;
   switch (key) {
      case 27:
         glutDestroyWindow(Win);
         exit(0);
         break;
   }
   glutPostRedisplay();
}


static void
Init(void)
{
   static const GLdouble plane[4] = {1, 1, 0, 0};
   glDrawBuffer(GL_FRONT);
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   glEnable(GL_LIGHT0);
   glClipPlane(GL_CLIP_PLANE0, plane);

   Vbuffer = (struct vertex *)
      malloc(BufferSize * sizeof(struct vertex));

   /* silence warnings */
   (void) ReportState;
   (void) LastPrim;
}


static void
ParseArgs(int argc, char *argv[])
{
   int i;
   for (i = 1; i < argc; i++) {
      if (strcmp(argv[i], "-s") == 0) {
         int j = atoi(argv[i + 1]);
         printf("Random seed value: %d\n", j);
         srand(j);
         i++;
      }
      else if (strcmp(argv[i], "-a") == 0) {
         i++;
         MinVertexCount = atoi(argv[i]);
      }
      else if (strcmp(argv[i], "-b") == 0) {
         i++;
         MaxVertexCount = atoi(argv[i]);
      }
   }
}


int
main(int argc, char *argv[])
{
   glutInit(&argc, argv);
   glutInitWindowPosition(0, 0);
   glutInitWindowSize(Width, Height);
   glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
   Win = glutCreateWindow(argv[0]);
   gladLoadGL();
   ParseArgs(argc, argv);
   glutReshapeFunc(Reshape);
   glutKeyboardFunc(Key);
   glutDisplayFunc(Draw);
   if (Anim)
      glutIdleFunc(Idle);
   Init();
   glutMainLoop();
   return 0;
}
