// ==========================================================================
//         ____            _                     _____           _
//        / ___| _   _ ___| |_ ___ _ __ ___     |_   _|__   ___ | |___
//        \___ \| | | / __| __/ _ \ '_ ` _ \ _____| |/ _ \ / _ \| / __|
//         ___) | |_| \__ \ ||  __/ | | | | |_____| | (_) | (_) | \__ \.
//        |____/ \__, |___/\__\___|_| |_| |_|     |_|\___/ \___/|_|___/
//               |___/
//                             --- System-Tools ---
//                  https://www.nntb.no/~dreibh/system-tools/
// ==========================================================================
//
// Time to Unix Timestamp Conversion
// Copyright (C) 2026 by Thomas Dreibholz
//
// 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 3 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 <http://www.gnu.org/licenses/>.
//
// Contact: thomas.dreibholz@gmail.com

#define _GNU_SOURCE   /* for timegm() */
#include <ctype.h>
#include <getopt.h>
#include <locale.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#ifndef nullptr
#define nullptr NULL
#endif

#ifdef ENABLE_NLS
#include <libintl.h>
#else
#define bindtextdomain(domain, dirname) { }
#define textdomain(domain) { }
#define gettext(string) string
#define ngettext(singular, plural, n) ((n) == 1 ? (singular) : (plural))
#endif

#include "package-version.h"


// ###### Version ###########################################################
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 202000L)
[[ noreturn ]]
#endif
static void version(void)
{
   printf("time2unixts %s\n", SYSTEMTOOLS_VERSION);
   exit(0);
}


// ###### Usage #############################################################
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 202000L)
[[ noreturn ]]
#endif
static void usage(const char* program, const int exitCode)
{
   fprintf(stderr, "%s %s time_string [...]"
           " [-F|--float]"
           " [-I|--integer]"
           " [-H|--human-readable]"
           " [-s|--seconds]"
           " [-m|--milliseconds]"
           " [-u|--microseconds]"
           " [-n|--nanoseconds]"
           " [-h|--help]"
           " [-v|--version]\n",
           gettext("Usage:"), program);
   exit(exitCode);
}



// ###### Main program ######################################################
int main(int argc, char** argv)
{
   // ====== Initialise i18n support ========================================
   if(setlocale(LC_ALL, "") == nullptr) {
      setlocale(LC_ALL, "C.UTF-8");   // "C" should exist on all systems!
   }
   setlocale(LC_NUMERIC, "C.UTF-8");   // Use "." for fractional numbers!
   bindtextdomain("time2unixts", nullptr);
   textdomain("time2unixts");

   // ====== Handle arguments ===============================================
   static const struct option long_options[] = {
      { "float",                  no_argument, 0, 'F' },
      { "integer-decimal",        no_argument, 0, 'I' },
      { "integer-hexadecimal",    no_argument, 0, 'X' },
      { "integer-0x-hexadecimal", no_argument, 0, '0' },
      { "human-readable",         no_argument, 0, 'H' },
      { "seconds",                no_argument, 0, 's' },
      { "milliseconds",           no_argument, 0, 'm' },
      { "microseconds",           no_argument, 0, 'u' },
      { "nanoseconds",            no_argument, 0, 'n' },
      { "help",                   no_argument, 0, 'h' },
      { "version",                no_argument, 0, 'v' },
      {  nullptr,                 0,           0, 0   }
   };

   int          option;
   int          longIndex;
   int          useInteger    = 10;
   bool         humanReadable = false;
   unsigned int divideBy      = 1;
   const char*  unit          = "ns";
   while( (option = getopt_long(argc, argv, "FIX0Hsmunvh", long_options, &longIndex)) != -1 ) {
      switch(option) {
         case 'F':
            useInteger = 0;
          break;
         case 'I':
            useInteger = 10;
          break;
         case 'X':
            useInteger = 16;
          break;
         case '0':
            useInteger = -16;
          break;
         case 'H':
            humanReadable = true;
          break;
         case 'n':
            divideBy = 1;
            unit     = "ns";
          break;
         case 'u':
            divideBy = 1000;
            unit     = "µs";
          break;
         case 'm':
            divideBy = 1000000;
            unit     = "ms";
          break;
         case 's':
            divideBy = 1000000000;
            unit     = "s";
          break;
         case 'v':
            version();
          break;
         case 'h':
         case '?':
            // Exit with 0 on h/help, exit with 1 on '?' (unknown option):
            usage(argv[0], (option == 'h') ? 0 : 1);
          break;
         case '-':
          break;
         default:
            // This should not happen: wrong getopt parameters, or missing case?
            fprintf(stderr, "INTERNAL ERROR: Unhandled option c=%c code=%x!\n",
                    (isprint(option) ? (char)option : ' '), option);
            return 1;
          break;
      }
   }

   // ====== Obtain Unix timestamp in ns ====================================
   struct timespec ts;
   for(int i = optind; i <= argc; i++) {
      // ====== Use current time, if no date/time string is given ===========
      if(i == argc) {
         if(optind == argc) {
            if(clock_gettime(CLOCK_REALTIME, &ts) == -1) {
               perror(gettext("clock_gettime() failed"));
               exit(1);
            }
         }
         else {
            break;
         }
      }

      // ====== Parse the next date/time string =============================
      else {
         struct tm tm = { 0 };
         const char* remainder = strptime(argv[i], "%Y-%m-%d %H:%M:%S", &tm);
         if(remainder != nullptr) {
            ts.tv_sec = timegm(&tm);
            if(remainder[0] != 0x00) {
               char*        endptr;
               const double fraction = strtod(remainder, &endptr);
               if( (endptr == nullptr) || (*endptr != 0x00) || (fraction < 0.0) ) {
                  remainder = nullptr;   // parse error
               }
               else {
                  ts.tv_nsec = (long long)(fraction * 1e9);
               }
            }
            else {
               ts.tv_nsec = 0;
            }
         }
         if(remainder == nullptr) {
            fputs(gettext("ERROR: Invalid time string!"), stderr);
            fputs("\n", stderr);
            exit(1);
         }
      }

      // ====== Convert timespec to Unix timestamp =============================
      const long long unixTS =
         (1000000000LL * ts.tv_sec) + ts.tv_nsec;

      if(useInteger != 0) {
         if(useInteger == 16) {
            printf("%llx", unixTS / divideBy);
         }
         else if(useInteger == -16) {
            printf("0x%llx", unixTS / divideBy);
         }
         else {
            printf("%lld", unixTS / divideBy);
         }
      }
      else {
         const char* format;
         if(divideBy == 1000000000) {
            format = "%1.9Lf";
         }
         else if(divideBy == 1000000) {
            format = "%1.6Lf";
         }
         else if(divideBy == 1000) {
            format = "%1.3Lf";
         }
         else {
            format = "%1.0Lf";
         }
         printf(format, (long double)unixTS / (long double)divideBy);
      }
      if(humanReadable) {
         printf(gettext(" %s since the Unix Epoch"), unit);
      }
      fputs("\n", stdout);
   }

   return 0;
}
