/*
 * ==========================================================================
 *         _   _      _   ____            __ __  __      _
 *        | \ | | ___| |_|  _ \ ___ _ __ / _|  \/  | ___| |_ ___ _ __
 *        |  \| |/ _ \ __| |_) / _ \ '__| |_| |\/| |/ _ \ __/ _ \ '__|
 *        | |\  |  __/ |_|  __/  __/ |  |  _| |  | |  __/ ||  __/ |
 *        |_| \_|\___|\__|_|   \___|_|  |_| |_|  |_|\___|\__\___|_|
 *
 *                  NetPerfMeter -- Network Performance Meter
 *                 Copyright (C) 2009-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:  dreibh@simula.no
 * Homepage: https://www.nntb.no/~dreibh/netperfmeter/
 */

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <getopt.h>
#include <iostream>
#include <unistd.h>

#if defined(HAVE_LIBIBERTY)
#include <libiberty.h>
extern "C" {
int getopt_long_only(int argc, char* const* argv, const char* optstring,
                     const struct option* longopts, int* longindex);
}
#endif

#include "inputfile.h"
#include "outputfile.h"
#include "package-version.h"


// ###### Check columns for proper quotation ################################
static bool checkColumns(const std::string& values)
{
   bool         inQuotation    = false;
   unsigned int quotationMarks = 0;
   for(size_t i = 0; i < values.size(); i++) {
      switch(values[i]) {
         case '\\':
            i++;
          break;
         case '"':
            inQuotation = !inQuotation;
            quotationMarks++;
          break;
      }
   }
   return ((quotationMarks & 1) == 0);
}


// ###### Read and process data file ########################################
void addDataFile(OutputFile&         outputFile,
                 const bool          withLineNumbers,
                 unsigned long long& outputLineNumber,
                 const std::string&  varNames,
                 const std::string&  varValues,
                 const std::string&  inputFileName,
                 const char*         separator = "\t")
{
   // ====== Open input file ================================================
   InputFile       inputFile;
   InputFileFormat inputFileFormat = IFF_Plain;
   if( (inputFileName.size() >= 4) &&
       ( (inputFileName.substr(inputFileName.size() - 4) == ".bz2") ||
         (inputFileName.substr(inputFileName.size() - 4) == ".BZ2")) ) {
       inputFileFormat = IFF_BZip2;
   }
   if(inputFile.initialize(inputFileName.c_str(), inputFileFormat) == false) {
      exit(1);
   }

   for(;;) {
      // ====== Read line from input file ===================================
      bool          eof;
      char          buffer[4097];
      const ssize_t bytesRead = inputFile.readLine(buffer, sizeof(buffer), eof);
      if((bytesRead < 0) || (eof)) {
         break;
      }

      // ====== Process line ================================================

      if(withLineNumbers) {
         if(inputFile.getLine() == 1) {
            if(outputLineNumber == 0) {
               if(outputFile.printf("%s%sSubLineNo%s%s\n", varNames.c_str(), separator, separator, buffer) == false) {
                  outputFile.finish();
                  exit(1);
               }
               outputLineNumber++;
            }
         }
         else {
            if(outputFile.printf("%llu%s%s%s%s\n",
                                 outputLineNumber, separator,
                                 varValues.c_str(), separator,
                                 buffer) == false) {
               outputFile.finish();
               exit(1);
            }
            outputLineNumber++;
         }
      }
      else {
         if(inputFile.getLine() == 1) {
            if(outputLineNumber == 0) {
               if(outputFile.printf("%s%s%s\n", varNames.c_str(), separator, buffer) == false) {
                  outputFile.finish();
                  exit(1);
               }
               outputLineNumber++;
            }
         }
         else {
            if(outputFile.printf("%s%s%s\n", varValues.c_str(), separator, buffer) == false) {
               outputFile.finish();
               exit(1);
            }
            outputLineNumber++;
         }
      }
   }

   inputFile.finish();
}


// ###### Version ###########################################################
[[ noreturn ]] static void version()
{
   std::cerr << "CombineSummaries" << " " << COMBINESUMMARIES_VERSION << "\n";
   exit(0);
}


// ###### Usage #############################################################
[[ noreturn ]] static void usage(const char* program, const int exitCode)
{
   std::cerr << "Usage:\n"
      << "* Run:\n  "
      << program << "\n"
         "    output_file\n"
         "    [variable_names]\n"
         "    [-c level|--compress level]\n"
         "    [-s separator|--separator separator]\n"
         "    [-l|--line-numbers|-n|--no-line-numbers]\n"
         "    [-q|--quiet]\n"
         "* Version:\n  " << program << " [-v|--version]\n"
         "* Help:\n  "    << program << " [-h|--help]\n";
   exit(exitCode);
}



// ###### Main program ######################################################
int main(int argc, char** argv)
{
   bool         quietMode        = false;
   bool         withLineNumbers  = false;
   unsigned int compressionLevel = 9;
   const char*  separator        = "\t";

   // ====== Handle command-line arguments ==================================
   const static struct option long_options[] = {
      { "compress",                  required_argument, 0, 'c' },
      { "separator",                 required_argument, 0, 's' },
      { "line-numbers",              no_argument,       0, 'l' },
      { "no-line-numbers",           no_argument,       0, 'n' },
      { "quiet",                     no_argument,       0, 'q' },

      { "help",                      no_argument,       0, 'h' },
      { "version",                   no_argument,       0, 'v' },
      {  nullptr,                    0,                 0, 0   }
   };

   int option;
   int longIndex;
   while( (option = getopt_long_only(argc, argv, "c:s:lnqhv", long_options, &longIndex)) != -1 ) {
      switch(option) {
         case 'c':
            compressionLevel = atol(optarg);
            if(compressionLevel < 1) {
               compressionLevel = 1;
            }
            else if(compressionLevel > 9) {
               compressionLevel = 9;
            }
          break;
         case 's':
            separator = optarg;
          break;
         case 'l':
            withLineNumbers = true;
          break;
         case 'n':
            withLineNumbers = false;
          break;
         case 'q':
            quietMode = true;
          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;
      }
   }
   if(optind >= argc) {
      usage(argv[0], 1);
   }
   const std::string outputFileName(argv[optind++]);
   std::string       varNames;
   if(optind < argc) {
      varNames = argv[optind++];
      if(!checkColumns(varNames)) {
         std::cerr << "ERROR: Invalid quotation in variable names string!\n";
         exit(1);
      }
   }
   if(optind < argc) {
      std::cerr << "ERROR: Invalid option " << argv[optind] << "!\n";
      usage(argv[0], 1);
   }


   // ====== Print information ==============================================
   if(!quietMode) {
      std::cout << "CombineSummaries " << COMBINESUMMARIES_VERSION << "\n"
                << "* Compression Level: " << compressionLevel << "\n"
                << "* Separator:         \"" << separator << "\"\n"
                << "* Line Numbers:      " << (withLineNumbers ? "yes" : "no") << "\n"
                << "\n";
   }


   // ====== Open output file ===============================================
   OutputFile       outputFile;
   OutputFileFormat outputFileFormat = OFF_Plain;
   if( (outputFileName.size() >= 4) &&
       ( (outputFileName.substr(outputFileName.size() - 4) == ".bz2") ||
         (outputFileName.substr(outputFileName.size() - 4) == ".BZ2")) ) {
       outputFileFormat = OFF_BZip2;
   }
   if(outputFile.initialize(outputFileName.c_str(), outputFileFormat,
                            compressionLevel)== false) {
      exit(1);
   }


   // ====== Process input ==================================================
   unsigned long long outputLineNumber = 0;
   std::string        varValues;
   std::string        simulationsDirectory(".");
   char               commandBuffer[4097];
   char*              command;

   if(!quietMode) {
      std::cout << "Ready> ";
      std::cout.flush();
   }
   while((command = fgets(commandBuffer, sizeof(commandBuffer), stdin))) {
      size_t length = strlen(command);
      if( (length > 0) && (command[length - 1] == '\n') ) {
         command[length - 1] = 0x00;
         length--;
      }

      if(length == 0) {
         if(!quietMode) {
            std::cout << "Ready> ";
            std::cout.flush();
         }
         continue;
      }

      if(!(strncmp(command, "--values=", 9))) {
         varValues = &command[9];
         if( (varValues.size() >= 2)    &&
             (varValues.front() == '"') &&
             (varValues.back() == '"') ) {
            varValues = varValues.substr(1, varValues.size() - 2);
         }
         if(!checkColumns(varValues)) {
            std::cerr << "ERROR: Invalid quotation in variable values string!\n";
            exit(1);
         }
      }
      else if(!(strncmp(command, "--input=", 8))) {
         if(varValues.empty()) {
            std::cerr << "ERROR: No values given (parameter --values=...)!\n";
            exit(1);
         }
         addDataFile(outputFile, withLineNumbers, outputLineNumber,
                     varNames, varValues,
                     simulationsDirectory + "/" + &command[8],
                     separator);
         varValues = "";
      }
      else if(!(strncmp(command, "--varnames=", 11))) {
         varNames = &command[11];
         if( (varNames.size() >= 2)    &&
             (varNames.front() == '"') &&
             (varNames.back() == '"') ) {
            varNames = varNames.substr(1, varNames.size() - 2);
         }
         if(!checkColumns(varNames)) {
            std::cerr << "ERROR: Invalid quotation in variable names string!\n";
            exit(1);
         }
      }
      else if(!(strncmp(command, "--simulationsdirectory=", 23))) {
         simulationsDirectory = &command[23];
      }
      else {
         std::cerr << "ERROR: Invalid command!\n";
         exit(1);
      }

      if(!quietMode) {
         std::cout << "Ready> ";
         std::cout.flush();
      }
   }


   // ====== Close output file ==============================================
   unsigned long long in, out;
   if(!outputFile.finish(true, &in, &out)) {
      exit(1);
   }
   if(!quietMode) {
      std::cout << "\n" << "Wrote " << outputLineNumber << " lines";
      if(in > 0) {
         std::cout << " (" << in << " -> " << out << " - "
                   << ((double)out * 100.0 / in) << "%)";
      }
      std::cout << "\n";
   }

   return 0;
}
