// cc -g -o objdraw -I/home/gtoal/src/battlezone2-code/GLEW/include/ objdraw.c -lGL -lglfw -lGLEW

// A rather poor skeleton for this program was first created by Google AI, but was missing the majority
// of fields :-/  What you see now has little resemblance to what was proposed by Google...

// Test .obj files came from Battlezone2 at /home/gtoal/src/battlezone2-code/models
// Building requires: sudo apt-get install libglfw3-dev libglew-dev (and possibly libglm-dev?)

// For objects to be used in the Vectrex world, we only need a subset of the features of .obj files.
// Other features may be parsed but not used.  External files will not be imported.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h> // just isblank()!

#include <GL/glew.h>
#include <GLFW/glfw3.h>

#define TRUE (0==0)
#define FALSE (0!=0)

typedef struct {
  float x, y, z, w;
} Vertex;

typedef struct {
  float x, y, z;
} Normal;

typedef struct {
  float u, v, w;
} Texture;

typedef struct {
  float u, v, w;
} PSpace;

typedef struct {
  int v1, v2, v3;
} Face;

typedef struct {
  Vertex *vertices;
  int num_vertices;
  Face *faces;
  int num_faces;
} Model;

int load_obj(const char *filename, Model *model) {
  FILE *file = fopen(filename, "r");
  if (file == NULL) {
    printf("objdraw: Could not open file %s\n", filename);
    return FALSE;
  }

  char linebuff[4096], *line;
  int vertex_count = 0;
  int face_count = 0;

  /*
      From https://en.wikipedia.org/wiki/Wavefront_.obj_file
    

      Relative and absolute indices

      OBJ files, due to their list structure, are able to reference vertices,
      normals, etc. either by their absolute position (1 represents the first
      defined vertex, N representing the Nth defined vertex), or by their relative
      position (-1 represents the latest defined vertex). However, not all software
      supports the latter approach, and conversely some software inherently writes
      only the latter form (due to the convenience of appending elements without
      needing to recalculate vertex offsets, etc.), leading to occasional incompatibilities.


      Vertex indices

      A valid vertex index matches the corresponding vertex elements of a previously
      defined vertex list. If an index is positive then it refers to the offset
      in that vertex list, starting at 1. If an index is negative then it relatively
      refers to the end of the vertex list, -1 referring to the last element.

      Each face can contain three or more vertices.

      f v1 v2 v3 ....


      Vertex texture coordinate indices

      Optionally, texture coordinate indices can be used to specify texture coordinates
      when defining a face. To add a texture coordinate index to a vertex index when
      defining a face, one must put a slash immediately after the vertex index and then
      put the texture coordinate index. No spaces are permitted before or after the slash.
      A valid texture coordinate index starts from 1 and matches the corresponding element
      in the previously defined list of texture coordinates. Each face can contain three
      or more elements.

      f v1/vt1 v2/vt2 v3/vt3 ...


      Vertex normal indices

      Optionally, normal indices can be used to specify normal vectors for vertices when
      defining a face. To add a normal index to a vertex index when defining a face, one
      must put a second slash after the texture coordinate index and then put the normal
      index. A valid normal index starts from 1 and matches the corresponding element in
      the previously defined list of normals. Each face can contain three or more elements.

      f v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3 ...


      Vertex normal indices without texture coordinate indices

      As texture coordinates are optional, one can define geometry without them, but one
      must put two slashes after the vertex index before putting the normal index.

      f v1//vn1 v2//vn2 v3//vn3 ...

   */

  
  // First pass: size everything and allocate space for it.

  while ((line=fgets(linebuff, sizeof(linebuff), file)) != NULL) {
    
    while (isblank(*line)) line++; // leading indentation
    
    if (line[0] == '\n' || line[0] == '\0') {
      // blank line
    } else if (strncmp(line, "#", 1) == 0) {
      // comment
      // # Vertices: 30
      // # Texture vertices: 59
      // # Normals: 30
      // # Faces: 16
    } else if (strncmp(line, "v ", 2) == 0) {
      // List of geometric vertices, with (x, y, z, [w]) coordinates, w is optional and defaults to 1.0.
      // v -0.002424 1.919356 -2.225567
      vertex_count++;
    } else if (strncmp(line, "f ", 2) == 0) {
      // Polygonal face element:  Vertex/Texture/Normal
      // f 6/11/6 7/12/7 8/14/8 5/8/5 ...
      // Add 1 to face_count (face_count++) for each group of triangles in the line.  
    } else if (strncmp(line, "l ", 2) == 0) {
      // Polygonal line element
      // l 5 8 1 2 4 9
    } else if (strncmp(line, "vn ", 3) == 0) {
      // Normals (normals might not be unit vectors)
      // vn 0.000000 0.000000 1.000000
      // vn -0.542794 0.689214 -0.479957
    } else if (strncmp(line, "vt ", 3) == 0) {
      // List of texture coordinates, in (u, [v, w]) coordinates, these will vary between 0 and 1. v, w are optional and default to 0.
      // vt 0.000000 0.000000
      // vt 1.000000 1.000000
      // vt 1.000000 0.002445
    } else if (strncmp(line, "vp ", 3) == 0) {
      // Parameter space vertices in (u, [v, w]) form; free form geometry statement
    } else if (strncmp(line, "s ", 2) == 0) {
      // Smoothing groups (or turn off)
      // s 2
      // usemtl NoName,1001
      // ...
      // s 1
      // usemtl NoName,1000
      // ...
      // s off
    } else if (strncmp(line, "mtllib ", 7) == 0) {
      // mtllib [external .mtl file name]
      // mtllib supertank2.mtl
    } else if (strncmp(line, "usemtl ", 7) == 0) {
      // usemtl [material name]
      // usemtl NoName,1001
      // Lines starting with "usemtl" specify which material to apply to a set of faces, and are often used in conjunction with .mtl files.
    } else if (strncmp(line, "o ", 2) == 0) {
      // o [object name]
    } else if (strncmp(line, "g ", 2) == 0) {
      // g [polygon group name]
      // g Default
    }
  }

  // Allocate memory for vertices and faces
  model->vertices = (Vertex *)malloc(sizeof(Vertex) * vertex_count);
  model->faces = (Face *)malloc(sizeof(Face) * face_count);
  if (!model->vertices || !model->faces) {
    printf("Error: Memory allocation failed\n");
    fclose(file);
    return 0;
  }

  fprintf(stderr, "%d vertices, %d faces\n", vertex_count, face_count);
  
  // Reset file pointer to the beginning so we can re-read the file.
  // (won't work on pipes etc!)
  
  fseek(file, 0, SEEK_SET);

  int vertex_index = 0;
  int face_index = 0;
  
  // Second pass: Now we know how much space was needed to store all the
  // data, let's actually read that data:
  
  while ((line=fgets(linebuff, sizeof(linebuff), file)) != NULL) {

    if (feof(file)) break;
    while (isblank(*line)) line++; // leading indentation
    
    if (line[0] == '\n' || line[0] == '\0') {
      // blank line
    } else if (strncmp(line, "#", 1) == 0) {
      // comment
      // # Vertices: 30
      // # Texture vertices: 59
      // # Normals: 30
      // # Faces: 16
    } else if (strncmp(line, "v ", 2) == 0) {
      // List of geometric vertices, with (x, y, z, [w]) coordinates, w is optional and defaults to 1.0.
      // v -0.002424 1.919356 -2.225567
      //sscanf(line, "v %f %f %f", &model->vertices[vertex_index].x, &model->vertices[vertex_index].y,
      //       &model->vertices[vertex_index].z);
      //fprintf(stderr, "v[%d]:  %f %f %f\n", vertex_index, model->vertices[vertex_index].x,
      //        model->vertices[vertex_index].y, model->vertices[vertex_index].z);
      //vertex_index++;
    } else if (strncmp(line, "f ", 2) == 0) {
      // Polygonal face element
      // f 6/11/6 7/12/7 8/14/8 5/8/5 ...
      //sscanf(line, "f %d %d %d", &model->faces[face_index].v1, &model->faces[face_index].v2,
      //       &model->faces[face_index].v3);
      // OBJ indices start from 1, so subtract 1
      //model->faces[face_index].v1--;
      //model->faces[face_index].v2--;
      //model->faces[face_index].v3--;
      //fprintf(stderr, "f[%d] %d %d %d\n", face_index, model->faces[face_index].v1, model->faces[face_index].v2,
      //        model->faces[face_index].v3);
      //face_index++;
    } else if (strncmp(line, "l ", 2) == 0) {
      // Polygonal line element
      // l 5 8 1 2 4 9
    } else if (strncmp(line, "vn ", 3) == 0) {
      // Normals (normals might not be unit vectors)
      // vn 0.000000 0.000000 1.000000
      // vn -0.542794 0.689214 -0.479957
    } else if (strncmp(line, "vt ", 3) == 0) {
      // List of texture coordinates, in (u, [v, w]) coordinates, these will vary between 0 and 1. v, w are optional and default to 0.
      // vt 0.000000 0.000000
      // vt 1.000000 1.000000
      // vt 1.000000 0.002445
    } else if (strncmp(line, "vp ", 3) == 0) {
      // Parameter space vertices in (u, [v, w]) form; free form geometry statement
    } else if (strncmp(line, "s ", 2) == 0) {
      // Smoothing groups (or turn off)
      // s 2
      // usemtl NoName,1001
      // ...
      // s 1
      // usemtl NoName,1000
      // ...
      // s off
    } else if (strncmp(line, "mtllib ", 7) == 0) {
      // mtllib [external .mtl file name]
      // mtllib supertank2.mtl
    } else if (strncmp(line, "usemtl ", 7) == 0) {
      // usemtl [material name]
      // usemtl NoName,1001
      // Lines starting with "usemtl" specify which material to apply to a set of faces, and are often used in conjunction with .mtl files.
    } else if (strncmp(line, "o ", 2) == 0) {
      // o [object name]
    } else if (strncmp(line, "g ", 2) == 0) {
      // g [polygon group name]
      // g Default
    }
  }

  fclose(file);
  model->num_vertices = vertex_count;
  model->num_faces = face_count;
  return TRUE;
}

void render_model(const Model *model) {

  // Since the Vectrex is not area-based, triangles are not the ideal way to represent
  // surfaces!  We should probably use 'L' polygonal line groups for drawing, with a
  // 'ghost' implementation of surfaces using triangles which are only ever used for
  // hidden surface removal...  it will be conceptually tricky because the obj file data
  // structure is not a good fit for Vectrex-style models, which sometimes are not even
  // consistent with planar surfaces (e.g. some of the Tailgunner spacecraft...)

  // Initially in fact it may make more sense to *only* use and draw 'L' groups.
  
  glBegin(GL_TRIANGLES);
  for (int i = 0; i < model->num_faces; i++) {
    Vertex v1 = model->vertices[model->faces[i].v1];
    Vertex v2 = model->vertices[model->faces[i].v2];
    Vertex v3 = model->vertices[model->faces[i].v3];

    glVertex3f(v1.x, v1.y, v1.z);
    glVertex3f(v2.x, v2.y, v2.z);
    glVertex3f(v3.x, v3.y, v3.z);
  }
  glEnd();
}

int main(int argc, char **argv) {

  if (argc != 2) {
    fprintf(stderr, "syntax: objdraw file.obj\n");
    exit(EXIT_FAILURE);
  }
  
  // Initialize graphics library GLFW:
  if (!glfwInit()) {
    fprintf(stderr, "objdraw: GLFW initialization failed\n");
    exit(EXIT_FAILURE);
  }

  // Create a window
  GLFWwindow *window = glfwCreateWindow(800, 600, "OBJ Viewer", NULL, NULL);
  if (!window) {
    fprintf(stderr, "objdraw: GLFW window creation failed\n");
    glfwTerminate();
    exit(EXIT_FAILURE);
  }

  // Make the window's context current
  glfwMakeContextCurrent(window);

  // Initialize GLEW
  if (glewInit() != GLEW_OK) {
    fprintf(stderr, "objdraw: GLEW initialization failed\n");
    glfwTerminate();
    exit(EXIT_FAILURE);
  }

  // Load the OBJ model
  Model model;
  if (!load_obj(argv[1], &model)) {
    glfwTerminate();
    exit(EXIT_FAILURE);
  }

  fprintf(stderr, "Drawing model. Press [X] to close.\n");
  // Main loop
  while (!glfwWindowShouldClose(window)) {
    // Clear the color buffer
    glClear(GL_COLOR_BUFFER_BIT);

    // Set up projection matrix
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-2, 2, -2, 2, -10, 10);

    // Set up modelview matrix
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    // Render the model
    render_model(&model);

    // Swap front and back buffers
    glfwSwapBuffers(window);

    // Poll for and process events
    glfwPollEvents();
  }

  // Free model data
  free(model.vertices); model.vertices = NULL;
  free(model.faces); model.faces = NULL;

  // Terminate GLFW
  glfwTerminate();
  
  exit(EXIT_SUCCESS);
  return EXIT_FAILURE;
}
