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

#include "app_common.h"
#include "CoverageFactory.h"
#include "CoverageMap.h"
#include "CoverageRanges.h"
#include "Explanations.h"
#include "ObjdumpProcessor.h"
#include "Reports.h"

/*
 *  Variables to control global behavior
 */
int verbose = 0;
Coverage::CoverageFormats_t coverageFormat;
char *mergedCoverageFile = NULL;
char *branchReportFile = NULL;
char *coverageReportFile = NULL;
char *sizeReportFile = NULL;
uint32_t lowAddress  = 0xffffffff;
uint32_t highAddress = 0xffffffff;

char *target = NULL;
char *executable = NULL;
char *explanations = NULL;
char *noExplanations = NULL;
char *progname;

/*
 *  Global variables for the program
 */
Coverage::CoverageMapBase    *CoverageMap      = NULL;
Coverage::CoverageReaderBase *CoverageReader   = NULL;
Coverage::CoverageWriterBase *CoverageWriter   = NULL;
Coverage::ObjdumpProcessor   *ObjdumpProcessor = NULL;
Coverage::CoverageRanges     *Ranges           = NULL;
Coverage::Explanations       *Explanations     = NULL;

int  BranchesAlwaysTaken = 0;
bool BranchesFound = false;
int  BranchesNeverTaken = 0;
int  UncoveredRanges = 0;

/*
 *  Set of addresses we need source line number for
 */
std::list<uint32_t> AddressesNeedingSourceLine; 

/*
 *  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] [-t] [-m file] -T TARGET [-e EXECUTABLE]-l ADDRESS -h ADDRESS coverage1... coverageN\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> "
                     "(RTEMS, TSIM or Skyeye)\n"
    "  -m FILE          - optional merged coverage file to write\n"
    "  -r REPORT        - optional coverage report to write\n"
    "  -s REPORT        - optional size report to write\n"
    "  -T TARGET        - target name\n"
    "  -e EXECUTABLE    - name of executable to get symbols from\n"
    "  -E EXPLANATIONS  - name of file with explanations\n"
    "  -v               - verbose at initialization\n"
    "\n",
    progname
  );
}

/*
 *  Look over the coverage map and compute uncovered ranges and branches
 */
void ComputeUncovered(void)
{
  uint32_t a, la, ha;
  std::list<Coverage::CoverageRange>::iterator it;

  a = lowAddress;
  while (a < highAddress) {

    /*
     *  Find all the unexecuted addresses and add them to the range.
     */
    if (!CoverageMap->wasExecuted( a )) {
      la = a;
      for (ha=a+1; ha<=highAddress && !CoverageMap->wasExecuted(ha); ha++)
        ;
      ha--;

      UncoveredRanges++;
      Ranges->add( la, ha );
      AddressesNeedingSourceLine.push_back( la ); 
      AddressesNeedingSourceLine.push_back( ha ); 
      a = ha + 1;
    }

    else if (CoverageMap->isBranch( a )) {
      BranchesFound = true;
      la = a;
      for (ha=a+1;
           ha<=highAddress && !CoverageMap->isStartOfInstruction(ha);
           ha++)
        ;
      ha--;

      if (CoverageMap->wasAlwaysTaken( la )) {
        BranchesAlwaysTaken++;
        AddressesNeedingSourceLine.push_back( la ); 
      }
      else if (CoverageMap->wasNeverTaken( la )) {
        BranchesNeverTaken++;
        AddressesNeedingSourceLine.push_back( la ); 
      }
      a = ha + 1;
    }
    else
      a++;
  }
}

/*
 *  Find source lines for addresses
 */
void FindSourceForAddresses(void)
{
  FILE                          *tmpfile;
  std::list<uint32_t>::iterator  it; 

  /*
   *  Write a temporary file with ranges
   */
  if ( verbose )
    fprintf( stderr, "Writing ranges.tmp input to addr2line\n" );

  tmpfile = fopen( "ranges.tmp", "w" );
  if ( !tmpfile ) {
    fprintf( stderr, "Unable to open %s\n\n", "ranges.tmp" );
    exit(-1);
  }

  for (it =  AddressesNeedingSourceLine.begin() ;
       it != AddressesNeedingSourceLine.end() ;
       it++ ) {
    fprintf(tmpfile, "0x%08x\n", *it);
  }

  fclose( tmpfile );

  /*
   *  Generate a file with the addr2line mapping
   */
  if ( verbose )
    fprintf( stderr, "Running addr2line\n" );

  {
    char command[512];
    sprintf(
      command,
      "%s -e %s <%s | dos2unix >%s",
      Tools->getAddr2line(),
      executable,
      "ranges.tmp",
      "ranges01.tmp"
    );
    if ( system( command ) ) {
      fprintf( stderr, "addr2line command (%s) failed\n", command );
      exit( -1 );
    }
  }

  /*
   *  Go back over the ranges, read the addr2line output, and correlate it.
   */
  if ( verbose )
    fprintf( stderr, "Merging addr2line output into range\n" );

  tmpfile = fopen( "ranges01.tmp", "r" );
  if ( !tmpfile ) {
    fprintf( stderr, "Unable to open %s\n\n", "ranges01.tmp" );
    exit(-1);
  }

  for (it =  AddressesNeedingSourceLine.begin() ;
       it != AddressesNeedingSourceLine.end() ;
       it++ ) {
    char buffer[512];
    char *cStatus;

    cStatus = fgets( buffer, 512, tmpfile );
    if ( cStatus == NULL ) {
      fprintf( stderr, "Out of sync in addr2line output\n" );
      exit( -1 );
    }
    buffer[ strlen(buffer) - 1] = '\0';

    CoverageMap->setSourceLine( *it, std::string( buffer ) );
  }
  fclose( tmpfile );
}

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

int main(
  int argc,
  char **argv
)
{
  int opt;
  int i;
  char *format = NULL;

  progname = argv[0];

  while ((opt = getopt(argc, argv, "b:e:E:f:h:l:m:r:s:T:v")) != -1) {
    switch (opt) {
      case 'b': branchReportFile   = optarg;  break;
      case 'e': executable         = optarg;  break;
      case 'E': explanations       = optarg;  break;
      case 'm': mergedCoverageFile = optarg;  break;
      case 'r': coverageReportFile = optarg;  break;
      case 's': sizeReportFile     = optarg;  break;
      case 'T': target             = optarg;  break;
      case 'v': verbose            = 1;       break;
      case 'f':
        coverageFormat = Coverage::CoverageFormatToEnum(optarg);
        format = optarg;
        break;
      case 'l':
        if ( ! stringToUint32( optarg, 16, &lowAddress ) ) {
          fprintf( stderr, "Low address is not a hexadecimal number\n" );
          usage();
          exit(-1);
        }
        break;
      case 'h': 
        if ( ! stringToUint32( optarg, 16, &highAddress ) ) {
          fprintf( stderr, "High address is not a hexadecimal number\n" );
          usage();
          exit(-1);
        }
        break;
      default: /* '?' */
        usage();
        exit( -1 );
    }
  }
  if ( verbose ) {
    fprintf( stderr, "verbose         : %d\n", verbose );
    fprintf( stderr, "Coverage Format : %s\n", format );
    fprintf( stderr, "low address     : 0x%08x\n", lowAddress );
    fprintf( stderr, "high address    : 0x%08x\n", highAddress );
    fprintf( stderr, "Target          : %s\n", PrintableString(target) );
    fprintf( stderr, "executable      : %s\n", PrintableString(executable) );
    fprintf( stderr, "merged coverage : %s\n",
             PrintableString(mergedCoverageFile) );
    fprintf( stderr, "\n" );
  }

  /*
   * Target name must be set
   */
  if ( !target ) {
    fprintf( stderr, "target must be given.\n\n" );
    usage();
    exit(-1);
  }

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

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

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

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

  /*
   *  Create toolnames based on target
   */
  TargetInfo = Target::TargetFactory( target );

  /*
   *  Create a ranges set
   */
  Ranges = new Coverage::CoverageRanges();
  Explanations = new Coverage::Explanations();

  Explanations->load( explanations );

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

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

  /*
   * Create the objdump processor
   */
  ObjdumpProcessor = new Coverage::ObjdumpProcessor();

  /*
   * Create writer
   *
   * NOTE: We ALWAYS write the merged coverage in RTEMS format.
   */
  CoverageWriter = Coverage::CreateCoverageWriter(
    Coverage::COVERAGE_FORMAT_RTEMS
  );
  if ( !CoverageWriter ) {
    fprintf( stderr, "Unable to create coverage file writer.\n\n" );
    exit(-1);
  }

  /*
   * Add in the objdump before reading the coverage information.  We may
   * want to take advantage of the information line where instructions
   * begin.
   */
  if ( executable ) {
    if ( verbose )
      fprintf( stderr, "Reading objdump of %s\n", executable );
    ObjdumpProcessor->initialize( executable, CoverageMap );
  }

  /*
   * Now get to some real work
   */
  if ( verbose )
    fprintf( stderr, "Processing coverage files\n" );
  for ( i=optind ; i < argc ; i++ ) {
    //fprintf( stderr, "Processing %s\n", argv[i] );
    CoverageReader->ProcessFile( argv[i], CoverageMap );
  } 

  /*
   * Now to write some output
   */
  if ( mergedCoverageFile ) {
    if ( verbose )
      fprintf(
        stderr,
        "Writing merged coverage file (%s)\n",
        mergedCoverageFile
      );
    CoverageWriter->writeFile(
      mergedCoverageFile,
      CoverageMap,
      lowAddress,
      highAddress
    );
  }

  /*
   *  Marks nops as executed when they are surrounded by executed instructions.
   */
  ObjdumpProcessor->markNopsAsExecuted( CoverageMap );

  /*
   * Iterate over the coverage map and determine the uncovered
   * ranges and branches.
   */
  ComputeUncovered();

  /*
   *  Look up the source file and line number for the addresses
   *  of interest.
   */ 
  FindSourceForAddresses();

  /*
   *  Generate report of ranges not executed
   */ 
  if ( coverageReportFile ) {
    if ( verbose )
      fprintf( stderr, "Writing coverage report (%s)\n", coverageReportFile );
    WriteCoverageReport( coverageReportFile );

    /*
     *  Let the user know how many cases there were
     */
    printf( "%d uncovered ranges found\n", UncoveredRanges );
  }

  /*
   *  Generate report of branches taken/not taken
   */ 
  if ( branchReportFile ) {
    if ( verbose )
      fprintf( stderr, "Writing branch report (%s)\n", branchReportFile );
    WriteBranchReport( branchReportFile, lowAddress, highAddress );

    /*
     *  Let the user know how many branch cases were found
     */
    if (!BranchesFound)
      printf( "No branch information found\n" );
    else {
      printf(
        "%d uncovered branches found\n",
        BranchesAlwaysTaken + BranchesNeverTaken
      );
      printf(
        "   %d branches always taken\n", BranchesAlwaysTaken
      );
      printf(
        "   %d branches never taken\n", BranchesNeverTaken
      );
    }
  }

  /*
   *  Simple formatted report of size of ranges 
   */ 
  if ( sizeReportFile ) {
    if ( verbose )
      fprintf( stderr, "Writing size report (%s)\n", sizeReportFile );
    WriteSizeReport( sizeReportFile );
  }

  /*
   *  Generate annotated assembly file
   */
  if ( verbose )
    fprintf( stderr, "Writing annotated report (%s)\n", "annotated.txt" );
  WriteAnnotatedReport( "annotated.txt", lowAddress, highAddress );

  /*
   * write explanations that were not found
   */
  std::string str = explanations;
  str = str + ".NotFound";
  if ( verbose )
    fprintf( stderr, "Writing Not Found Report (%s)\n", str.c_str() );
  Explanations->writeNotFound(str.c_str());

  /*
   * Calculate coverage percentage
   */
  {
    uint32_t a;
    uint32_t notExecuted = 0;
    double   percentage;

    for ( a=lowAddress ; a < highAddress ; a++ ) {
      if ( !CoverageMap->wasExecuted( a ) )
        notExecuted++;
    }

    percentage = (double) notExecuted;
    percentage /= (double) (highAddress - lowAddress);
    percentage *= 100.0;
    printf( "Bytes Analyzed          : %d\n", highAddress - lowAddress );
    printf( "Bytes Not Executed      : %d\n", notExecuted );
    printf( "Percentage Executed     : %5.4g\n", 100.0 - percentage  );
    printf( "Percentage Not Executed : %5.4g\n", percentage  );
  }

  return 0;
}
