{

extern char *parser_filename;

// objdraw.c is basically the same program, except with an ad-hoc parser
// and code to draw the model rather than output it as C structures.

// In this program we throw away any data that's not relevant to outputting a
// simple wireframe suitable for drawing on the Vectrex...

// APPMODULE: obj.c
// APPCOMMAND: walkobj

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

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;

#include <wchar.h> // Unicode supported.
#include <math.h>

// Would be better to use FLEX package below:
#define MAX_ELEMENTS 1024
#define MAX_WSTR_LEN 1024

wchar_t *v[MAX_ELEMENTS];
static int next_free_v = 0;

wchar_t *vn[MAX_ELEMENTS];
static int next_free_vn = 0;

wchar_t *vt[MAX_ELEMENTS];
static int next_free_vt = 0;

wchar_t *f[MAX_ELEMENTS];
static int next_free_f = 0;

#define NOT_SURE 1 // trick to get the code below to execute when converting the CST to an AST
wchar_t *str_walk(int P); // return the result of a walk of a subtree as a string (off the heap)

// This replaces the default tree-walking code 'walkobj' that is usually called after compiling the AST:

int walkobj(int P, int depth) { // walk default CST (concrete syntax tree...)

  // I would rather that this code was embedded in the grammar below but that would require a major
  // restructuring of the 'uparse' parser, where currently the embedded code is what builds the AST,
  // not what is executed when walking the completed CST.
  
  if (P == -1) return -1;

  int i;
  int AST_type = (int)((unsigned int)P&(((unsigned int)AST_type_mask)<<(unsigned int)AST_type_shift));
  int AST_index = P&AST_idx_mask;
  int op = AST(AST_index+1);
  int alt = AST(AST_index+2);
  int count = AST(AST_index+3);

  if (AST_type == PHRASE_TYPE) {   // DEBUG CHANGED AST_PHRASE TO PHRASE_TYPE
    for (i = 1; i <= count; i++) walkobj(SubPhraseIdx(P,i), depth+1);

    if (op == G_v) { // P<v> = «v» <float> <float> <float> {

      wchar_t *a,*b,*c;
      wchar_t tmp[MAX_WSTR_LEN];
      swprintf(tmp, MAX_WSTR_LEN, L"  {%ls, %ls, %ls},\n", a=str_walk(SubPhraseIdx(P,2)), b=str_walk(SubPhraseIdx(P,3)), c=str_walk(SubPhraseIdx(P,4)));
      free(a); free(b); free(c);
      v[next_free_v++] = wcsdup(tmp);
      
    } if (op == G_vn) { // P<vn> = «vn» <float> <float> <float> {
    
      wchar_t *a,*b,*c;
      wchar_t tmp[MAX_WSTR_LEN];
      swprintf(tmp, MAX_WSTR_LEN, L"  {%ls, %ls, %ls},\n", a=str_walk(SubPhraseIdx(P,2)), b=str_walk(SubPhraseIdx(P,3)), c=str_walk(SubPhraseIdx(P,4)));
      free(a); free(b); free(c);
      vn[next_free_vn++] = wcsdup(tmp);
      
    } if (op == G_vt) { // P<vt> = «vt» <float> <float> <opt-float> {
    
      wchar_t *a,*b;
      wchar_t tmp[MAX_WSTR_LEN];
      swprintf(tmp, MAX_WSTR_LEN, L"  {%ls, %ls},\n", a=str_walk(SubPhraseIdx(P,2)), b=str_walk(SubPhraseIdx(P,3)));
      free(a); free(b);
      vt[next_free_vt++] = wcsdup(tmp);
      
    } else if (op == G_f_groups) {
      // P<vn-index> = <int> {
      // P<opt-f-vn> = "/" <vn-index> | {
      // P<opt-vt-index> = <int> | {
      // P<f-vt-vn-indices> = "/" <opt-vt-index> <opt-f-vn> | {
      // P<v-index> = <int> {
      // P<more-f-groups> = <v-index> <f-vt-vn-indices> <more-f-groups> | {
      // P<f-groups> = <v-index> <f-vt-vn-indices> <more-f-groups> {
      
      wchar_t *prev_v_index = str_walk(SubPhraseIdx(P,1));
      wchar_t *first_v_index = prev_v_index;
      wchar_t tmp[MAX_WSTR_LEN];
      
      for (;;) {
        P = SubPhraseIdx(P,3); // <more-f-groups>
        
        AST_type = (int)((unsigned int)P&(((unsigned int)AST_type_mask)<<(unsigned int)AST_type_shift));
        AST_index = P&AST_idx_mask;
        op = AST(AST_index+1);
        alt = AST(AST_index+2);
        count = AST(AST_index+3);

        wchar_t *next_v_index;
        if (alt == 1) { // empty alt - end of path around face
          next_v_index = first_v_index; // close the loop on the path around this face.
        } else {
          next_v_index = str_walk(SubPhraseIdx(P,1)); // <v-index> in "P<more-f-groups> = <v-index> <f-vt-vn-indices> <more-f-groups>"
        }
        if (wcstol(prev_v_index, NULL, 10) < wcstol(next_v_index, NULL, 10)) {
          // enforce canonical order then let uniq remove duplicates after sorting.
          swprintf(tmp, MAX_WSTR_LEN, L"  {%5ls,%5ls },\n", prev_v_index, next_v_index);
        } else {
          swprintf(tmp, MAX_WSTR_LEN, L"  {%5ls,%5ls },\n", next_v_index, prev_v_index);
        }
        f[next_free_f++] = wcsdup(tmp);

        if (alt == 1) { // empty alt - end of path around face
          break;
        }


        prev_v_index = next_v_index;
      }

    } else if (op == G_SS) { // Top-level - a good place to output results after parsing and building up inernal tables of the OBJect..

      char *sp, *object_name = strdup(parser_filename);
      if (*object_name == '\0') object_name = strdup("object");
      if ((sp = strrchr(object_name, '.')) != NULL) *sp = '\0'; 
      if ((sp = strrchr(object_name, '/')) != NULL) sp += 1; else sp = object_name;
      fwprintf(stdout, L"const Vertex %s_v[] = {\n  {    0,0,0}, // Vertex indices in .OBJ files are based at 1.\n", sp/*, next_free_v*/);

      for (int i = 0; i < next_free_v; i++) {
        fwprintf(stdout, L"%ls", v[i]); free(v[i]);
      }
      fwprintf(stdout, L"  { -128,-128,-128}, /* End of list marker */\n};\n");

#ifdef NEVER
      fwprintf(stdout, L"const Vertex vn[%d] = {\n", next_free_vn);
      for (int i = 0; i < next_free_vn; i++) {
        fwprintf(stdout, L"%ls", vn[i]); free(vn[i]);
      }
      fwprintf(stdout, L"};\n");
#endif

#ifdef NEVER
      fwprintf(stdout, L"const Point vt[%d] = {\n", next_free_vt);
      for (int i = 0; i < next_free_vt; i++) {
        fwprintf(stdout, L"%ls", vt[i]); free(vt[i]);
      }
      fwprintf(stdout, L"};\n");
#endif

      fwprintf(stdout, L"const Edge %s_e[] = {\n", sp/*, next_free_f*/);
      
      fflush(stdout);
      // marginally easier than calling C's qsort:

      // Ordering is less important than eliminating duplicates...
      // Sort keys are very hard to get right in this context,
      // and also the ends of the lines should have been ordered
      // lowest to highest to eliminate duplicates caused by
      // adjacent faces with opposite winding orders.

      FILE *tmp = fopen("/tmp/xxx.xx", "w");
      for (int i = 0; i < next_free_f; i++) {
        fwprintf((tmp ? tmp : stdout), L"%ls", f[i]); free(f[i]);
      }
      if (tmp) {
        fclose(tmp);
        int rc = system("/usr/bin/sort -n -t' ' -k4 -k8 < /tmp/xxx.xx | /usr/bin/uniq"); // sort -u is broken
      }
      fflush(stdout);

      fwprintf(stdout, L"  {    0,    0 }  /* End of list. (Index 0 is never used) */\n};\n");
      free(object_name);
    }

  } else if (AST_type == AST_PHRASE) {
    for (i = 1; i <= count; i++) walkobj(SubPhraseIdx(P,i), depth+1);
  }
}

void AppendAtomStr(wchar_t *s, int Literal) {
  for (int i = atom(Literal).start; i < atom(Literal).end; i++) swprintf(s+wcslen(s), 1024, L"%lc", source(i).ch);
}

int str(wchar_t *s, int P) { // walk but don't print default AST
  if (P == -1) return -1;

  int i, AST_type = (int)((unsigned int)P&(((unsigned int)AST_type_mask)<<(unsigned int)AST_type_shift));
  int AST_index = P&AST_idx_mask;
  int op = AST(AST_index+1);
  int alt = AST(AST_index+2);
  int count = AST(AST_index+3);

  if (AST_type == PHRASE_TYPE) {
    if (op == G_float) {
      float val;
      wchar_t floatval[128];
      str(floatval, SubPhraseIdx(P,1));
      swscanf(wcsrchr(floatval, ' ') ? wcsrchr(floatval, ' ') : floatval, L"%f", &val);
      swprintf(s+wcslen(s), 1024, L"%5d /*%10ls */", (int)round(val*16.0), wcsrchr(floatval, ' ') ? wcsrchr(floatval, ' ') : floatval);
    } else {
      for (i = 1; i <= count; i++) str(s, SubPhraseIdx(P,i));
    }
  } else if (AST_type == AST_PHRASE) {   // DEBUG CHANGED AST_PHRASE TO PHRASE_TYPE
    for (i = 1; i <= count; i++) str(s, SubPhraseIdx(P,i));
  } else if (AST_type == AST_ATOM_LIT) {
    AppendAtomStr(s, AST_index);
  }
}

wchar_t *str_walk(int P) { // walk but don't print default AST
  wchar_t tmp[1024] = { L'\0' };
  str(tmp, P); // dangerous, could exceed capacity.  Quick hack.
  return wcsdup(tmp);
}

}

B<EOF> = 0;
B<ch> = 1;
B<bnl> = 2;  # we use a built-in phrase for \n because our regular expressions don't handle newlines well
P<nl> = <bnl> ;  # because <!BIP> is not supported but not warned about, and causes a runtime error :-(

P<int> = «[0-9][0-9]*» {
};

P<opt-exponent> = 'e' «[\\\\-]?» <int> | {
};

P<float> = «[\\\\-]?[0-9]*[\\\\.][0-9]*» <opt-exponent> {
};

# Main entry point: (SS = 'Source Statements')
P<SS> = <item> <nl> <more_items> <EOF> {
};

P<item> = <comment> | <usemtl> | <mtllib> | <v> | <vt> | <vn> | <f> | <g> | <s> | <vp> | <o> | <l> | <EOF> ;

P<more_items> = <item> <nl> <more_items> | ;

P<non-empty-content> = <!nl> <ch> <non-empty-content> | {
};

# P<filename> = «[A-Za-z0-9/_.\\\\-\\\\.][A-Za-z0-9/_.\\\\-\\\\.]*» { // literal '-' and '.' characters allowed in filenames.
P<filename> = <non-empty-content> {
};

P<comment> = «#» <non-empty-content> {
  // Some comments may be worth parsing...

  // # Vertices: 30
  // # Texture vertices: 59
  // # Normals: 30
  // # Faces: 16
      
  //fwprintf(stdout, L"COMMENT\n");
};

P<usemtl_options> = ',' «[1-9][0-9]*» | {
};

P<usemtl> = «usemtl» <filename> <usemtl_options> {
  // 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.

  //fwprintf(stdout, L"USEMTL\n");
};

P<mtllib> = «mtllib» <filename> {
  // mtllib [external .mtl file name]
  // mtllib supertank2.mtl
      
  //fwprintf(stdout, L"MTLLIB\n");
};

P<v> = «v» <float> <float> <float> <opt-float> {
  // 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

  //fwprintf(stdout, L"V ...\n");
};

P<opt-float> = <float> | {
};

P<vt> = «vt» <float> <float> <opt-float> {
  // 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

  //fwprintf(stdout, L"VT ...\n");
};

P<vn> = «vn» <float> <float> <float> {
  // Normals (normals might not be unit vectors)
  // vn 0.000000 0.000000 1.000000
  // vn -0.542794 0.689214 -0.479957

  //fwprintf(stdout, L"VN ...\n");
};

P<vp> = «vn» <non-empty-content> {
  // Parameter space vertices in (u, [v, w]) form; free form geometry statement

  //fwprintf(stdout, L"VP ...\n");
};

P<vn-index> = <int> {
};

P<opt-f-vn> = "/" <vn-index> | {
};

P<opt-vt-index> = <int> | {
};

P<f-vt-vn-indices> = "/" <opt-vt-index> <opt-f-vn> | {
};

P<v-index> = <int> {
};

P<more-f-groups> = <v-index> <f-vt-vn-indices> <more-f-groups> | {
};

P<f-groups> = <v-index> <f-vt-vn-indices> <more-f-groups> {

/*
      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 ...
      
*/
};

P<f> = «f» <f-groups> {
  // 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.  

  //fwprintf(stdout, L"F ...\n");
};

P<g> = «g» <non-empty-content> {
  // g [polygon group name]
  // g Default

  //fwprintf(stdout, L"G ...\n");
};

P<s> = «s» «[0-9][0-9]*» | «s» «off»{
  // Smoothing groups (or turn off)
  // s 2
  // usemtl NoName,1001
  // ...
  // s 1
  // usemtl NoName,1000
  // ...
  // s off

  //fwprintf(stdout, L"S ...\n");
};

P<o> = «o» <filename> {
  // o [object name]

  //fwprintf(stdout, L"O ...\n");
};

P<l> = «l» <non-empty-content> {
  // Polygonal line element
  // l 5 8 1 2 4 9

  //fwprintf(stdout, L"L ...\n");
};

E
