//********************************************************************************
//* File       : EarthPoints.hpp                                                 *
//* Author     : Mahlon R. Smith                                                 *
//*              Copyright (c) 2022-2026 Mahlon R. Smith, The Software Samurai   *
//*                 GNU GPL copyright notice below                               *
//* Date       : 31-Aug-2025                                                     *
//* Version    : (see AppVersion string in EarthPoints.cpp)                      *
//*                                                                              *
//* Description: EarthPoints class definition.                                   *
//*                                                                              *
//*                                                                              *
//********************************************************************************

//****************
//* Header Files *
//****************
#define EARTHPOINTS_DEFINED   //* Be sure file is included only once

#include <fstream>            //* File I/O streams
#include <cmath>              //* Math library
#include "AnsiCmd.hpp"        //* AnsiCmd class definition
#include "AnsiCmdWin.hpp"     //* ACWin class definition

//* ------------------------------------------------------ *
//* Enable/disable application-level debugging information.*
//* ------------------------------------------------------ *
#define DEBUG_EPNTS (1)       // Global debugging control
#if DEBUG_EPNTS != 0
#define DEBUG_PARSE (0)       // Debug file and command-line parsing
#if DEBUG_PARSE != 0
#define DEBUG_PVALUE (0)      // Debug the gclaParseValue() method
#endif   // DEBUG_PARSE
#define DEBUG_VERBOSE (0)     // Extended debugging information

static gString gsDebug ;      // Text formatting for debugging output
#endif   // DEBUG_EPNTS


//***************************
//* General constant values *
//***************************
typedef double dbl_t ;              // alias for 'double' type
typedef long double ldbl_t ;        // alias for 'long double' type

//* The value of PI as defined in 'math.h' ('include'd in 'cmath').            *
//*  # define M_PI   3.14159265358979323846                  // pi (8-byte)    *
//*  # define M_PIl  3.141592653589793238462643383279502884L // pi (IEEE quad) *
//* The full 16-byte 'long double' definition is used to initialize our local  *
//* value. This yields an extra 1.5 digits of precision in our calculations.   *
//* The controlling standard is IEEE-754.                                      *
// Programmer's Note: On most x86 hardware AND for GCC and Clang, 'double' and
// 'long double' are both implemented as eight(8)-byte values although sizeof()
// may report 'long double' as 16 bytes (indicating _alignment_, not precision).
// The "extended precision format" (80-bit precision) may be available within
// the floating point processor as intermediate data.
const ldbl_t m_pi = (ldbl_t)M_PIl ;

//* The number of degrees in half a circle *
const ldbl_t semiCircle = (ldbl_t)(180.0) ;

//* Number of radians in one degree *
const ldbl_t radsPerDeg = m_pi / semiCircle ;

//* Range checking for arc in degrees. *
const ldbl_t minDegrees = (ldbl_t)(-180.0) ;
const ldbl_t maxDegrees = (ldbl_t)(180.0) ;

//* Range checking for arc in radians. *
const ldbl_t minRadians = m_pi * -1.0 ;
const ldbl_t maxRadians = m_pi ;
//* (positive values only)             *
//const ldbl_t minRadians = (ldbl_t)(0.0) ;
//const ldbl_t maxRadians = (ldbl_t)(m_pi * 2.0) ;

//* Authalic Radius of the earth in kilometers and statute miles.     *
//* Source: Wikipedia                                                 *
//* https://en.wikipedia.org/wiki/Earth_radius#Arithmetic_mean_radius *
//* Minimum: 6,357 km (3,950 mi)                                      *
//* Maximum: 6,378 km (3,963 mi)                                      *
const ldbl_t eradKm = 6371.0072 ;
const ldbl_t eradMi = 3958.7603 ;

//* Maximum number of characters for 'gpsCoord::label' field.*
const short LABEL_LEN = 64 ;

//* Return values for GetCommandLineArgs(). ( 'userOption' ) *
enum clArgs : short 
{ clQuickHelp, clHelp, clHelpless, 
  clVersion, clVersiona, clArc, clConvert, clDump, clAnsi } ;

//*********************************************
//* Gather and process command-line arguments *
//*********************************************
class commArgs
{
   public:
      //* Initialization constructor *
   commArgs ( int argc, char** argv, char** argenv ) :
               argCount(argc), argList(argv), envList(argenv)
   {
      this->verFlag = this->helpFlag = false ;
   }

   //** Public Data Members **
   short    argCount ;           // application's 'argc'
   char**   argList ;            // application's 'argv'
   char**   envList ;            // application's 'argenv'

   TermConfig tConfig ;          // terminal configuration info (see AnsiCmd class)

   bool     verFlag ;            // --version: 'true' if display command-line version
   bool     helpFlag ;           // -H option: 'true' if display command-line help
} ;

//* Input format for a GPS coordinate value ('gpsValue:fmt' field).*
enum valFmt : short
{
   vfUnk = ZERO,  // format unknown
   vfDec,         // decimal degrees
   vfMin,         // integer degrees and decimal minutes
   vfSec,         // integer degrees, integer minutes and decimal seconds
   vfRad,         // decimal radians
} ;

//* Parsed and interpreted GPS coordinate value *
class gpsValue
{
   public:
   ~gpsValue ( void ) { }        // destructor

   gpsValue ( void )             // default constructor
   {
      this->reset () ;
   }

   void reset ( void )           // initialize data members
   {
      this->dec = (ldbl_t)(0.0) ;
      this->deg = (ldbl_t)(0.0) ;
      this->min = (ldbl_t)(0.0) ;
      this->sec = (ldbl_t)(0.0) ;
      this->rad = (ldbl_t)(0.0) ;
      this->fmt = vfUnk ;
      this->valid = false ;
   }

   //* Public Data Members *
   ldbl_t dec ;            // full value in decimal degrees
   ldbl_t deg ;            // value degrees component
   ldbl_t min ;            // value minutes component
   ldbl_t sec ;            // value seconds component
   ldbl_t rad ;            // value in radians
   valFmt fmt ;            // format of value as provided by user
   bool   valid ;          // 'true' if all values verified, else 'false'

} ;   // gpsValue

//* Parsed and interpreted GPS coordiate pair *
class gpsCoord
{
   public:
   ~gpsCoord ( void ) {}         // destructor

   gpsCoord ( void )             // default constructor
   {
      this->reset () ;
   }

   void reset ( void )           // initialize data members
   {
      this->label[0] = L'\0' ;
      this->valid = false ;
      this->lat.reset () ;
      this->lon.reset () ;
   }

   //* Public data members *
   wchar_t  label[LABEL_LEN] ;   // GPS location label (location name)
   gpsValue lat ;                // Latitude coordinate
   gpsValue lon ;                // Longitude coordinate
   bool     valid ;              // 'true' if lat/lon coordinates validated

} ;   // gpsCoord

//********************************
//* Application class definition *
//********************************

//* Application exit codes returned to shell. *
//* Used to display warning messages.         *
enum xCode : int
{
   xcGOOD = ZERO,    // success
   xcOOR  = -1,      // value out-of-range
   xcFNF  = -2,      // target file not found
   xcRNF  = -3,      // specified record not found in target file
   xcRSYN = -4,      // gps record syntax error
   xcFCMD = -5,      // bad format for "--file" command
} ;
//* Supported formulae for calculating the arc distance.*
//* See the 'formula' member.                           *
enum suppForm : short
{
   sfLambert,        // Lambert formula
   sfHaversine,      // Haversine formula
   sfAll,            // Calculate and report for all supported formulae
} ;
class EarthPoints
{
   public:
   ~EarthPoints ( void ) ;          // destructor
   EarthPoints ( commArgs& ca ) ;   // constructor

   //* GPS location and distance calculation methods.  *
   //* ----------------------------------------------- *
   //*                                                 *
   //* Calculate the distance between the two GPS      *
   //* location points using the specified calculation *
   //* formula or formulae.                            *
   void ArcDistance ( void ) ;
   bool CalculateArcLambert ( ldbl_t& adKm, ldbl_t& adMi, ldbl_t& caRad ) ;
   bool CalculateArcHaversine ( ldbl_t& adKm, ldbl_t& adMi ) ;
   //* Calculate the central angle using the haversine formula *
   ldbl_t havAngle ( ldbl_t latA, ldbl_t lonA, ldbl_t latB, ldbl_t lonB ) ;
   //* Calculate the central angle using the chord method *
   ldbl_t chordAngle ( ldbl_t latA, ldbl_t lonA, ldbl_t latB, ldbl_t lonB ) ;

   //* Format and display the arc-distance report.   *
   //* --------------------------------------------- *
   //* Display the report as TTY-style output.*
   void TTY_Report ( ldbl_t lamKm, ldbl_t lamMi, ldbl_t havKm, ldbl_t havMi ) ;
   //* Display the report in a window object. *
   void ACWinReport ( ldbl_t lamKm, ldbl_t lamMi, ldbl_t havKm, ldbl_t havMi ) ;
   //* Format for display, the degrees/radians source data.*
   void FormatSourceData ( gString& gsFmt, short& txtRows, short& txtCols, 
                           short maxWidth = ZERO ) ;
   //* Display value conversion data. (--convert option) *
   void DisplayConvData ( const gpsValue *valPtr, const char *title ) ;

   //* Calculation support methods *
   //* --------------------------- *
   ldbl_t Deg2Rad ( ldbl_t degree ) ;     // Convert degrees to radians
   //** Convert radians to degrees **
   ldbl_t Rad2Deg ( ldbl_t radian ) ;     // Convert radians to degrees
   ldbl_t degRange ( ldbl_t degree ) ;    // Range check degree values
   ldbl_t radRange ( ldbl_t radian ) ;    // Range check radian values

   //* General support methods *
   //* ----------------------- *
   clArgs GetCommandLineArgs ( commArgs& ca ) ;
   bool gclaParseRecord ( const gString& gsVal, gpsCoord *cPtr ) ;
   bool gclaParseValue ( const gString& gsVal, gpsValue *valPtr ) ;
   bool gclaScanRecordFile ( const gString& gsReq ) ;
   bool gclaExtractFileRecord ( const char *fspec, gString& gsRec, short indx = -1 ) ;
   bool gclaGetRecord ( ifstream& ifs, gString& gsRec, short indx = -1 ) ;
   bool gclaScanValue ( const gString& gsCmd, gpsValue *valPtr ) ;
   bool gclaSelectFormula ( const gString& gsCmd ) ;
   bool gclaColorScheme ( const gString& gsCmd, TermConfig& tCfg ) ;
   void Dump_GPS_File ( void ) ;
   void gclaSysInfo ( void ) ;
   void   SetWarningCode ( xCode warn ) ; // Set error/warning code
   void ComposeTitle ( gString& gs, bool tailNL = false, bool headNL = false ) ;
   void DisplayHelp ( clArgs userOption ) ;
   void DisplayVersion ( bool verbose ) ;

   void reset ( void )              // set all data members to default values
   {
      this->coord_a.reset() ;
      this->coord_b.reset() ;
      this->ansi = NULL ;           // not yet instantiated
      this->exitCode = xcGOOD ;     // assume success
      this->verboseOutput = false ; // brief output format
      this->windowOutput = false ;  // TTY report format
      this->formula = sfLambert ;   // default to Lambert formula
      this->fgndAttr = aesFG_DFLT ; // default foreground attribute
      this->bgndAttr = aesBG_DFLT ; // default background attribute
      this->bdrfAttr = acaFG_DFLT ; // window-output border foreground attribute
      this->bdrbAttr = acaBG_DFLT ; // window-output border background attribute
      this->winpos = { 1, 4 } ;     // window origin
      this->arcDistKm = 0.0 ;       // distances initially unknown
      this->arcDistMi = 0.0 ;
      this->arcTheta  = 0.0 ;       // central angle initially unknown
      *this->fspec = L'\0' ;        // filespece unknown
   }

   gpsCoord coord_a ;               // coordinate A latitude and longitude
   gpsCoord coord_b ;               // coordinate B latitude and longitude
   ldbl_t arcDistKm ;               // arc distance between A and B in kilometers
   ldbl_t arcDistMi ;               // arc distance between A and B in miles
   ldbl_t arcTheta ;                // central angle in radians

   AnsiCmd *ansi ;                  // direct all ANSI attributes through the AnsiCmd class

   char fspec[gsALLOCMIN] ;         // filespec for target files containing GPS coordinates
   xCode exitCode ;                 // application exit code
   suppForm formula ;               // formula to use for arc distance calculation
   aeSeq fgndAttr ;                 // application foreground attribute
   aeSeq bgndAttr ;                 // application background attribute
   acAttr bdrfAttr ;                // window border foreground attribute
   acAttr bdrbAttr ;                // window border background attribute
   WinPos winpos ;                  // position for windowed output (upper-left corner)
   bool  verboseOutput ;            // report verbose results of calculations
   bool  windowOutput ;             // format the report within an ACWin object

   //* Used only for testing of the AnsiCmd-class functionality.*
   dbgArgs act ;

} ;   // EarthPoints class

