// =============================================================================
//! \file
//! \brief Global functions and startup entry point
//!
//! Copyright (c) 2012-2022 by the developers. See the LICENSE file for details.


// =============================================================================
// Include headers

// Include this first for conditional compilation
extern "C"
{
#include "config.h"
}

// C++98
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <sstream>

// Local
extern "C"
{
#include "bdate.h"
#include "conf.h"
#include "core.h"
#include "digest.h"
#include "database.h"
#include "filter.h"
#include "group.h"
#include "hmac.h"
#include "inet.h"
#include "log.h"
#include "nls.h"
#include "sighandler.h"
#include "unicode.h"
}
#include "compression.hxx"
#include "main.hxx"
#include "tls.hxx"
#include "ui.hxx"


// =============================================================================
//! \defgroup MAIN MAIN: Command line option parser
//! @{


// =============================================================================
// Constants

//! \brief Message prefix for MAIN module
#define MAIN_ERR_PREFIX  "MAIN: "


// =============================================================================
// Variables

extern "C"
{
//! Enable additional debug output if nonzero
int  main_debug = 0;

//! Configuration directory path from command line option or \c NULL otherwise
const char*  main_confprefix = NULL;
}

//! Flag indicating that configuration was loaded
static int  main_config_loaded = 0;


// =============================================================================
//! Print program version information

static void  print_version(void)
{
   std::cout << CFG_NAME << " " << CFG_VERSION << " for " << CFG_OS << "\n"
#if defined(CFG_MODIFIED) && CFG_MODIFIED != 0
             << "(This is a modified version!)" << "\n"
#endif  // CFG_MODIFIED
             << "Unicode version: " << UC_VERSION << "\n"
             << "Compiled with options:"
#if CFG_USE_CLB
             << " CLB"
#endif  // CFG_USE_CLB
#if CFG_USE_FSC
             << " FSC"
#endif  // CFG_USE_FSC
#if CFG_USE_IP6
             << " IP6"
#endif  // CFG_USE_IP6
#if CFG_USE_XSI && !CFG_NLS_DISABLE
             << " NLS"
#endif  // CFG_USE_XSI && !CFG_NLS_DISABLE
#if  CFG_USE_POSIX_API >= 200112
             << " P2K"
#endif  // CFG_USE_POSIX_API >= 200112
#if CFG_USE_TLS
             << " TLS"
#endif  // CFG_USE_TLS
#if !CFG_DB_DISABLE
             << " XDBE"
#endif  // CFG_DB_DISABLE
#if CFG_USE_XSI
             << " XSI"
#endif  // CFG_USE_XSI
#if CFG_USE_ZLIB
             << " ZLIB"
#endif  // CFG_USE_ZLIB
             << "\n"
             << "Build: " << BDATE << std::endl;

}


// =============================================================================
// Print help

static void  print_help(void)
{
   std::cout << "Usage: " << CFG_NAME << " [options]\n"
             << "\n"
             << "Options:\n\n"
             << "   -4                       Force usage of IPv4 network "
             <<                             "protocol\n"
             << "   -confprefix path         Specify configuration directory "
             <<                             "path\n"
             << "   -debug                   Enable debug mode\n"
             << "   -display [host]:display  X window system display\n"
             << "   -geometry WxH[+X+Y]      Window size and position\n"
             << "   -h                       Print help message "
             <<                             "to standard output and exit\n"
             << "   -iconic                  Starts with minimized window\n"
             << "   -notooltips              Disable tooltip messages\n"
             << "   -v                       Print version information "
             <<                             "to standard output and exit\n"
             << "\n"
             << "The GUI style can be customized with the environment "
             << "variable FLTK_SCHEME.\n"
             << "(See manual page for details)"
             << std::endl;
}


// =============================================================================
// Save configuration and exit

static void  save_exit(int  ret)
{
   if(main_config_loaded)
   {
      if(EXIT_SUCCESS == ret)
      {
         // Save configuration
         conf_store(config);
      }
      // Release memory allocated for configuration data
      conf_delete(config);
   }

   // Release memory allocated for configuration path
   if(NULL != main_confprefix)
   {
      delete[] main_confprefix;
   }

   // Regular exit
   ts_environ_exit();
   std::exit(ret);
}


// =============================================================================
// Loop for normal execution

static void  loop(void)
{
   static const char  sbuf[] = "Terminate due to signal";

   while(1)
   {
      // Drive UI
      if(ui_exec())  { break; }

      // Check whether signal to exit is pending
      if(sighandler_check_abort())
      {
         // Yes
         std::clog << std::endl;
         std::cout << CFG_NAME << ": " << MAIN_ERR_PREFIX
                   << sbuf << std::endl << std::flush;
         break;
      }
   }
}


// =============================================================================
//! \brief Lock UI thread
//!
//! Exported as C style function
//!
//! \note
//! It is save to call this function for all threads, including the UI thread
//! itself.
//!
//! \attention
//! It is not allowed for a thread to call this function multiple times without
//! unlock between them!
//!
//! \return
//! - 0 on success
//! - Negative value on error

int  ts_lock_ui(void)
{
   int  res = 0;

   // Execute as NOP for UI thread itself
   if(!core_check_thread_ui())
   {
      res = ui_lock();
   }

   return(res);
}


// =============================================================================
//! \brief Unlock UI thread
//!
//! Exported as C style function
//!
//! \note
//! It is save to call this function for all threads, including the UI thread
//! itself.
//!
//! \return
//! - 0 on success
//! - Negative value on error

int  ts_unlock_ui(void)
{
   int  res = 0;

   // Execute as NOP for UI thread itself
   if(!core_check_thread_ui())
   {
      res = ui_unlock();
   }

   return(res);
}


// =============================================================================
//! \brief Print error message
//!
//! Exported as C style function
//!
//! \param[in] msg  String (Unicode error message)
//!
//! This function executes as NOP if \e msg is \e NULL .
//! Otherwise \e msg must be a NUL-terminated string.
//! <br>
//! The Unicode encoding of \e msg is checked and is allowed to be invalid.
//!
//! \attention
//! Currently only the US-ASCII subset of Unicode will be readable in output.
//!
//! \note
//! This function is guaranteed to work before other modules are initialized.

void  print_error(const char*  msg)
{
   char*  s;
   std::size_t  len;
   std::size_t  i;
   int  c;

   if(NULL != msg)
   {
      len = std::strlen(msg);
      s = new char[len + (std::size_t) 1];

      for(i = 0; i < len; ++i)
      {
         c = (int) msg[i];
         // Accept only printable, HT (9), LF (10, 0x0A) and SPACE (32, 0x20)
         if(!(9 == c || 10 == c || (32 <= c && 126 >= c)))  { s[i] = '?'; }
         else  { s[i] = msg[i]; }
      }
      s[len] = 0;
      std::clog << CFG_NAME << ": " << s << std::endl << std::flush;

      delete[] s;
   }
}


// =============================================================================
//! \brief Program entry point
//!
//! \param[in] argc  Number of command line arguments
//! \param[in] argv  Array containing command line argument strings
//!
//! Parse command line and control startup and shutdown
//!
//! \return
//! - \c EXIT_SUCCESS on success
//! - \c EXIT_FAILURE on error

int  main(int  argc, char**  argv)
{
   // List of options that must not be passed to FLTK
   static const char*  options_to_remove[] =
   {
      "-debug", "-confprefix", "-4", NULL
   };
   int  rv;
   enum { OK, ERROR, ABORT }  rv2 = OK;
   char  option = 0;               // Flag indicating option value will follow
   unsigned int  i, j, k;
   int  removed;
   char*  tmp;

   ts_environ_init();

   // Install signal handlers
   // Note:
   // This is done in a separate C module because POSIX requires a C compiler
   rv = sighandler_install();
   if(0 > rv)  rv2 = ERROR;

   // Delete old logfile
   if(OK == rv2)  { log_delete_logfile(); }

   // Process parameters
   if (OK == rv2)
   {
      for(i = 1; i < (unsigned int) argc; i++)
      {
         if(!std::strcmp(argv[i], "-v") || !std::strcmp(argv[i], "--version"))
         {
            // Print version information and report success
            print_version();
            rv2 = ABORT;
            break;
         }
         else if(!std::strcmp(argv[i], "-h") || !std::strcmp(argv[i], "--help"))
         {
            // Print help and report success
            print_help();
            rv2 = ABORT;
            break;
         }
         else if(!std::strcmp(argv[i], "-debug"))  // Not for FLTK
         {
            main_debug = 1;
         }
         else if(!std::strcmp(argv[i], "-confprefix"))  // Not for FLTK
         {
            option = 2;
         }
         else if(!std::strcmp(argv[i], "-4"))  // Not for FLTK
         {
            // Set flag to force IPv4 network protocol
            inet_force_ipv4 = 1;
         }
         else if(!std::strcmp(argv[i], "-display")
                 || !std::strcmp(argv[i], "-geometry")
                 || !std::strcmp(argv[i], "-iconic")
                 || !std::strcmp(argv[i], "-notooltips"))
         {
            // Handled by FLTK
            option = 1;
         }
         else if(option)
         {
            if(2 == option)
            {
               // Special handling for option "-confprefix"
               tmp = new char[std::strlen(argv[i]) + (std::size_t) 1];
               std::strcpy(tmp, argv[i]);
               main_confprefix = tmp;
            }
            option = 0;
         }
         else if('-' == argv[i][0])
         {
            // Unknown option
            PRINT_ERROR("Unknown option");
            rv2 = ERROR;
            break;
         }
      }
      if(ERROR == rv2)
      {
         PRINT_ERROR("Use '-h' or read the man page for help");
      }
   }
   if(OK == rv2 && main_debug)
   {
      std::cout << CFG_NAME << ": " << MAIN_ERR_PREFIX
                << "Debug mode enabled" << std::endl << std::flush;
      if(inet_force_ipv4)
      {
         std::cout << CFG_NAME << ": " << MAIN_ERR_PREFIX
                   << "Force IPv4 network protocol" << std::endl << std::flush;
      }
   }

   // Remove options that are not supported by FLTK
repeat:
   removed = 0;
   for(i = 1; i < (unsigned int) argc; i++)
   {
      j = 0;
      while(NULL != options_to_remove[j])
      {
         if(!std::strcmp(argv[i], options_to_remove[j]))
         {
            // Found option to remove
            for (k = i; k < (unsigned int) argc; k++)
            {
               argv[k] = argv[k + 1U];
            }
            --argc;
            // Remove potential value of option too
            if(i < (unsigned int) argc && '-' != argv[i][0])
            {
               for (k = i; k < (unsigned int) argc; k++)
               {
                  argv[k] = argv[k + 1U];
               }
               --argc;
            }
            removed = 1;
            break;
         }
         ++j;
      }
      // Repeat loop because argc has changed
      if(removed)  { break; }
   }
   if(removed)  { goto repeat; }
   if(OK == rv2 && main_debug)
   {
      std::cout << CFG_NAME << ": " << MAIN_ERR_PREFIX
                << "Options passed to FLTK: ";
      for(i = 1; i < (unsigned int) argc; i++)
      {
         std::cout << argv[i] << " ";
      }
      std::cout << std::endl << std::flush;
   }

   // Load configuration (after options are parsed because of "-confprefix")
   if(OK == rv2)
   {
      if(main_debug && NULL != main_confprefix)
      {
         std::cout << CFG_NAME << ": " << MAIN_ERR_PREFIX
                   << "Configuration directory path: "
                   << main_confprefix << std::endl << std::flush;
      }
      rv = conf_load(config);
      if(0 != rv)
      {
         rv2 = ERROR;
      }
      else
      {
         // Valid default configuration is sufficient
         main_config_loaded = 1;
      }
   }

   // Check what to do
   switch(rv2)
   {
      case OK:
      {
         // Normal startup into user interface
         // Attention:
         // All modules that spawn additional threads must be initialized by the
         // UI, not here!
         nls_init();  // Don't abort on error, the NLS module will always work
         if(0 > filter_init(ui_get_locale_utf8()))  // Do this after NLS init
         {
            rv = EXIT_FAILURE;
            break;
         }
#if !CFG_CMPR_DISABLE
         if(0 > cmpr_init())
         {
            rv = EXIT_FAILURE;
            break;
         }
#endif  // !CFG_CMPR_DISABLE
#if CFG_USE_TLS
         if(0 > tls_init())
         {
            rv = EXIT_FAILURE;
            break;
         }
#endif  // CFG_USE_TLS
         digest_init();
         hmac_init();
         group_init();
         if(0 > db_init())
         {
            rv = EXIT_FAILURE;
            break;
         }
         // Core init is handled by UI
         ui_init(argc, argv);
         loop();
         std::cout << CFG_NAME << ": " << MAIN_ERR_PREFIX
                   << "Shutdown" << std::endl << std::flush;
         // Core shutdown is handled by UI
         ui_exit();
         db_exit();
         group_exit();
         hmac_exit();
         digest_exit();
#if CFG_USE_TLS
         tls_exit();
#endif  // CFG_USE_TLS
#if !CFG_CMPR_DISABLE
         cmpr_exit();
#endif  // !CFG_CMPR_DISABLE
         filter_exit();
         nls_exit();
         rv = EXIT_SUCCESS;
         break;
      }
      case ABORT:
      {
         // Nothing more to do, but no error
         rv = EXIT_SUCCESS;
         break;
      }
      default:
      {
         // Error
         rv = EXIT_FAILURE;
         break;
      }
   }

   save_exit(rv);
   // We should never be here
   PRINT_ERROR("Fatal error: Hit end of main()");
}


//! @}

// EOF
