/* See {stmesh_view_paint.h} */
/* Last edited on 2015-11-18 01:02:52 by stolfilocal */

#define _GNU_SOURCE
#include <stdint.h>
#include <assert.h>
#include <math.h>

#include <GL/glu.h>
#include <GL/glut.h>

#include <r3.h>
#include <bool.h>
#include <affirm.h>
#include <frgb.h>
#include <group_sort_uint32.h>

#include <stmesh.h>
/* #include <stmesh_utils.h> */
/* #include <salamic_utils.h> */
#include <stmesh_view_paint.h>

/* INTERNAL PROTOTYPES */

void stmesh_view_paint_collect_edges_by_style
  ( stmesh_t mesh, 
    uint32_t deg_max, 
    uint32_t *ngP,       /* (OUT) Number of groups. */         
    stmesh_edge_t **ePP, /* (OUT) Edges sorted by group. */    
    uint32_t **gstartPP  /* (OUT) Beg and end of each group. */
  );
  /* Assign a drawing style index to each of the
    {ne} edges of {mesh}, and sorts them by style group.
    
    Currently, the style index for each edge is its degree (number of
    incident faces). However, even and odd degrees greater than
    {deg_max} (whhic must be even) are mapped to {deg_max} and
    {deg_max-1}, respectively.
  
    Returns in {*ngP} the number {ng} of distinct edge styles used.
    Returns in {*ePP} the address of a newly allocated
    vector {e[0..ne-1]} with all the mesh edges, sorted by increasing style. Also
    returns in {*gstartPP} the address of a newly allocated vector {gstart[0..ng]}
    with the beginning and end in {e} of each style
    group. Namely, for any group index {ig} in {0..ng-1}, the edges in
    group {ig} are {e[k0..k1-1]}, where {k0=gstart[ig]} and
    {k1=gstart[ig+1]}. Note that {gstart} will have {ng+1} elements, not
    {ng}. */

frgb_t stmesh_vew_paint_tweak_hue(frgb_t *p);
  /* Returns a version of the color {p} with the hue slightly shifted forwards. */

/* IMPLEMENTATIONS */

void stmesh_view_paint_mesh(stmesh_t mesh, bool_t showFaces, bool_t showEdges)
  {
    if (mesh == NULL) { return; }
    if (stmesh_view_debug_paint) { fprintf(stderr, "+ %s\n", __FUNCTION__); }

    if (showFaces)
      { stmesh_view_paint_mesh_faces(mesh); }

    if (showEdges)
      { stmesh_view_paint_mesh_edges(mesh); }

    if (stmesh_view_debug_paint) { fprintf(stderr, "- %s\n", __FUNCTION__); }
  }
      
void stmesh_view_paint_mesh_faces(stmesh_t mesh)
  { 
    bool_t debug = FALSE;
    if (stmesh_view_debug_paint) { fprintf(stderr, "+ %s\n", __FUNCTION__); }
    
    uint32_t nf = stmesh_face_count(mesh);
    float eps = stmesh_get_eps(mesh);

    /* Set surface finish: */
    glEnable(GL_COLOR_MATERIAL);
    glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE);

    /* Get color in GL format: */
    GLfloat CR = 0.700f, CG = 0.800f, CB = 0.600f;
    glColor3f(CR,CG,CB);

    /* Paint triangles: */
    glBegin(GL_TRIANGLES);
    stmesh_face_unx_t uxf;
    for(uxf = 0; uxf < nf; uxf++)
      { /* Get face vertices: */
        stmesh_face_t f = stmesh_get_face(mesh, uxf);
        stmesh_vert_t v[3];
        stmesh_face_get_corners(f, v);
        /* Get vertex coords (in mm): */
        r3_t vR[3];
        int k;
        for (k = 0; k < 3; k++)
          { i3_t vQ = stmesh_vert_get_pos(v[k]);
            if (debug) { fprintf(stderr, "( %d %d %d )\n", vQ.c[0], vQ.c[1], vQ.c[2]); }
            vR[k] = stmesh_unround_point(&vQ, eps);
          }
        if (debug) { fprintf(stderr, "\n"); }

        stmesh_view_paint_triangle(vR);
      }
    glEnd();
    glDisable(GL_COLOR_MATERIAL);

    if (stmesh_view_debug_paint) { fprintf(stderr, "- %s\n", __FUNCTION__); }
  }

void stmesh_view_paint_mesh_edges(stmesh_t mesh)
  { 
    if (stmesh_view_debug_paint) { fprintf(stderr, "+ %s\n", __FUNCTION__); }
    
    float eps = stmesh_get_eps(mesh);

    glEnable(GL_COLOR_MATERIAL);
    glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE);

    /* Line width cannot be changed inside a begin-end. So we need to sort the
      edges by style and paint each style separately.
      !!! Should be pre-sorted !!! */

    /* Sort edges by drawing style: */
    uint32_t deg_max = 4; /* Max degree for style assignment. */
    uint32_t ng;        /* Number of edge drawing styles. */
    stmesh_edge_t *e;   /* Edges {mesh.e[0..ne-1]}, sorted by style. */
    uint32_t *gstart;   /* Beg/end of each style group. */
    stmesh_view_paint_collect_edges_by_style(mesh, deg_max, &ng, &e, &gstart);
    
    /* Define the edge colors for the first 5 groups: */
    frgb_t gGolor[ng]; /* Color for each edge group. */
    assert(ng >= 5);
    gGolor[0] = (frgb_t){{ 1.000f, 0.250f, 1.000f }}; /* Degree zero (should not happen). */
    gGolor[1] = (frgb_t){{ 1.000f, 0.250f, 0.000f }}; /* Degree 1 (border). */
    gGolor[2] = (frgb_t){{ 0.000f, 0.000f, 0.000f }}; /* Degree 2 (manifold). */
    gGolor[3] = (frgb_t){{ 1.000f, 0.350f, 0.000f }}; /* Degree 3 (border + manifold). */
    gGolor[4] = (frgb_t){{ 0.000f, 0.350f, 1.000f }}; /* Degree 4 (double manifold). */
    /* Define the colors of other groups, if any: */
    uint32_t ig;
    for (ig = 5; ig < ng; ig++)
      { gGolor[ig] = stmesh_vew_paint_tweak_hue(&(gGolor[ig-2])); }
        
    /* Paint edges by group: */
    for (ig = 0; ig < ng; ig++)
      { 
        /* Get the sublist of edges of this group: */
        uint32_t k0 = gstart[ig];   /* First edge of group has index {ixEdge[k0]}. */
        uint32_t k1 = gstart[ig+1]; /* Last edge of group has index {ixEdge[k1-1]}. */
        
        if (k0 < k1)
          { /* Group is not empty. */
            
            /* Set the edge color and line thickness: */
            frgb_t *clr = &(gGolor[ig]);
            glColor3f(clr->c[0], clr->c[1], clr->c[2]);
            
            /* Set the line thickness: */
            if (ig == 2)
              { glLineWidth(1.0f); }
            else
              { glLineWidth(2.0f); }
              
            /* Draw the edges of group {ig}: */
            glBegin(GL_LINES);

            uint32_t ke;
            for(ke = k0; ke < k1; ke++)
              { /* Get edge: */
                stmesh_edge_t ek = e[ke];
                /* Get endpoints: */
                stmesh_vert_t v[2];
                stmesh_edge_get_endpoints(ek, v);
                /* Get endpoint coords in mm: */
                r3_t vR[2];
                int r;
                for (r = 0; r < 2; r++)
                  { i3_t vQ = stmesh_vert_get_pos(v[r]);
                    vR[r] = stmesh_unround_point(&vQ, eps);
                  }
                stmesh_view_paint_line(vR);
              }
            glEnd();
          }
      }
    glDisable(GL_COLOR_MATERIAL);

    free(gstart);
    free(e);
    
    if (stmesh_view_debug_paint) { fprintf(stderr, "- %s\n", __FUNCTION__); }
  }

void stmesh_view_paint_collect_edges_by_style
  ( stmesh_t mesh, 
    uint32_t deg_max, 
    uint32_t *ngP,        /* (OUT) Number of groups. */
    stmesh_edge_t **ePP,  /* (OUT) Edges sorted by group. */
    uint32_t **gstartPP   /* (OUT) Beg and end of each group. */
  )
  { 
    uint32_t ne = stmesh_edge_count(mesh);   /* Number of edges. */

    demand((deg_max & 2) == 0, "max degree must be even");
    
    uint32_t ng = deg_max + 1;

    stmesh_edge_t *e = notnull(malloc(ne*sizeof(stmesh_edge_t)), "no mem"); /* Edges sorted by style. */
    uint32_t *gstart = notnull(malloc((ng+1)*sizeof(uint32_t)), "no mem"); /* Start/end of styles. */

    /* Assigns group index {egroup[uxe]} to each face with index {uxe}: */
    uint32_t *egroup = notnull(malloc(ne*sizeof(uint32_t)), "no mem"); /* Group of each edge. */
    
    uint32_t ke;
    for (ke = 0; ke < ne; ke++)
      { stmesh_edge_t ek = stmesh_get_edge(mesh, ke);
        uint32_t deg_raw = stmesh_edge_degree(ek);
        uint32_t deg_clip = (deg_raw <= deg_max ? deg_raw : deg_max - (deg_raw & 1));
        egroup[ke] = deg_clip;
      }

    /* Sort edge indices by group: */
    stmesh_edge_unx_t *uxe = notnull(malloc(ne*sizeof(stmesh_edge_unx_t)), "no mem"); /* Edge indices in group order. */
    group_sort_uint32(ne, ng, egroup, uxe, gstart);
      
    /* Pick the edges in that order: */
    for (ke = 0; ke < ne; ke++) { e[ke] = stmesh_get_edge(mesh, uxe[ke]); }

    /* Paranoia check: */
    assert(gstart[ng] == ne);

    (*ngP) = ng;
    (*ePP) = e;
    (*gstartPP) = gstart;

    free(uxe);
    free(egroup);
  }

void stmesh_view_paint_slices(stmesh_t mesh, int np, stmesh_section_t *slice[])
  { 
    if (stmesh_view_debug_paint) { fprintf(stderr, "+ %s\n", __FUNCTION__); }
    
    /* For now, same color and line width for all sections: */
    frgb_t clr = (frgb_t){{ 1.000f, 0.650f, 0.100f }};
    float lwd = 3.0f;

    /* Draw the slices: */
    int ip;
    for (ip = 0; ip < np; ip++) 
      { stmesh_view_paint_slice(slice[ip], &clr, lwd); }
    
    if (stmesh_view_debug_paint) { fprintf(stderr, "- %s\n", __FUNCTION__); }
  }

void stmesh_view_paint_slice(stmesh_section_t *sec, frgb_t *clr, float lwd)
  {
  
    /* The {Z} coordinate of the slice: */
    double fpZ = ((double)sec->eps)*((double)sec->pZ);

    /* Set the color, finish, line thickness: */
    glEnable(GL_COLOR_MATERIAL);
    glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE);
    glColor3f(clr->c[0], clr->c[1], clr->c[2]);
    glLineWidth(lwd);
    glBegin(GL_LINES);
      
    /* Loop on the paths: */
    uint32_t kc;
    for (kc = 0; kc < sec->nc; kc++)
      { uint32_t ini = sec->cstart[kc];
        uint32_t fin = sec->cstart[kc+1] - 1;
        
        /* Draw the path with vertices {sec->v[ini..fin]}: */
        uint32_t iv;
        for (iv = ini; iv < fin; iv++)
          { r3_t p[2]; /* Segment endpoints in 3D. */
            int r;
            for (r = 0; r < 2; r++)
              { r2_t *vi = &(sec->v[iv + r]);
                p[r] = (r3_t){{ vi->c[0], vi->c[1], fpZ }};
              }
            stmesh_view_paint_line(p);
          }
      }

    glEnd();
    glDisable(GL_COLOR_MATERIAL);
  } 

frgb_t stmesh_vew_paint_tweak_hue(frgb_t *p)
  { 
    frgb_t r;
    float rmin = 1.0f; /* Min raw color coord. */ 
    float rmax = 0.0f; /* Max raw color coord. */ 
    int k;
    for (k = 0; k < 3; k++)
      { double a = (double)(p->c[k]);
        double b = (double)(p->c[(k+1) % 3]);
        r.c[k] = (float)(0.8*a + 0.2*b);
        if (r.c[k] < rmin) { rmin = r.c[k]; }
        if (r.c[k] > rmax) { rmax = r.c[k]; }
      }
    /* Normalize to min 0, max 1: */
    for (k = 0; k < 3; k++)
      { r.c[k] = (r.c[k]  - rmin)/(rmax - rmin); }
    return r;
  }

void stmesh_view_paint_reference_plane(r3_t *minP, r3_t *maxP, double z)
  {
    glEnable(GL_COLOR_MATERIAL);
    glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE);

    glColor3f(0.650f, 0.975f, 0.925f);

    glBegin(GL_QUADS);
    glVertex3f((GLfloat)minP->c[0], (GLfloat)minP->c[1], (GLfloat)z);
    glVertex3f((GLfloat)maxP->c[0], (GLfloat)minP->c[1], (GLfloat)z);
    glVertex3f((GLfloat)maxP->c[0], (GLfloat)maxP->c[1], (GLfloat)z);
    glVertex3f((GLfloat)minP->c[0], (GLfloat)maxP->c[1], (GLfloat)z);
    glEnd();

    glDisable(GL_COLOR_MATERIAL);
  }

void stmesh_view_paint_line(r3_t v[])
  {
    glVertex3f((GLfloat)v[0].c[0], (GLfloat)v[0].c[1], (GLfloat)v[0].c[2]);
    glVertex3f((GLfloat)v[1].c[0], (GLfloat)v[1].c[1], (GLfloat)v[1].c[2]);
  }

void stmesh_view_compute_normal(r3_t *u, r3_t *v, r3_t *n)
  {
    n->c[0] = u->c[1]*v->c[2] - v->c[1]*u->c[2];
    n->c[1] = u->c[2]*v->c[0] - v->c[2]*u->c[0];
    n->c[2] = u->c[0]*v->c[1] - v->c[0]*u->c[1];
    double nmod = sqrt(n->c[0]*n->c[0] + n->c[1]*n->c[1] + n->c[2]*n->c[2]);
    if (nmod > 0)
      { n->c[0] /= nmod;
        n->c[1] /= nmod;
        n->c[2] /= nmod;
      }
  }

void stmesh_view_paint_triangle(r3_t v[])
  {
    r3_t a, b;
    a.c[0] = v[1].c[0] - v[0].c[0];
    a.c[1] = v[1].c[1] - v[0].c[1];
    a.c[2] = v[1].c[2] - v[0].c[2];
    
    b.c[0] = v[2].c[0] - v[0].c[0];
    b.c[1] = v[2].c[1] - v[0].c[1];
    b.c[2] = v[2].c[2] - v[0].c[2];
    
    r3_t n;
    stmesh_view_compute_normal(&a, &b, &n);

    glNormal3f((GLfloat)n.c[0], (GLfloat)n.c[1], (GLfloat)n.c[2]);

    glVertex3f((GLfloat)v[0].c[0], (GLfloat)v[0].c[1], (GLfloat)v[0].c[2]);
    glVertex3f((GLfloat)v[1].c[0], (GLfloat)v[1].c[1], (GLfloat)v[1].c[2]);
    glVertex3f((GLfloat)v[2].c[0], (GLfloat)v[2].c[1], (GLfloat)v[2].c[2]);
  }

void stmesh_view_paint_quad(r3_t v[])
  {
    /* Compute normal assuming that it is flat: */
    r3_t a, b;
    a.c[0] = v[1].c[0] - v[0].c[0];
    a.c[1] = v[1].c[1] - v[0].c[1];
    a.c[2] = v[1].c[2] - v[0].c[2];
    
    b.c[0] = v[2].c[0] - v[0].c[0];
    b.c[1] = v[2].c[1] - v[0].c[1];
    b.c[2] = v[2].c[2] - v[0].c[2];
    
    r3_t n;
    stmesh_view_compute_normal(&a, &b, &n);

    glNormal3f((GLfloat)n.c[0], (GLfloat)n.c[1], (GLfloat)n.c[2]);

    glVertex3f((GLfloat)v[0].c[0], (GLfloat)v[0].c[1], (GLfloat)v[0].c[2]);
    glVertex3f((GLfloat)v[1].c[0], (GLfloat)v[1].c[1], (GLfloat)v[1].c[2]);
    glVertex3f((GLfloat)v[2].c[0], (GLfloat)v[2].c[1], (GLfloat)v[2].c[2]);
    glVertex3f((GLfloat)v[3].c[0], (GLfloat)v[3].c[1], (GLfloat)v[3].c[2]);
  }
