/* survexport.cc
 * Convert a processed survey data file to another format.
 */

/* Copyright (C) 1994-2026 Olly Betts
 * Copyright (C) 2004 John Pybus (SVG Output code)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see
 * <https://www.gnu.org/licenses/>.
 */

#include <config.h>

#define MSG_SETUP_PROJ_SEARCH_PATH 1

#include <ctype.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "export.h"
#include "mainfrm.h"

#include "cmdline.h"
#include "filename.h"
#include "img_for_survex.h"
#include "message.h"
#include "useful.h"

#include <iostream>
#include <string>

using namespace std;

int
main(int argc, char **argv)
{
   double pan = 0;
   double tilt = -90.0;
   export_format format = FMT_MAX_PLUS_ONE_;
   int show_mask = 0;
   const char *survey = NULL;
   double grid = 0.0; /* grid spacing (or 0 for no grid) */
   double text_height = DEFAULT_TEXT_HEIGHT; /* for station labels */
   double marker_size = DEFAULT_MARKER_SIZE; /* for station markers */
   double scale = 500.0;
   SurveyFilter* filter = NULL;

   {
       /* Default to .pos output if installed as 3dtopos. */
       char* progname = baseleaf_from_fnm(argv[0]);
       for (char * p = progname; *p; ++p) {
	   *p = tolower((unsigned char)*p);
       }
       if (strcmp(progname, "3dtopos") == 0) {
	   format = FMT_POS;
       }
       free(progname);
   }

   const int OPT_FMT_BASE = 20000;
   enum {
       OPT_SCALE = 0x100, OPT_BEARING, OPT_TILT, OPT_PLAN, OPT_ELEV,
       OPT_LEGS, OPT_SURF, OPT_SPLAYS, OPT_CROSSES, OPT_LABELS, OPT_ENTS,
       OPT_FIXES, OPT_EXPORTS, OPT_ANON_STNS, OPT_XSECT, OPT_WALLS, OPT_PASG,
       OPT_CENTRED, OPT_FULL_COORDS, OPT_CLAMP_TO_GROUND, OPT_DEFAULTS
   };
   static const struct option long_opts[] = {
	/* const char *name; int has_arg (0 no_argument, 1 required, 2 options_*); int *flag; int val */
	{"survey", required_argument, 0, 's'},
	{"scale", required_argument, 0, OPT_SCALE},
	{"bearing", required_argument, 0, OPT_BEARING},
	{"tilt", required_argument, 0, OPT_TILT},
	{"plan", no_argument, 0, OPT_PLAN},
	{"elevation", no_argument, 0, OPT_ELEV},
	{"legs", no_argument, 0, OPT_LEGS},
	{"surface-legs", no_argument, 0, OPT_SURF},
	{"splays", no_argument, 0, OPT_SPLAYS},
	{"crosses", no_argument, 0, OPT_CROSSES},
	{"station-names", no_argument, 0, OPT_LABELS},
	{"anonymous-stations", no_argument, 0, OPT_ANON_STNS},
	{"entrances", no_argument, 0, OPT_ENTS},
	{"fixes", no_argument, 0, OPT_FIXES},
	{"exports", no_argument, 0, OPT_EXPORTS},
	{"cross-sections", no_argument, 0, OPT_XSECT},
	{"walls", no_argument, 0, OPT_WALLS},
	{"passages", no_argument, 0, OPT_PASG},
	{"origin-in-centre", no_argument, 0, OPT_CENTRED},
	{"full-coordinates", no_argument, 0, OPT_FULL_COORDS},
	{"clamp-to-ground", no_argument, 0, OPT_CLAMP_TO_GROUND},
	{"defaults", no_argument, 0, OPT_DEFAULTS},
	{"grid", optional_argument, 0, 'g'},
	{"text-height", required_argument, 0, 't'},
	{"marker-size", required_argument, 0, 'm'},
	{"3d", no_argument, 0, OPT_FMT_BASE + FMT_3D},
	{"csv", no_argument, 0, OPT_FMT_BASE + FMT_CSV},
	{"dxf", no_argument, 0, OPT_FMT_BASE + FMT_DXF},
	{"eps", no_argument, 0, OPT_FMT_BASE + FMT_EPS},
	{"gpx", no_argument, 0, OPT_FMT_BASE + FMT_GPX},
	{"hpgl", no_argument, 0, OPT_FMT_BASE + FMT_HPGL},
	{"json", no_argument, 0, OPT_FMT_BASE + FMT_JSON},
	{"kml", no_argument, 0, OPT_FMT_BASE + FMT_KML},
	{"plt", no_argument, 0, OPT_FMT_BASE + FMT_PLT},
	{"pos", no_argument, 0, OPT_FMT_BASE + FMT_POS},
	{"shp-lines", no_argument, 0, OPT_FMT_BASE + FMT_SHP_LINES},
	{"shp-points", no_argument, 0, OPT_FMT_BASE + FMT_SHP_POINTS},
	{"svg", no_argument, 0, OPT_FMT_BASE + FMT_SVG},
	{"help", no_argument, 0, HLP_HELP},
	{"version", no_argument, 0, HLP_VERSION},
	// US spelling:
	{"origin-in-center", no_argument, 0, OPT_CENTRED},
	// Abbreviations:
	{"full-coords", no_argument, 0, OPT_FULL_COORDS},
	{"anon-stations", no_argument, 0, OPT_ANON_STNS},
	{0,0,0,0}
   };

#define short_opts "s:g::t:m:"

   static struct help_msg help[] = {
	/*			<-- */
	{HLP_ENCODELONG(0),   /*only load the sub-survey with this prefix*/199, 0, 0},
	/* TRANSLATORS: Used in survexport --help output.  The --scale option
	 * accepts any of these ways of specifying the scale.  These example
	 * input values should not be translated.
	 */
	{HLP_ENCODELONG(1),   /*scale (50, 0.02, 1:50 and 2:100 all mean 1:50)*/217, 0, 0},
	/* TRANSLATORS: Used in survexport --help output.  The --bearing option
	 * accepts any of these ways of specifying how much to rotate the
	 * survey about a vertical axis.  These example input values should not
	 * be translated.
	 */
	{HLP_ENCODELONG(2),   /*bearing (90, 90d, 100g all mean 90°)*/460, 0, 0},
	/* TRANSLATORS: Used in survexport --help output.  The --tilt option
	 * accepts any of these ways of specifying how much to rotate the
	 * survey about a horizontol axis.  These example input values should
	 * not be translated.
	 */
	{HLP_ENCODELONG(3),   /*tilt (45, 45d, 50g, 100% all mean 45°)*/461, 0, 0},
	/* TRANSLATORS: Don't translate example command line option --tilt=-90 */
	{HLP_ENCODELONG(4),   /*plan view (equivalent to --tilt=-90)*/462, 0, 0},
	/* TRANSLATORS: Don't translate example command line option --tilt=0 */
	{HLP_ENCODELONG(5),   /*elevation view (equivalent to --tilt=0)*/463, 0, 0},
	{HLP_ENCODELONG(6),   /*underground survey legs*/476, 0, 0},
	{HLP_ENCODELONG(7),   /*surface survey legs*/464, 0, 0},
	{HLP_ENCODELONG(8),   /*splay legs*/465, 0, 0},
	{HLP_ENCODELONG(9),   /*station markers*/474, 0, 0},
	{HLP_ENCODELONG(10),  /*station labels*/475, 0, 0},
	// TRANSLATORS: Option description in `survexport --help` output.
	{HLP_ENCODELONG(11),  /*anonymous stations*/538, 0, 0},
	{HLP_ENCODELONG(12),  /*entrances*/466, 0, 0},
	{HLP_ENCODELONG(13),  /*fixed points*/467, 0, 0},
	{HLP_ENCODELONG(14),  /*exported stations*/468, 0, 0},
	{HLP_ENCODELONG(15),  /*cross-sections*/469, 0, 0},
	{HLP_ENCODELONG(16),  /*walls*/470, 0, 0},
	{HLP_ENCODELONG(17),  /*passages*/471, 0, 0},
	{HLP_ENCODELONG(18),  /*origin in centre*/472, 0, 0},
	{HLP_ENCODELONG(19),  /*full coordinates*/473, 0, 0},
	{HLP_ENCODELONG(20),  /*clamp to ground*/478, 0, 0},
	{HLP_ENCODELONG(21),  /*include items exported by default*/155, 0, 0},
	{HLP_ENCODELONG(22),  /*generate grid (default %sm)*/148, STRING(DEFAULT_GRID_SPACING), 0},
	{HLP_ENCODELONG(23),  /*station labels text height (default %s)*/149, STRING(DEFAULT_TEXT_HEIGHT), 0},
	{HLP_ENCODELONG(24),  /*station marker size (default %s)*/152, STRING(DEFAULT_MARKER_SIZE), 0},
	{HLP_ENCODELONG(25),  /*produce Survex 3d output*/487, 0, 0},
	{HLP_ENCODELONG(26),  /*produce CSV output*/102, 0, 0},
	{HLP_ENCODELONG(27),  /*produce DXF output*/156, 0, 0},
	{HLP_ENCODELONG(28),  /*produce EPS output*/454, 0, 0},
	{HLP_ENCODELONG(29),  /*produce GPX output*/455, 0, 0},
	{HLP_ENCODELONG(30),  /*produce HPGL output*/456, 0, 0},
	{HLP_ENCODELONG(31),  /*produce JSON output*/457, 0, 0},
	{HLP_ENCODELONG(32),  /*produce KML output*/458, 0, 0},
	/* TRANSLATORS: "Compass" and "Carto" are the names of software packages,
	 * so should not be translated. */
	{HLP_ENCODELONG(33),  /*produce Compass PLT output for Carto*/159, 0, 0},
	{HLP_ENCODELONG(34),  /*produce Survex POS output*/459, 0, 0},
	{HLP_ENCODELONG(35),  /*produce Shapefile (lines) output*/525, 0, 0},
	{HLP_ENCODELONG(36),  /*produce Shapefile (points) output*/526, 0, 0},
	{HLP_ENCODELONG(37),  /*produce SVG output*/160, 0, 0},
	{0, 0, 0, 0}
   };

   msg_init(argv);

   string optmap[sizeof(show_mask) * CHAR_BIT];

   int long_index;
   bool always_include_defaults = false;
   cmdline_init(argc, argv, short_opts, long_opts, &long_index, help, 1, 2);
   while (1) {
      long_index = -1;
      int opt = cmdline_getopt();
      if (opt == EOF) break;
      int bit = 0;
      switch (opt) {
       case OPT_LEGS:
	 bit = LEGS;
	 break;
       case OPT_SURF:
	 bit = SURF;
	 break;
       case OPT_SPLAYS:
	 bit = SPLAYS;
	 break;
       case OPT_CROSSES:
	 bit = STNS;
	 break;
       case OPT_LABELS:
	 bit = LABELS;
	 break;
       case OPT_ENTS:
	 bit = ENTS;
	 break;
       case OPT_FIXES:
	 bit = FIXES;
	 break;
       case OPT_EXPORTS:
	 bit = EXPORTS;
	 break;
       case OPT_ANON_STNS:
	 bit = ANON_STNS;
	 break;
       case OPT_XSECT:
	 bit = XSECT;
	 break;
       case OPT_WALLS:
	 bit = WALLS;
	 break;
       case OPT_PASG:
	 bit = PASG;
	 break;
       case OPT_CENTRED:
	 bit = CENTRED;
	 break;
       case OPT_FULL_COORDS:
	 bit = FULL_COORDS;
	 break;
       case OPT_CLAMP_TO_GROUND:
	 bit = CLAMP_TO_GROUND;
	 break;
       case OPT_DEFAULTS:
	 always_include_defaults = true;
	 break;
       case 'g': /* Grid */
	 if (optarg) {
	    grid = cmdline_double_arg();
	 } else {
	    grid = (double)DEFAULT_GRID_SPACING;
	 }
	 bit = GRID;
	 break;
       case OPT_SCALE: {
	 char* colon = strchr(optarg, ':');
	 if (!colon) {
	     /* --scale=1000 => 1:1000 => scale = 1000 */
	     scale = cmdline_double_arg();
	     if (scale < 1.0) {
		 /* --scale=0.001 => 1:1000 => scale = 1000 */
		 scale = 1.0 / scale;
	     }
	 } else if (colon - optarg == 1 && optarg[0] == '1') {
	     /* --scale=1:1000 => 1:1000 => scale = 1000 */
	     optarg += 2;
	     scale = cmdline_double_arg();
	     optarg -= 2;
	 } else {
	     /* --scale=2:1000 => 1:500 => scale = 500 */
	     *colon = '\0';
	     scale = cmdline_double_arg();
	     optarg = colon + 1;
	     scale = cmdline_double_arg() / scale;
	     *colon = ':';
	 }
	 bit = SCALE;
	 break;
       }
       case OPT_BEARING: {
	 int units = 0;
	 size_t len = strlen(optarg);
	 if (len > 0) {
	     char ch = optarg[len - 1];
	     switch (ch) {
		 case 'd':
		 case 'g':
		     units = ch;
		     optarg[len - 1] = '\0';
		     break;
	     }
	     pan = cmdline_double_arg();
	     optarg[len - 1] = ch;
	 } else {
	     pan = cmdline_double_arg();
	 }
	 if (units == 'g') {
	     pan *= 0.9;
	 }
	 bit = ORIENTABLE;
	 break;
       }
       case OPT_TILT: {
	 int units = 0;
	 size_t len = strlen(optarg);
	 if (len > 0) {
	     char ch = optarg[len - 1];
	     switch (ch) {
		 case '%':
		 case 'd':
		 case 'g':
		     units = ch;
		     optarg[len - 1] = '\0';
		     break;
	     }
	     tilt = cmdline_double_arg();
	     optarg[len - 1] = ch;
	 } else {
	     tilt = cmdline_double_arg();
	 }
	 if (units == 'g') {
	     tilt *= 0.9;
	 } else if (units == '%') {
	     tilt = deg(atan(tilt * 0.01));
	 }
	 bit = ORIENTABLE;
	 break;
       }
       case OPT_PLAN:
	 tilt = -90.0;
	 bit = ORIENTABLE;
	 break;
       case OPT_ELEV:
	 tilt = 0.0;
	 bit = ORIENTABLE;
	 break;
       case 't': /* Text height */
	 text_height = cmdline_double_arg();
	 bit = TEXT_HEIGHT;
	 break;
       case 'm': /* Marker size */
	 marker_size = cmdline_double_arg();
	 bit = MARKER_SIZE;
	 break;
       case 's':
	 if (survey) {
	     if (!filter) {
		 filter = new SurveyFilter();
		 filter->add(survey);
	     }
	     filter->add(optarg);
	 } else {
	     survey = optarg;
	 }
	 break;
       default:
	 if (opt >= OPT_FMT_BASE && opt < OPT_FMT_BASE + FMT_MAX_PLUS_ONE_) {
	     format = export_format(opt - OPT_FMT_BASE);
	 }
      }
      if (bit) {
	  show_mask |= bit;
	  int i = 0;
	  while (((bit >> i) & 1) == 0) ++i;

	  if (!optmap[i].empty()) optmap[i] += ' ';

	  // Reconstruct what the command line option was.
	  if (long_index < 0) {
	      optmap[i] += '-';
	      optmap[i] += char(opt);
	      if (optarg) {
		  if (optarg == argv[optind - 1]) {
		      optmap[i] += ' ';
		  }
		  optmap[i] += optarg;
	      }
	  } else {
	      optmap[i] += "--";
	      optmap[i] += long_opts[long_index].name;
	      if (optarg) {
		  if (optarg == argv[optind - 1]) {
		      optmap[i] += ' ';
		  } else {
		      optmap[i] += '=';
		  }
		  optmap[i] += optarg;
	      }
	  }
      }
   }

   // A single --survey is handled by img at load-time.  Multiple --survey are
   // handled via a SurveyFilter at export time.
   if (filter) survey = NULL;

   const char* fnm_in = argv[optind++];
   const char* fnm_out = argv[optind];
   if (fnm_out) {
      if (format == FMT_MAX_PLUS_ONE_) {
	 // Select format based on extension.
	 size_t len = strlen(fnm_out);
	 // Length of longest extension of interest.
	 constexpr size_t MAX_EXT_LEN = 4;
	 char ext[MAX_EXT_LEN + 2];
	 for (size_t i = 0; i < MAX_EXT_LEN + 2; ++i) {
	     ext[i] = tolower((unsigned char)fnm_out[len - (MAX_EXT_LEN + 1) + i]);
	 }
	 for (size_t i = 0; i < FMT_MAX_PLUS_ONE_; ++i) {
	    const auto& info = export_format_info[i];
	    size_t l = strlen(info.extension);
	    if (len > l + 1 &&
		strcmp(ext + MAX_EXT_LEN + 1 - l, info.extension) == 0) {
	       // Shapefile (lines) will be selected for .shp, which is
	       // probably what's wanted.
	       format = export_format(i);
	       break;
	    }
	 }
	 if (format == FMT_MAX_PLUS_ONE_) {
	    fatalerror(/*Export format not specified and not known from output file extension*/252);
	 }
      }
   } else {
      if (format == FMT_MAX_PLUS_ONE_) {
	 fatalerror(/*Export format not specified*/253);
      }
      char *baseleaf = baseleaf_from_fnm(fnm_in);
      /* note : memory allocated by fnm_out gets leaked in this case... */
      fnm_out = add_ext(baseleaf, export_format_info[format].extension);
      free(baseleaf);
   }

   const auto& format_info_mask = export_format_info[format].mask;
   unsigned not_allowed = show_mask &~ format_info_mask;
   if (not_allowed) {
       printf("warning: The following options are not supported for this export format and will be ignored:\n");
       int i = 0;
       unsigned bit = 1;
       while (not_allowed) {
	   if (not_allowed & bit) {
	       // E.g. --walls maps to two bits in show_mask, but the options
	       // are only put on the least significant in such cases.
	       if (!optmap[i].empty())
		   printf("%s\n", optmap[i].c_str());
	       not_allowed &= ~bit;
	   }
	   ++i;
	   bit <<= 1;
       }
       show_mask &= format_info_mask;
   }

   if (always_include_defaults || show_mask == 0) {
       show_mask |= export_format_info[format].defaults;
   }

   if (!(format_info_mask & ORIENTABLE)) {
       pan = 0.0;
       tilt = -90.0;
   }

   Model model;
   int err = model.Load(fnm_in, survey);
   if (err) fatalerror(err, fnm_in);
   if (filter) filter->SetSeparator(model.GetSeparator());

   try {
       if (!Export(fnm_out, model.GetSurveyTitle(),
		   model, filter,
		   pan, tilt, show_mask, format,
		   grid, text_height, marker_size,
		   scale)) {
	  fatalerror(/*Couldn’t write file “%s”*/402, fnm_out);
       }
   } catch (const wxString & m) {
       wxString r = msg_appname();
       r += ": ";
       r += wmsg(/*error*/93);
       r += ": ";
       r += m;
       wcerr << r.c_str() << '\n';
   }

   return 0;
}
