#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <netdb.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include "CoverageFactory.h"
#include "CoverageMap.h"

/*
 *  Variables to control global behavior
 */
Coverage::CoverageReaderBase* coverageReader = NULL;
Coverage::CoverageWriterBase* coverageWriter = NULL;
uint32_t                      highAddress = 0xffffffff;
Coverage::CoverageFormats_t   inputFormat;
uint32_t                      lowAddress  = 0xffffffff;
char*                         progname;

/*
 *  Global variables for the program
 */
Coverage::CoverageMapBase    *CoverageMap    = NULL;

/*
 *  Convert string to int with status out
 */

bool stringToUint32(
  const char* s,
  int         base,
  uint32_t*   n
)
{
  long long result;

  if (!n)
    return false;

  errno = 0;
  *n    = 0;

  result = strtoll( s, NULL, base );

  if ((result == 0) && errno)
    return false;

  if ((result == LLONG_MAX) && (errno == ERANGE))
    return false;

  if ((result == LLONG_MIN) && (errno == ERANGE))
    return false;

  *n = (uint32_t)result;
  return true;
}

/*
 *  Print program usage message
 */
void usage()
{
  fprintf(
    stderr,
    "Usage: %s [-v] -l ADDRESS -h ADDRESS coverage_in coverage_out\n"
    "\n"
    "  -l low address   - low address of range to merge\n"
    "  -l high address  - high address of range to merge\n"
    "  -f format        - coverage files are in <format> (Qemu)\n"
    "  -v               - verbose at initialization\n"
    "\n",
    progname
  );
}

#define PrintableString(_s) \
       ((!(_s)) ? "NOT SET" : (_s))

int main(
  int    argc,
  char** argv
)
{
  char*       format = NULL;
  const char* coverageFile;
  const char* coverageIn;
  int         opt;

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

  while ((opt = getopt(argc, argv, "f:h:l:v")) != -1) {
    switch (opt) {
      case 'v': Verbose = 1;       break;
      case 'f':
        inputFormat = Coverage::CoverageFormatToEnum(optarg);
        format = optarg;
        break;
      case 'l':
        if (!stringToUint32( optarg, 16, &lowAddress )) {
          fprintf( stderr, "ERROR: Low address is not a hexadecimal number\n" );
          usage();
          exit(-1);
        }
        break;
      case 'h':
        if (!stringToUint32( optarg, 16, &highAddress )) {
          fprintf( stderr, "ERROR: High address is not a hexadecimal number\n" );
          usage();
          exit(-1);
        }
        break;
      default: /* '?' */
        usage();
        exit( -1 );
    }
  }

  if ((argc - optind) != 2) {
    fprintf( stderr, "ERROR: Must provide input and output files\n" );
    exit(1);
  }

  coverageIn   = argv[optind];
  coverageFile = argv[optind + 1];

  if (Verbose) {
    fprintf( stderr, "Verbose       : %d\n", Verbose );
    fprintf( stderr, "Input Format  : %s\n", format );
    fprintf( stderr, "Input File    : %s\n", coverageIn );
    fprintf( stderr, "Output Format : %s\n", "RTEMS" );
    fprintf( stderr, "Output File   : %s\n", coverageFile );
    fprintf( stderr, "low address   : 0x%08x\n", lowAddress );
    fprintf( stderr, "high address  : 0x%08x\n", highAddress );
    fprintf( stderr, "\n" );
  }

  //
  // Validate inputs.
  //

  // Validate format.
  if (!format) {
    fprintf( stderr, "ERROR: input format must be given.\n\n" );
    usage();
    exit(-1);
  }

  // Validate address range
  if (lowAddress == 0xffffffff) {
    fprintf( stderr, "ERROR: Low address not specified.\n\n" );
    usage();
    exit(-1);
  }

  if (highAddress == 0xffffffff) {
    fprintf( stderr, "ERROR: High address not specified.\n\n" );
    usage();
    exit(-1);
  }

  if (lowAddress >= highAddress) {
    fprintf( stderr, "ERROR: Low address >= high address.\n\n" );
    usage();
    exit(-1);
  }

  //
  // Create data to support conversion.
  //

  // Create coverage map.
  CoverageMap = new Coverage::CoverageMap( lowAddress, highAddress );
  if (!CoverageMap) {
    fprintf( stderr, "ERROR: Unable to create coverage map.\n\n" );
    exit(-1);
  }

  // Create coverage writer.
  coverageWriter =
    Coverage::CreateCoverageWriter(Coverage::COVERAGE_FORMAT_RTEMS);
  if (!coverageWriter) {
    fprintf( stderr, "ERROR: Unable to create coverage file writer.\n\n" );
    exit(-1);
  }

  // Create coverage reader.
  coverageReader = CreateCoverageReader( inputFormat );
  if (!coverageReader) {
    fprintf( stderr, "ERROR: Unable to create input file reader.\n\n" );
    exit(-1);
  }

  // Now get to some real work.
  if (Verbose)
    fprintf( stderr, "Processing %s\n", coverageIn );
  coverageReader->processFile( coverageIn, CoverageMap );

  if (Verbose)
    fprintf(
      stderr, "Writing coverage file (%s)\n", coverageFile );
  coverageWriter->writeFile(
    coverageFile,
    CoverageMap,
    lowAddress,
    highAddress
  );

  return 0;
}
