/*! @file TraceReaderLogQEMU.cc
 *  @brief TraceReaderLogQEMU Implementation
 *
 *  This file contains the implementation of the functions supporting
 *  reading the QEMU coverage data files.
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <string.h>
#include <getopt.h>
#include <signal.h>
#include <unistd.h>

#include <rld.h>
#include <rld-process.h>

#include "qemu-log.h"
#include "TraceReaderLogQEMU.h"
#include "TraceWriterQEMU.h"
#include "TraceList.h"
#include "ObjdumpProcessor.h"
#include "TargetFactory.h"

#if defined(_WIN32) || defined(__CYGWIN__)
  #define kill(p,s) raise(s)
#endif

std::string progname;

void usage()
{
  std::cerr << "Usage: "
            << progname
            << " [-v] -c CPU -e executable -t tracefile [-E logfile]"
            << std::endl;
  exit( 1 );
}

static void fatal_signal( int signum )
{
  signal( signum, SIG_DFL );

  rld::process::temporaries_clean_up();

  /*
   * Get the same signal again, this time not handled, so its normal effect
   * occurs.
   */
  kill( getpid(), signum );
}

static void setup_signals()
{
  if ( signal (SIGINT, SIG_IGN) != SIG_IGN ) {
    signal( SIGINT, fatal_signal );
  }
#ifdef SIGHUP
  if ( signal( SIGHUP, SIG_IGN ) != SIG_IGN ) {
    signal( SIGHUP, fatal_signal );
  }
#endif
  if ( signal( SIGTERM, SIG_IGN ) != SIG_IGN ) {
    signal( SIGTERM, fatal_signal );
  }
#ifdef SIGPIPE
  if ( signal( SIGPIPE, SIG_IGN ) != SIG_IGN ) {
    signal( SIGPIPE, fatal_signal );
  }
#endif
#ifdef SIGCHLD
  signal( SIGCHLD, SIG_DFL );
#endif
}

int main(
  int    argc,
  char** argv
)
{
  int                                 opt;
  Trace::TraceReaderLogQEMU           log;
  Trace::TraceWriterQEMU              trace;
  std::string                         cpuname;
  std::string                         executable;
  std::string                         tracefile;
  std::string                         logname = "/tmp/qemu.log";
  Coverage::ExecutableInfo*           executableInfo;
  Coverage::DesiredSymbols            symbolsToAnalyze;
  bool                                verbose = false;
  std::string                         dynamicLibrary;
  int                                 ec = 0;
  std::shared_ptr<Target::TargetBase> targetInfo;
  rld::process::tempfile              *objdumpFile;
  rld::process::tempfile              *err;

  try
  {
    objdumpFile = new rld::process::tempfile( ".dmp" );
  }
  catch ( rld::error re )
  {
    std::cerr << "Failed to make .dmp tempfile " << std::endl;
    ec = 10;

    return ec;
  }

  try
  {
    err = new rld::process::tempfile( ".err" );
  }
  catch ( rld::error re )
  {
    std::cerr << "Failed to make .err tempfile " << std::endl;
    ec = 10;

    return ec;
  }

  setup_signals();

   //
   // Process command line options.
   //
  progname = argv[0];

  while ( (opt = getopt( argc, argv, "c:e:l:L:t:v" ) ) != -1 ) {
    switch ( opt ) {
      case 'c': cpuname        = optarg; break;
      case 'e': executable     = optarg; break;
      case 'l': logname        = optarg; break;
      case 'L': dynamicLibrary = optarg; break;
      case 't': tracefile      = optarg; break;
      case 'v': verbose        = true;   break;
      default: usage();
    }
  }

  // Make sure we have all the required parameters
  if ( cpuname.empty() ) {
    std::cerr << "cpuname not specified" << std::endl;
    usage();
  }

  if ( executable.empty() ) {
    std::cerr << "executable not specified" << std::endl;
    usage();
  }

  if ( tracefile.empty() ) {
    std::cerr << "output trace file not specified" << std::endl;
    usage();
  }


  // Create toolnames.
  try
  {
    targetInfo.reset( Target::TargetFactory( cpuname ) );
  }
  catch ( rld::error re )
  {
    std::cerr << "error: "
              << re.where << ": " << re.what
              << std::endl;
    ec = 10;

    return ec;
  }

  Coverage::ObjdumpProcessor objdumpProcessor( symbolsToAnalyze, targetInfo );

  if ( !dynamicLibrary.empty() ) {
    executableInfo = new Coverage::ExecutableInfo(
      executable.c_str(),
      dynamicLibrary,
      false,
      symbolsToAnalyze
    );
  } else {
    try
    {
      executableInfo = new Coverage::ExecutableInfo(
        executable.c_str(),
        "",
        false,
        symbolsToAnalyze
      );
    }
    catch ( rld::error re )
    {
      std::cerr << "error: "
                << re.where << ": " << re.what
                << std::endl;
      ec = 10;
    }
  }

  // If a dynamic library was specified, determine the load address.
  if ( !dynamicLibrary.empty() ) {
    try
    {
      executableInfo->setLoadAddress(
        objdumpProcessor.determineLoadAddress( executableInfo )
      );
    }
    catch ( rld::error re )
    {
      std::cerr << "error: "
                << re.where << ": " << re.what
                << std::endl;
      ec = 10;

      return ec;
    }
  }

  try
  {
    objdumpProcessor.loadAddressTable( executableInfo, *objdumpFile, *err );
    log.processFile( logname.c_str(), objdumpProcessor );
    trace.writeFile( tracefile.c_str(), &log, verbose );
  }
  catch ( rld::error re )
  {
    std::cerr << "error: "
              << re.where << ": " << re.what
              << std::endl;
    ec = 10;
  }

  return ec;
}
