#define PROG_NAME "salamic"
#define PROG_DESC "topological slicing of triangle meshes for 3D printing"
#define PROG_VERS "1.0"

#define salamic_C_COPYRIGHT \
  "Copyright © 2015 by Federal Technological Universioty of Parana (UTFPR) and State University of Campinas (UNICAMP)"

/* Last edited on 2015-11-17 14:37:04 by stolfilocal */

#define PROG_HELP \
  "  " PROG_NAME " \\\n" \
  "    -modelFile {MODEL_FILE} \\\n" \
  "    -format { ascii | binary } \\\n" \
  "    [ -nfGuess {GUESSED_NUM_FACES} ] \\\n" \
  "    -eps {EPS} \\\n" \
  "    [ -preSorted ] \\\n" \
  "    { \\\n" \
  "      -planeZFile {PZ_FILE} | \\\n" \
  "      -deltaZ {DELTA_Z} [ -startZ {START_Z} ] \\\n" \
  "    } \\\n" \
  "    [ -slicer { TRIVIAL | MINETTO } ] \\\n" \
  "    [ -closer { NONE | TRIVIAL | STOLFI } ] \\\n" \
  "    -outPrefix {OUT_PREFIX} \\\n" \
  "    " argparser_help_info_HELP ""

#define PROG_INFO \
  "NAME\n" \
  "  " PROG_NAME " - " PROG_DESC "\n" \
  "\n" \
  "SYNOPSIS\n" \
  PROG_HELP "\n" \
  "\n" \
  "DESCRIPTION\n" \
  "  The program reads an unstructured triangle mesh from an STL file {MODEL_FILE}, and slices it" \
  " by a list of horizontal planes.  The {Z}-coordinates of the planes may be arbitrary.\n" \
  "\n" \
  "  " stmesh_STL_read_INFO "\n" \
  "\n" \
  "  The program does not detect (or care) about purely geometric problems like" \
  " two distinct triangles or two distinct edges whose interiors intersect.  It also does not check" \
  " presently whether every vertex has the manifold topology (at most one ring or" \
  " one fan of triangles incident to it).\n" \
  "\n" \
  "OUTPUT FILES\n" \
  "  " salamic_closer_output_file_name_INFO "\n" \
  "\n" \
  "  " stmesh_section_format_INFO "\n" \
  "\n" \
  "OPTIONS\n" \
  "  -modelFile {MODEL_FILE}\n" \
  "    This mandatory argument specifies the name of the input STL file.\n" \
  "\n" \
  "  -format ascii\n" \
  "  -format binary\n" \
  "    This mandatory argument specifies the format of the input STL file.\n" \
  "\n" \
  "  -nfGuess {GUESSED_NUM_FACES} \n" \
  "    This optional argument is a hint for the number of triangles that are" \
  " expected to be in the input file.  It is used by the program to preallocate" \
  " internal tables, so that they don't have to be expanded dynamically while" \
  " the file is being read.  The program is more efficient if {GUESSED_NUM_FACES} is the" \
  " actual number of triangles, or somewhat higher.  If omitted, the" \
  " program assumes \"-nfGuess " stringify(salamic_nfGuess_DEFAULT) "\".\n" \
  "\n" \
  "  -eps {EPS}\n" \
  "    This mandatory argument specifies the basic unit of length" \
  " (in millimeters).  All input coordinates will be rounded" \
  " to even integer multiples of {EPS} before the mesh topology" \
  " is extracted from the STL data.\n" \
  "\n" \
  "  -preSorted\n" \
  "    This optional argument, if present, tells the program that" \
  " the triangles in the input STL file are already sorted by" \
  " increasing {minZ} coordinate.  The slicing algorithm will" \
  " run slightly faster in this case.  The program will fail" \
  " if this option is specified but the file is not sorted.  If this" \
  " option is omitted, the program does not make any assumption about" \
  " the order of the triangles in the file.\n" \
  "\n" \
  "  -planeZFile {PZ_FILE}\n" \
  "    This argument specifies the name of the file that contains the Z-coordinates" \
  " of the slicing planes.  If this argument is present, the \"-deltaZ\" argument" \
  " should be omitted, and vice-versa.  Each {Z}-coordinate read from the file is" \
  " divided by the fundamental unit of length {EPS} and rounded to the nearest" \
  " odd integer.  Coordinates that do not intersect the model's bounding box" \
  " are ignored.\n" \
  "\n" \
  "    " salamic_planes_file_format_INFO "\n" \
  "\n" \
  "  -deltaZ {DELTA_Z}\n" \
  "    This argument specifies the spacing between the slicing planes, in" \
  " millimeters, for uniform slicing.  If this argument is" \
  " present, the \"-planeZFile\" argument should be omitted, and vice-versa.  The" \
  " given spacing {DELTA_Z} will be rounded to an even integer multiple of" \
  " the {EPS} length unit.  Beware that this rounding may cause cumulative" \
  " displacement of the slicing planes by up to {NP*EPS} millimeters, where" \
  " {NP} is the number of planes.\n" \
  "\n" \
  "  -startZ {START_Z}\n" \
  "    This optional argument specifies the {Z}-coordinate of one" \
  " of the slicing planes.  This argument may be present only if the \"-deltaZ\" option (uniform" \
  " slicing) is used.  The given coordinate {START_Z} will be rounded" \
  " to an odd integer multiple of the {EPS} length unit.  In spite of" \
  " the argument's name, the slicing planes will extend above and" \
  " below {START_Z}, spanning the whole height of the model.  If this" \
  " argument is omitted, the program assumes {START_Z = EPS}.\n" \
  "\n" \
  "  -slicer { TRIVIAL | MINETTO }\n" \
  "    This optional argument specifies the slicing algorithm to" \
  " be used. The \"TRIVIAL\" algorithm tests every triangle against" \
  " every slicing plane.  The \"MINETTO\" algorithm uses the sweep" \
  " plane technique described by Minetto, Volpato and Stolfi (2015).  If" \
  " this argument is not specified, the program uses the \"MINETTO\" slicer.\n" \
  "\n" \
  "  -closer { NONE | TRIVIAL | STOLFI }\n" \
  "    This optional argument specifies the algorithm to be used for converting" \
  " the unstructured lists of line segments on each slicing plane into" \
  " a set of maximal open or closed paths.  The \"TRIVIAL\" algorithm" \
  " searches for the next segment that extends the current path " \
  " by scanning the entire segment list each time.  The \"STOLFI\" algorithm" \
  " uses a technique similar to the one described by Minetto, Volpato and" \
  " Stolfi (2015), except that topological mesh information (instead of" \
  " {X,Y} coordinates of path vertices) is used to locate the next" \
  " segment.  The \"NONE\" option is intended for debugging and timing" \
  " tests: if chosen, the program will still find the triangles" \
  " intercepted by each slicing plane, but will skip the processing and output" \
  " of the slice contours.  If this argument is omitted, the" \
  " program uses the \"STOLFI\" closer.\n" \
  "\n" \
  "DOCUMENTATION OPTIONS\n" \
  argparser_help_info_HELP_INFO "\n" \
  "\n" \
  "SEE ALSO\n" \
  "  bolognac(1), prosciuttox(1), pancettaz(1).\n" \
  "\n" \
  "AUTHOR\n" \
  "  Created 2015-09-25 by Jorge Stolfi, IC-UNICAMP from {SliceMain.cpp} by Minetto and Habib.\n" \
  "\n" \
  "MODIFICATION HISTORY\n" \
  "  2015-09-26 Converted to C by J. Stolfi, IC-UNICAMP.\n" \
  "  2015-10-01 Separated the {stmesh} library (J. Stolfi).\n" \
  "  2015-10-18 Added the \"STOLFI\" closer; bug fixes (J. Stolfi).\n" \
  "  2015-11-14 Fixed a bug in slice vertex computation (R. Minetto).\n" \
  "  2015-11-14 Changed the format of slice output files (J. Stolfi).\n" \
  "  2015-11-14 Moved slice i/o routines to {libstmesh} (J. Stolfi).\n" \
  "  2015-11-16 Fixed bug in mesh reading (R. Minetto).\n"             \
  "  2015-11-16 Fixed bug in binary search (J. Stolfi).\n"             \
  "\n" \
  "WARRANTY\n" \
  argparser_help_info_NO_WARRANTY "\n" \
  "\n" \
  "RIGHTS\n" \
  "  " salamic_C_COPYRIGHT ".\n" \
  "\n" \
  argparser_help_info_STANDARD_RIGHTS

#define stringify(x) strngf(x)
#define strngf(x) #x

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

#include <bool.h>
#include <vec.h>
#include <argparser.h>
#include <affirm.h>

#include <stmesh.h>
#include <stmesh_STL.h>
#include <stmesh_section.h>

#include <salamic_planes.h>
#include <salamic_stats.h>
#include <salamic_slicer.h>
#include <salamic_closer.h>
/* #include <salamic_xml.h> */
/* #include <salamic_graphics.h> */

typedef struct salamic_options_t
  {
    /* Quantization: */
    float eps;             /* Vertex and plane coords are multiple of {eps}. */
    
    /* Parameters for slicing planes: */
    float startZ;         /* First {Z} coordinate (mm), or {NAN}. */
    float deltaZ;         /* Spacing for uniform slicing (mm), or {NAN}. */
    char *planeZFile;     /* Name of file with plane {Z}-coords. */

    /* Input files: */
    char *modelFile;       /* Name of file with the input STL model. */
    bool_t binary;         /* FALSE if ASCII STL format, TRUE if binary STL format. */
    bool_t preSorted;      /* If true, assumes that the input triangles be sorted by increasing {Z}. */
    int nfGuess;           /* Hint about number of triangles in the mesh. */
    
    /* Algorithms: */
    char *slicer; /* Slicer algorithm ("TRIVIAL", "MINETTO", "SANG", etc.). */
    char *closer; /* Loop closing algorithm ("TRIVIAL", "STOLFI", etc.). */

    /* Output files: */
    char *outPrefix;  /* Prefix for output file names. */

  } salamic_options_t;

/* INTERNAL PROTOTYPES */

int main(int argc, char **argv);
  /* Main program. */

salamic_options_t *salamic_parse_options(int argc, char **argv);
  /* Parses the command line arguments and packs them as an {options_t}. */

/* IMPLEMENTATIONS */

int main(int argc, char **argv)
  {
    /* Parse the command line options: */
    salamic_options_t *o = salamic_parse_options(argc, argv);

    /* Get the input triangle mesh {mesh}, quantizing to even integers: */
    stmesh_t mesh = stmesh_STL_read(o->modelFile, o->binary, o->eps, o->nfGuess, TRUE, o->preSorted);

    /* Get the Z-coordinates of the slicing planes, in increasing order: */
    i3_t minP, maxP; /* Bounding box of mesh. */
    stmesh_get_bounding_box(mesh, &minP, &maxP);
    bool_t uniform = ((! isnan(o->deltaZ)) && (o->deltaZ != 0));
    int_vec_t planeZ; /* The {Z}-coordinates of the slicing planes, quantized by {eps}. */
    if (uniform)
      { planeZ = salamic_planes_get_uniform(o->startZ, o->deltaZ, o->eps, minP.c[2], maxP.c[2]); }
    else
      { planeZ = salamic_planes_get_adaptive(o->planeZFile, o->eps, minP.c[2], maxP.c[2]); }

    /* Process it: */
    salamic_stats_t st;
    salamic_slicer_slice(mesh, o->preSorted, &planeZ, uniform, o->slicer, o->closer, o->outPrefix, &st);

    /* Show statistics: */
    salamic_stats_print(stderr, &st);

    fprintf(stderr, "done.\n");
    return 0;
  }

/* ARGUMENT PARSING */

#define salamic_eps_MIN (0.000001f)
  /* Minimum value of fundamental unit {eps} (mm). */
  
#define salamic_eps_MAX (1.0f)
  /* Maximum value of fundamental unit {eps} (mm). */
  
#define salamic_deltaZ_MINQ 10 
  /* Minimum plane spacing for uniform slicing, as multiple of fundamental unit {eps}. */
   
#define salamic_deltaZ_MAX 1000.0 
  /* Maximum plane spacing for uniform slicing (mm). */
 
#define salamic_startZ_MAX (10000.0f) 
  /* Max {Z} coordinate of reference plane (mm). */
  
#define salamic_startZ_DEFAULTQ 1 
  /* Default {Z} coordinate of start plane, an odd multiple of fundamental unit {eps}. */

#define salamic_nfGuess_MAX (500*1000*1000)
  /* Default guess for the number of faces in the mesh. */

#define salamic_nfGuess_DEFAULT 100000
  /* Maximum guess for the number of faces in the mesh. */

salamic_options_t *salamic_parse_options(int argc, char** argv)
  {
    /* Initialize argument parser: */
    argparser_t *pp = argparser_new(stderr, argc, argv);
    argparser_set_help(pp, PROG_NAME " version " PROG_VERS ", usage:\n" PROG_HELP);
    argparser_set_info(pp, PROG_INFO);
    argparser_process_help_info_options(pp);
    
    /* Allocate the command line argument record: */
    salamic_options_t *o = notnull(malloc(sizeof(salamic_options_t)), "no mem"); 
    
    /* Parse keyword parameters: */
    
    /* Fundamental unit: */
    argparser_get_keyword(pp, "-eps");
    o->eps = (float)argparser_get_next_double(pp, salamic_eps_MIN, salamic_eps_MAX);
    
    /* {Z}-coordinates of planes: */
    if (argparser_keyword_present(pp, "-planeZFile")) 
      { /* Read plane positions from file: */
        o->planeZFile = argparser_get_next_non_keyword(pp);
        o->deltaZ = NAN;
        o->startZ = NAN;
      }
    else
      { /* Uniform slicing: */
        argparser_get_keyword(pp, "-deltaZ");
        o->deltaZ = (float)argparser_get_next_double(pp, salamic_deltaZ_MINQ*o->eps, salamic_deltaZ_MAX);
        if (argparser_keyword_present(pp, "-startZ")) 
          { o->startZ = (float)argparser_get_next_double(pp, -salamic_startZ_MAX, +salamic_startZ_MAX); }
        else
          { o->startZ = o->eps; }
        o->planeZFile = NULL;
      }

    /* Model, format, and size guess: */
    argparser_get_keyword(pp, "-modelFile");
    o->modelFile = argparser_get_next_non_keyword(pp);

    argparser_get_keyword(pp, "-format");
    if (argparser_keyword_present_next(pp, "ascii"))
      { o->binary = FALSE; }
    else if (argparser_keyword_present_next(pp, "binary"))
      { o->binary = TRUE; }
    else
      { argparser_error(pp, "unrecognized file format"); }
      
    o->preSorted = argparser_keyword_present(pp, "-preSorted");
    
    if (argparser_keyword_present(pp, "-nfGuess")) 
      { o->nfGuess = (int)argparser_get_next_int(pp, 0, salamic_nfGuess_MAX); }
    else
      { o->nfGuess = salamic_nfGuess_DEFAULT; }
    
    /* Algorithms: */
    if (argparser_keyword_present(pp, "-slicer"))
      { o->slicer = argparser_get_next_non_keyword(pp); }
    else
      { o->slicer = "MINETTO"; }
    
    if (argparser_keyword_present(pp, "-closer"))
      { o->closer = argparser_get_next_non_keyword(pp); }
    else
      { o->closer = "STOLFI"; }
    
    argparser_get_keyword(pp, "-outPrefix");
    o->outPrefix = argparser_get_next_non_keyword(pp);
    
    /* Parse positional arguments: */
    argparser_skip_parsed(pp);

    /* Check for spurious arguments: */
    argparser_finish(pp);
    
    return o;
  }

