/*
 * GL_ARB_shading_language_100 test application.
 *
 * Tests correctness of emited code. Runs multiple well-formed shaders and checks if
 * they produce valid results.
 *
 * Requires specific support on the GL implementation side. A special function printMESA()
 * must be supported in the language that prints current values of generic type
 * to the appropriate shader's info log, and optionally to the screen.
 *
 * Author: Michal Krol
 */

#include "framework.h"

#define EPSILON 0.0001f

static GLhandleARB vert = 0;
static GLhandleARB prog = 0;

static int get_line (FILE *f, char *line, int size)
{
   if (fgets (line, size, f) == NULL)
      return 0;
   if (line[strlen (line) - 1] == '\n')
      line[strlen (line) - 1] = '\0';
   return 1;
}

struct ATTRIB
{
   char name[32];
   GLfloat value[64][4];
   GLuint count;
};

struct ATTRIBS
{
   struct ATTRIB attrib[32];
   GLuint count;
};

struct SHADER
{
   char code[16000];
   GLfloat output[1000];
   GLuint count;
};

enum SHADER_LOAD_STATE
{
   SLS_NONE,
   SLS_CODE,
   SLS_OUTPUT
};

struct PROGRAM
{
   struct PROGRAM *next;
   char name[256];
   struct ATTRIBS attribs;
   struct SHADER vertex;
};

enum PROGRAM_LOAD_STATE
{
   PLS_NONE,
   PLS_ATTRIB,
   PLS_VERTEX
};

static struct PROGRAM *program = NULL;

static void load_test_file (const char *filename, struct PROGRAM **program)
{
   struct PROGRAM **currprog = program;
   FILE *f;
   char line[256];
   enum PROGRAM_LOAD_STATE pls = PLS_NONE;
   enum SHADER_LOAD_STATE sls = SLS_NONE;

   f = fopen (filename, "r");
   if (f == NULL)
      return;

   while (get_line (f, line, sizeof (line))) {
      if (line[0] == '$') {
         if (strncmp (line + 1, "program", 7) == 0) {
            if (*currprog != NULL)
               currprog = &(**currprog).next;
            *currprog = (struct PROGRAM *) (malloc (sizeof (struct PROGRAM)));
            if (*currprog == NULL)
               break;
            (**currprog).next = NULL;
            strcpy ((**currprog).name, line + 9);
            (**currprog).attribs.count = 0;
            (**currprog).vertex.code[0] = '\0';
            (**currprog).vertex.count = 0;
            pls = PLS_NONE;
         }
         else if (strncmp (line + 1, "attrib", 6) == 0) {
            if (*currprog == NULL)
               break;
            strcpy ((**currprog).attribs.attrib[(**currprog).attribs.count].name, line + 8);
            (**currprog).attribs.attrib[(**currprog).attribs.count].count = 0;
            (**currprog).attribs.count++;
            pls = PLS_ATTRIB;
         }
         else if (strcmp (line + 1, "vertex") == 0) {
            if (*currprog == NULL)
               break;
            pls = PLS_VERTEX;
            sls = SLS_NONE;
         }
         else if (strcmp (line + 1, "code") == 0) {
            if (*currprog == NULL || pls != PLS_VERTEX)
               break;
            sls = SLS_CODE;
         }
         else if (strcmp (line + 1, "output") == 0) {
            if (*currprog == NULL || pls != PLS_VERTEX)
               break;
            sls = SLS_OUTPUT;
         }
      }
      else {
         if ((*currprog == NULL || pls == PLS_NONE || sls == SLS_NONE) && line[0] != '\0')
            break;
         if (*currprog != NULL && pls == PLS_VERTEX) {
            if (sls == SLS_CODE) {
               strcat ((**currprog).vertex.code, line);
               strcat ((**currprog).vertex.code, "\n");
            }
            else if (sls == SLS_OUTPUT && line[0] != '\0') {
               if (strcmp (line, "true") == 0)
                  (**currprog).vertex.output[(**currprog).vertex.count] = 1.0f;
               else if (strcmp (line, "false") == 0)
                  (**currprog).vertex.output[(**currprog).vertex.count] = 0.0f;
               else
                  sscanf (line, "%f", &(**currprog).vertex.output[(**currprog).vertex.count]);
               (**currprog).vertex.count++;
            }
         }
         else if (*currprog != NULL && pls == PLS_ATTRIB && line[0] != '\0') {
            struct ATTRIB *att = &(**currprog).attribs.attrib[(**currprog).attribs.count - 1];
            GLfloat *vec = att->value[att->count];
            sscanf (line, "%f %f %f %f", &vec[0], &vec[1], &vec[2], &vec[3]);
            att->count++;
         }
      }
   }

   fclose (f);
}

void InitScene (void)
{
   prog = glCreateProgramObjectARB ();
   vert = glCreateShaderObjectARB (GL_VERTEX_SHADER_ARB);
   glAttachObjectARB (prog, vert);
   glDeleteObjectARB (vert);
   load_test_file ("cltest.txt", &program);
}

void RenderScene (void)
{
   struct PROGRAM *nextprogram;
   char *code;
   GLint info_length, length;
   char output[65000], *p;
   GLuint i;

   if (program == NULL)
      exit (0);

   code = program->vertex.code;
   glShaderSourceARB (vert, 1, (const GLcharARB **) (&code), NULL);
   glCompileShaderARB (vert);
   CheckObjectStatus (vert);

   for (i = 0; i < program->attribs.count; i++) {
      const char *name = program->attribs.attrib[i].name;
      if (strcmp (name, "gl_Vertex") != 0)
         glBindAttribLocationARB (prog, i, name);
   }

   glLinkProgramARB (prog);
   CheckObjectStatus (prog);
   glUseProgramObjectARB (prog);

   printf ("\n--- %s\n", program->name);

   glGetObjectParameterivARB (vert, GL_OBJECT_INFO_LOG_LENGTH_ARB, &info_length);

   glBegin (GL_POINTS);
   if (program->attribs.count == 0) {
      glVertex2f (0.0f, 0.0f);
   }
   else {
      for (i = 0; i < program->attribs.attrib[0].count; i++) {
         GLuint j;
         for (j = 0; j < program->attribs.count; j++) {
            GLuint n = (j + 1) % program->attribs.count;
            GLfloat *vec = program->attribs.attrib[n].value[i];
            const char *name = program->attribs.attrib[n].name;
            if (strcmp (name, "gl_Vertex") == 0)
               glVertex4fv (vec);
            else
               glVertexAttrib4fvARB (n, vec);
         }
      }
   }
   glEnd ();
   glFlush ();

   glGetInfoLogARB (vert, sizeof (output), &length, output);
   p = output + info_length - 1;
   for (i = 0; i < program->vertex.count; i++) {
      GLfloat value;
      if (p == NULL) {
         printf ("*** %s\n", "I/O error");
         break;
      }
      if (strncmp (p, "true", 4) == 0)
         value = 1.0f;
      else if (strncmp (p, "false", 5) == 0)
         value = 0.0f;
      else if (sscanf (p, "%f", &value) != 1) {
         printf ("*** %s\n", "I/O error");
         break;
      }
      if (fabs (value - program->vertex.output[i]) > EPSILON) {
         printf ("*** Values are different, is %f, should be %f\n", value,
                 program->vertex.output[i]);
      }
      p = strchr (p, '\n');
      if (p != NULL)
         p++;
   }
   if (p && *p != '\0')
      printf ("*** %s\n", "I/O error");

   nextprogram = program->next;
   free (program);
   program = nextprogram;
}

int main (int argc, char *argv[])
{
   InitFramework (&argc, argv);
   return 0;
}