//*************************************************************************
//
//  Test_StdVector (STL Container) - C++ language
//
//  Test_StdVector.cpp
//
//  Developed by Armando Giuseppe BONATTO MINELLA.
//
//*************************************************************************
#include <QtCore>
#include <iostream>
#include <iterator>
#include <time.h>
#include <unistd.h>
#include <vector>
//*************************************************************************
#define SOURCE_FILESIZE_MIN 42949672960ll
#define SEED_MIN                      0ll
#define SEED_MAX             8589934592ll
#define TIME_LIMIT_MIN                0ull // 0 = Unlimited
#define TIME_LIMIT_MAX         31536000ull // About one year
#define EXPONENT_MIN                 10
#define EXPONENT_MAX                 30
//*************************************************************************
typedef enum : std::int32_t
{
  Executable     = 0,
  SourceFilename = 1,
  Test           = 2,
  Seed           = 3,
  TimeLimit      = 4
} Arguments_t;
//*************************************************************************
typedef enum : std::int32_t
{
  FIRST_TEST         = 1,
  Prepend            = 1,
  InsertInTheMiddle  = 2,
  Append             = 3,
  RemoveTheFirstOne  = 4,
  RemoveTheMiddleOne = 5,
  RemoveTheLastOne   = 6,
  ScanByIndex        = 7,
  ScanByIterator     = 8,
  IndexByPointer     = 9,
  LAST_TEST          = 9
} Tests_t;
//*************************************************************************
typedef std::vector<std::uint64_t>           Container_t;
typedef std::vector<std::uint64_t>::iterator Iterator_t ;
//*************************************************************************
std::int32_t main (std::int32_t   argc   ,
                   char         * argv [] )
{
  bool valid = false;
  //-------------------------------------
  if (!((argc                                                            >  Arguments_t::TimeLimit) &&
        QFile::exists (QString (argv [Arguments_t::SourceFilename]))                                &&
        (QFile (QString (argv [Arguments_t::SourceFilename])).size ()    >= SOURCE_FILESIZE_MIN)    &&
        (QString (argv [Arguments_t::Test]).toInt (& (valid))            >= Tests_t::FIRST_TEST)    &&
        valid                                                                                       &&
        (QString (argv [Arguments_t::Test]).toInt (& (valid))            <= Tests_t::LAST_TEST)     &&
        valid                                                                                       &&
        (QString (argv [Arguments_t::Seed]).toLongLong (& (valid))       >= SEED_MIN)               &&
        valid                                                                                       &&
        (QString (argv [Arguments_t::Seed]).toLongLong (& (valid))       <= SEED_MAX)               &&
        valid                                                                                       &&
        (QString (argv [Arguments_t::TimeLimit]).toULongLong (& (valid)) <= TIME_LIMIT_MAX)         &&
        valid                                                                                         ))
  {
    std::cout << ""                                                                                                                                                           << std::endl
              << "WARNING: invalid command line arguments"                                                                                                                    << std::endl
              << ""                                                                                                                                                           << std::endl
              << "Usage: Test_StdVector <Source filename> <Test> <Seed> <Time limit>"                                                                                         << std::endl
              << "       +--> Source filename"                                                                                                                                << std::endl
              << "       |    +--> Minimum size of " << QString::asprintf ("%'lld",SOURCE_FILESIZE_MIN).toStdString () << " bytes"                                            << std::endl
              << "       +--> Test"                                                                                                                                           << std::endl
              << "       |    +--> 1 = Prepend"                                                                                                                               << std::endl
              << "       |    +--> 2 = Insert in the middle"                                                                                                                  << std::endl
              << "       |    +--> 3 = Append"                                                                                                                                << std::endl
              << "       |    +--> 4 = Remove the first one"                                                                                                                  << std::endl
              << "       |    +--> 5 = Remove the middle one"                                                                                                                 << std::endl
              << "       |    +--> 6 = Remove the last one"                                                                                                                   << std::endl
              << "       |    +--> 7 = Scan by index"                                                                                                                         << std::endl
              << "       |    +--> 8 = Scan by iterator"                                                                                                                      << std::endl
              << "       |    +--> 9 = Index by pointer"                                                                                                                      << std::endl
              << "       +--> Seed"                                                                                                                                           << std::endl
              << "       |    +--> From " << QString::asprintf ("%'lld",SEED_MIN).toStdString () << " to " << QString::asprintf ("%'lld",SEED_MAX).toStdString ()             << std::endl
              << "       +--> Time limit"                                                                                                                                     << std::endl
              << "            +--> From " << QString::asprintf ("%'llu",TIME_LIMIT_MIN).toStdString () << " to " << QString::asprintf ("%'llu",TIME_LIMIT_MAX).toStdString () << std::endl
              << "                 +--> 0              = unlimited"                                                                                                           << std::endl
              << "                 +--> Greater than 0 = time limit in seconds"                                                                                               << std::endl
              << ""                                                                                                                                                           << std::endl;
  }
  else
  {
    QString       sourceFilename                  = QString (argv [Arguments_t::SourceFilename])          ;
    std::int32_t  test                            = QString (argv [Arguments_t::Test]).toInt ()           ;
    std::int64_t  seed                            = QString (argv [Arguments_t::Seed]).toLongLong ()      ;
    std::uint64_t timeLimit                       = QString (argv [Arguments_t::TimeLimit]).toULongLong ();
    std::uint16_t endianness                      = 1u                                                    ;
    QFile         sourceFile     (sourceFilename)                                                         ;
    //-------------------------------------
    std::cout << "Container..: " << "std::vector (STL Container)" << std::endl;
    switch (test)
    {
      case Tests_t::Prepend:
        std::cout << "Test.......: Prepend" << std::endl;
        break;
      case Tests_t::InsertInTheMiddle:
        std::cout << "Test.......: Insert in the middle" << std::endl;
        break;
      case Tests_t::Append:
        std::cout << "Test.......: Append" << std::endl;
        break;
      case Tests_t::RemoveTheFirstOne:
        std::cout << "Test.......: Remove the first one" << std::endl;
        break;
      case Tests_t::RemoveTheMiddleOne:
        std::cout << "Test.......: Remove the middle one" << std::endl;
        break;
      case Tests_t::RemoveTheLastOne:
        std::cout << "Test.......: Remove the last one" << std::endl;
        break;
      case Tests_t::ScanByIndex:
        std::cout << "Test.......: Scan by index" << std::endl;
        break;
      case Tests_t::ScanByIterator:
        std::cout << "Test.......: Scan by iterator" << std::endl;
        break;
      case Tests_t::IndexByPointer:
        std::cout << "Test.......: Index by pointer" << std::endl;
        break;
      default:
        break;
    }
    std::cout << "Payload....: " << "8 bytes (64 bits)"                                                               << std::endl
              << "Source file: " << sourceFilename.toStdString ()                                                     << std::endl
              << "Endianness.: " << ((* ((std::uint8_t *) (& (endianness)))) ?
                                     "Little"                                :
                                     "Big"                                    ) << "-Endian"                          << std::endl
              << "Seed.......: " << QString::asprintf ("%'13lld",((long long) seed)).toStdString ()                   << std::endl
              << "Time limit.: " << ((timeLimit == 0ull)                                                           ?
                                     "Unlimited"                                                                   :
                                     QString::asprintf ("%'13llu",((unsigned long long) timeLimit)).toStdString ()  ) << std::endl
              << ""                                                                                                   << std::endl
              << "Elements      Time [s]                Digest (SHA-256)"                                             << std::endl;
    if (sourceFile.open (QIODeviceBase::ReadOnly))
    {
      for (std::int32_t exponent = EXPONENT_MIN;(exponent <= EXPONENT_MAX);++exponent)
      {
        std::uint64_t elements = (1ull << exponent);
        //-------------------------------------
        if (sourceFile.seek (seed))
        {
          QCryptographicHash hash      (QCryptographicHash::Algorithm::Sha256)          ;
          Container_t        container                                                  ;
          Iterator_t         iterator                                                   ;
          timespec           timeStart                                         = { 0,0 };
          timespec           timeStop                                          = { 0,0 };
          std::uint64_t      value                                             = 0ull   ;
          //-------------------------------------
          switch (test)
          {
            case Tests_t::Prepend:
              clock_gettime (CLOCK_PROCESS_CPUTIME_ID,
                             (& (timeStart))          );
              while (container.size () < elements)
              {
                sourceFile.read (((char *) (& (value))),
                                 sizeof (std::uint64_t) );
                container.insert (container.begin (),
                                  value              );
              }
              clock_gettime (CLOCK_PROCESS_CPUTIME_ID,
                             (& (timeStop))           );
              for (iterator = container.begin ();(iterator != container.end ());++iterator)
              {
                hash.addData (QByteArrayView (((char *) (& (* iterator))),
                                              sizeof (std::uint64_t)      ));
              }
              break;
            //-------------------------------------
            case Tests_t::InsertInTheMiddle:
              clock_gettime (CLOCK_PROCESS_CPUTIME_ID,
                             (& (timeStart))          );
              while (container.size () < elements)
              {
                sourceFile.read (((char *) (& (value))),
                                 sizeof (std::uint64_t) );
                iterator = (container.begin ()    +
                            (container.size () /
                             2ull               )  );
                container.insert (iterator,
                                  value    );
              }
              clock_gettime (CLOCK_PROCESS_CPUTIME_ID,
                             (& (timeStop))           );
              for (iterator = container.begin ();(iterator != container.end ());++iterator)
              {
                hash.addData (QByteArrayView (((char *) (& (* iterator))),
                                              sizeof (std::uint64_t)      ));
              }
              break;
            //-------------------------------------
            case Tests_t::Append:
              clock_gettime (CLOCK_PROCESS_CPUTIME_ID,
                             (& (timeStart))          );
              while (container.size () < elements)
              {
                sourceFile.read (((char *) (& (value))),
                                 sizeof (std::uint64_t) );
                container.push_back (value);
              }
              clock_gettime (CLOCK_PROCESS_CPUTIME_ID,
                             (& (timeStop))           );
              for (iterator = container.begin ();(iterator != container.end ());++iterator)
              {
                hash.addData (QByteArrayView (((char *) (& (* iterator))),
                                              sizeof (std::uint64_t)      ));
              }
              break;
            //-------------------------------------
            case Tests_t::RemoveTheFirstOne:
              while (container.size () < elements)
              {
                sourceFile.read (((char *) (& (value))),
                                 sizeof (std::uint64_t) );
                container.push_back (value);
              }
              clock_gettime (CLOCK_PROCESS_CPUTIME_ID,
                             (& (timeStart))          );
              while (container.size () > 0ull)
              {
                hash.addData (QByteArrayView (((char *) (& (container.front ()))),
                                              sizeof (std::uint64_t)              ));
                container.erase (container.begin ());
              }
              clock_gettime (CLOCK_PROCESS_CPUTIME_ID,
                             (& (timeStop))           );
              break;
            //-------------------------------------
            case Tests_t::RemoveTheMiddleOne:
              while (container.size () < elements)
              {
                sourceFile.read (((char *) (& (value))),
                                 sizeof (std::uint64_t) );
                container.push_back (value);
              }
              clock_gettime (CLOCK_PROCESS_CPUTIME_ID,
                             (& (timeStart))          );
              while (container.size () > 0ull)
              {
                iterator = (container.begin ()    +
                            (container.size () /
                             2ull               )  );
                hash.addData (QByteArrayView (((char *) (& (* iterator))),
                                              sizeof (std::uint64_t)      ));
                container.erase (iterator);
              }
              clock_gettime (CLOCK_PROCESS_CPUTIME_ID,
                             (& (timeStop))           );
              break;
            //-------------------------------------
            case Tests_t::RemoveTheLastOne:
              while (container.size () < elements)
              {
                sourceFile.read (((char *) (& (value))),
                                 sizeof (std::uint64_t) );
                container.push_back (value);
              }
              clock_gettime (CLOCK_PROCESS_CPUTIME_ID,
                             (& (timeStart))          );
              while (container.size () > 0ull)
              {
                hash.addData (QByteArrayView (((char *) (& (container.back ()))),
                                              sizeof (std::uint64_t)             ));
                container.pop_back ();
              }
              clock_gettime (CLOCK_PROCESS_CPUTIME_ID,
                             (& (timeStop))           );
              break;
            //-------------------------------------
            case Tests_t::ScanByIndex:
              while (container.size () < elements)
              {
                sourceFile.read (((char *) (& (value))),
                                 sizeof (std::uint64_t) );
                container.push_back (value);
              }
              clock_gettime (CLOCK_PROCESS_CPUTIME_ID,
                             (& (timeStart))          );
              for (std::uint64_t index = 0ull;(index < container.size ());++index)
              {
                hash.addData (QByteArrayView (((char *) (& (container [index]))),
                                              sizeof (std::uint64_t)             ));
              }
              clock_gettime (CLOCK_PROCESS_CPUTIME_ID,
                             (& (timeStop))           );
              break;
            //-------------------------------------
            case Tests_t::ScanByIterator:
              while (container.size () < elements)
              {
                sourceFile.read (((char *) (& (value))),
                                 sizeof (std::uint64_t) );
                container.push_back (value);
              }
              clock_gettime (CLOCK_PROCESS_CPUTIME_ID,
                             (& (timeStart))          );
              for (iterator = container.begin ();(iterator != container.end ());++iterator)
              {
                hash.addData (QByteArrayView (((char *) (& (* iterator))),
                                              sizeof (std::uint64_t)      ));
              }
              clock_gettime (CLOCK_PROCESS_CPUTIME_ID,
                             (& (timeStop))           );
              break;
            //-------------------------------------
            case Tests_t::IndexByPointer:
              while (container.size () < elements)
              {
                sourceFile.read (((char *) (& (value))),
                                 sizeof (std::uint64_t) );
                container.push_back (value);
              }
              clock_gettime (CLOCK_PROCESS_CPUTIME_ID,
                             (& (timeStart))          );
              for (iterator = container.begin ();(iterator != container.end ());++iterator)
              {
                std::uint64_t index = ((((std::uint64_t) (& (* iterator)))  -
                                        ((std::uint64_t) container.data ())  ) /
                                       sizeof (std::uint64_t)                   );
                hash.addData (QByteArrayView (((char *) (& (index))),
                                              sizeof (std::uint64_t) ));
              }
              clock_gettime (CLOCK_PROCESS_CPUTIME_ID,
                             (& (timeStop))           );
              break;
            //-------------------------------------
            default:
              break;
          }
          //-------------------------------------
          std::uint64_t timeElapsed = (((((std::uint64_t) timeStop.tv_sec) *
                                         1000000000ull                      ) +
                                        ((std::uint64_t) timeStop.tv_nsec)     )  -
                                       ((((std::uint64_t) timeStart.tv_sec) *
                                         1000000000ull                       ) +
                                        ((std::uint64_t) timeStart.tv_nsec)     )  );
          //-------------------------------------
          std::cout << QString::asprintf ("%'13llu",((unsigned long long) elements)).toStdString () << " "
                    << QString::asprintf ("%'23.9f",(((double) timeElapsed) /
                                                     1000000000.0            )).toStdString ()      << " "
                    << QString (hash.result ().toHex ()).toStdString ()                             << std::endl;
          if ((exponent  <  EXPONENT_MAX)      &&
              (timeLimit >  0ull)              &&
              (timeLimit <= (timeElapsed   /
                             1000000000ull  ))   )
          {
            do
            {
              elements *= 2ull;
              std::cout << QString::asprintf ("%'13llu",((unsigned long long) elements)).toStdString () << std::endl;
            }
            while (elements < (1ull << EXPONENT_MAX));
            break;
          }
        }
        else
        {
          std::cout << "ERROR: cannot seek source file ('" << sourceFilename.toStdString () << "')" << std::endl;
          sourceFile.close ();
          return (EXIT_FAILURE);
        }
      }
      sourceFile.close ();
    }
    else
    {
      std::cout << "ERROR: cannot open source file ('" << sourceFilename.toStdString () << "')" << std::endl;
      return (EXIT_FAILURE);
    }
    std::cout << std::endl;
  }
  return (EXIT_SUCCESS);
}
//*************************************************************************

