//*************************************************************************
//
//  Basic_Benchmark_EntangledList (EntangledChain Algorithm) - C++ language
//
//  Basic_Benchmark_EntangledList.cpp
//
//  Developed by Armando Giuseppe BONATTO MINELLA.
//
//*************************************************************************
#include <QtCore>
#include <iostream>
#include <time.h>
#include <unistd.h>
#include "EntangledList.h"
//*************************************************************************
#define SOURCE_FILESIZE_MIN   42949672960ll
#define ELEMENTS_EXPONENT_MIN           0
#define ELEMENTS_EXPONENT_MAX          30
#define SEED_MIN                        0ll
#define SEED_MAX               8589934592ll
#define ROUNDS_EXPONENT                30
//*************************************************************************
typedef enum : std::int32_t
{
  Executable       = 0,
  SourceFilename   = 1,
  ElementsExponent = 2,
  Seed             = 3
} Arguments_t;
//*************************************************************************
typedef EntangledList_Alpha_Page<std::uint64_t>       Container_t;
typedef EntangledList_Alpha_Page<std::uint64_t>::NODE Node_t     ;
//*************************************************************************
std::int32_t main (std::int32_t   argc   ,
                   char         * argv [] )
{
  bool valid = false;
  //-------------------------------------
  if (!((argc                                                             >  Arguments_t::Seed)     &&
        QFile::exists (QString (argv [Arguments_t::SourceFilename]))                                &&
        (QFile (QString (argv [Arguments_t::SourceFilename])).size ()     >= SOURCE_FILESIZE_MIN)   &&
        (QString (argv [Arguments_t::ElementsExponent]).toInt (& (valid)) >= ELEMENTS_EXPONENT_MIN) &&
        valid                                                                                       &&
        (QString (argv [Arguments_t::ElementsExponent]).toInt (& (valid)) <= ELEMENTS_EXPONENT_MAX) &&
        valid                                                                                       &&
        (QString (argv [Arguments_t::Seed]).toLongLong (& (valid))        >= SEED_MIN)              &&
        valid                                                                                       &&
        (QString (argv [Arguments_t::Seed]).toLongLong (& (valid))        <= SEED_MAX)              &&
        valid                                                                                         ))
  {
    std::cout << ""                                                                                                                                                                     << std::endl
              << "WARNING: invalid command line arguments"                                                                                                                              << std::endl
              << ""                                                                                                                                                                     << std::endl
              << "Usage: Basic_Benchmark_EntangledList <Source filename> <Elements exponent> <Seed>"                                                                                    << std::endl
              << "       +--> Source filename"                                                                                                                                          << std::endl
              << "       |    +--> Minimum size of " << QString::asprintf ("%'lld",SOURCE_FILESIZE_MIN).toStdString () << " bytes"                                                      << std::endl
              << "       +--> Elements exponent (power of 2)"                                                                                                                           << std::endl
              << "       |    +--> From " << QString::asprintf ("%'d",ELEMENTS_EXPONENT_MIN).toStdString () << " to " << QString::asprintf ("%'d",ELEMENTS_EXPONENT_MAX).toStdString () << std::endl
              << "       +--> Seed"                                                                                                                                                     << std::endl
              << "            +--> From " << QString::asprintf ("%'lld",SEED_MIN).toStdString () << " to " << QString::asprintf ("%'lld",SEED_MAX).toStdString ()                       << std::endl
              << ""                                                                                                                                                                     << std::endl;
  }
  else
  {
    QString       sourceFilename                    = QString (argv [Arguments_t::SourceFilename])           ;
    std::int32_t  elementsExponent                  = QString (argv [Arguments_t::ElementsExponent]).toInt ();
    std::int64_t  seed                              = QString (argv [Arguments_t::Seed]).toLongLong ()       ;
    std::uint64_t elements                          = (1ull << elementsExponent)                             ;
    std::uint64_t rounds                            = (1ull << ROUNDS_EXPONENT)                              ;
    std::uint16_t endianness                        = 1u                                                     ;
    QFile         sourceFile       (sourceFilename)                                                          ;
    //-------------------------------------
    std::cout << "Container..: " << "EntangledList_Alpha (Allocator : Page)"                                     << std::endl
              << "Benchmark..: " << "Basic"                                                                      << std::endl
              << "Payload....: " << "8 bytes (64 bits)"                                                          << std::endl
              << "Source file: " << sourceFilename.toStdString ()                                                << std::endl
              << "Endianness.: " << ((* ((std::uint8_t *) (& (endianness)))) ?
                                     "Little"                                :
                                     "Big"                                    ) << "-Endian"                     << std::endl
              << "Elements...: " << QString::asprintf ("%'13llu",((unsigned long long) elements)).toStdString () << std::endl
              << "Seed.......: " << QString::asprintf ("%'13lld",((long long) seed)).toStdString ()              << std::endl
              << "Rounds.....: " << QString::asprintf ("%'13llu",((unsigned long long) rounds)).toStdString ()   << std::endl
              << "Time limit.: " << "Unlimited"                                                                  << std::endl
              << ""                                                                                              << std::endl
              << "Rounds        Time [s]                Digest (SHA-256)"                                        << std::endl;
    if (sourceFile.open (QIODeviceBase::ReadOnly))
    {
      if (sourceFile.seek (seed))
      {
        QCryptographicHash hash        (QCryptographicHash::Algorithm::Sha256)          ;
        Container_t        container                                                    ;
        timespec           timeStart                                           = { 0,0 };
        timespec           timeStop                                            = { 0,0 };
        std::uint64_t      timeElapsed                                         = 0ull   ;
        std::uint64_t      counter                                             = 0ull   ;
        std::uint64_t      index                                               = 0ull   ;
        std::uint64_t      value                                               = 0ull   ;
        std::uint64_t      nextDigest                                          = 1ull   ;
        //-------------------------------------
        // Preload container and output initial digest
        //-------------------------------------
        while (container.count < elements)
        {
          sourceFile.read (((char *) (& (value))),
                           sizeof (std::uint64_t) );
          container.Append (value);
          hash.addData (QByteArrayView (((char *) (& (value))),
                                        sizeof (std::uint64_t) ));
        }
        std::cout << "            0"                                  << " "
                  << "            0.000000000"                        << " "
                  << QString (hash.result ().toHex ()).toStdString () << std::endl;
        //-------------------------------------
        clock_gettime (CLOCK_PROCESS_CPUTIME_ID,
                       (& (timeStart))          );
        while (counter < rounds)
        {
          //-------------------------------------
          // Remove
          //-------------------------------------
          sourceFile.read (((char *) (& (index))),
                           sizeof (std::uint64_t) );
          container.RemoveAt (index    %
                              elements  );
          //-------------------------------------
          // Insert
          //-------------------------------------
          sourceFile.read (((char *) (& (index))),
                           sizeof (std::uint64_t) );
          sourceFile.read (((char *) (& (value))),
                           sizeof (std::uint64_t) );
          container.InsertAt ((index    %
                               elements  ),
                              value        );
          //-------------------------------------
          ++counter;
          if (counter == nextDigest)
          {
            clock_gettime (CLOCK_PROCESS_CPUTIME_ID,
                           (& (timeStop))           );
            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)     )  );
            hash.reset ();
            for (Node_t * nodePtr = container.First ();(nodePtr != nullptr);nodePtr = nodePtr->Next ())
            {
              hash.addData (QByteArrayView (((char *) (& (nodePtr->payload))),
                                            sizeof (std::uint64_t)            ));
            }
            std::cout << QString::asprintf ("%'13llu",((unsigned long long) counter)).toStdString () << " "
                      << QString::asprintf ("%'23.9f",(((double) timeElapsed) /
                                                       1000000000.0            )).toStdString ()     << " "
                      << QString (hash.result ().toHex ()).toStdString ()                            << std::endl;
            nextDigest <<= 1ull;
            clock_gettime (CLOCK_PROCESS_CPUTIME_ID,
                           (& (timeStart))          );
          }
        }
      }
      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);
}
//*************************************************************************

