#include "Logger.h"
#include "MessageStatistics.h"
#include "Parameters.h"
#include "Statistics.h"
#include "GlobalStatistics.h"

#include <cstdarg>
#include <cstdio>
#include <cassert>

const Logger::LogLevel Logger::default_logLevel = Logger::LOGLEVEL_DEBUG;
const char * const Logger::default_filenameBase = "psg";

Logger * Logger::instance = NULL;

Logger * Logger::getInstance() {
  if ( !instance )
    instance = new Logger;
  return instance;
}

void Logger::deleteInstance() {
  if ( instance )
    delete instance;
  instance = NULL;
}

Logger::Logger() :
    logLevel( default_logLevel ) {
  generateFilenames( "" );
}

void Logger::setLogLevel( LogLevel level ) {
  logLevel = level;
}

const Logger::LogLevel Logger::getLogLevel() const {
  return logLevel;
}

const char * Logger::getFilenameLog() const {
  return filenameLog;
}
const char * Logger::getFilenameReport() const {
  return filenameReport;
}
const char * Logger::getFilenameMsgs() const {
  return filenameMsgs;
}
void Logger::generateFilenames( std::string uniqueIdentifier, const char *filenameBase ) {
  const int rank = Parameters::getInstance()->getRank();
  uid = uniqueIdentifier;
  if ( rank < 0 ) {
    std::sprintf( filenameLog, "%s.%s.log", filenameBase, uid.c_str() );
    std::sprintf( filenameReport, "%s.%s.report", filenameBase, uid.c_str() );
    std::sprintf( filenameMsgs, "%s.%s.msgs", filenameBase, uid.c_str() );
  } else {
    std::sprintf( filenameLog, "%s.%s.p%02d.log", filenameBase, uid.c_str(), rank );
    std::sprintf( filenameReport, "%s.%s.p%02d.report", filenameBase, uid.c_str(), rank );
    std::sprintf( filenameMsgs, "%s.%s.p%02d.msgs", filenameBase, uid.c_str(), rank );
  }
}

void Logger::log( LogLevel msgLevel, const char *format, ... ) const {
  if ( msgLevel <= logLevel ) {
    const Statistics *stats = Statistics::getInstance();
    const Parameters *params = Parameters::getInstance();
    FILE *file = std::fopen( filenameLog, "at" );
    assert( file );
    std::fprintf( file,
                  "%d\t" // rank
                  "%u\t" // numGenerationsTotal
                  "%u\t" // numEpochs
                  "%u\t" // numGenerationsEpoch
                  "%u\t", // numConvergedAlleles
                  params->getRank(),
                  stats->getNumGenerationsTotal(),
                  stats->getNumEpochs(),
                  stats->getNumGenerationsEpoch(),
                  stats->getNumConvergedAlleles() );

    va_list args;
    va_start( args, format );
    vfprintf( file, format, args );
    va_end( args );

    std::fprintf( file, "\n" );
    std::fclose( file );
  }
}

void Logger::writeReports( FloatType bestFitness, bool append ) const {
  const Parameters *params = Parameters::getInstance();
  const int rank = params->getRank();
  const Statistics *stats = Statistics::getInstance();
  const MessageStatistics *ms = MessageStatistics::getInstance();
  const GlobalStatistics *gs = GlobalStatistics::getInstance();
  FILE *file;

  if ( rank == 0 ) {
    file = std::fopen( filenameReport, append ? "at" : "wt" );
    assert( file );

    std::fprintf( file, "uid\tn\t" );
    std::fprintf( file, "sumA\tmaxA\t" );
    std::fprintf( file, "epsilon\tconvergenceThreshold\t" );
    std::fprintf( file, "p\tdelta\t" );
    std::fprintf( file, "mu\tgamma\tepochGenerationLimit\t" );
    std::fprintf( file, "intervalUpdateParameters\tintervalCheckEpoch\tintervalStoppingLocal\tintervalStoppingGlobal\t" );
    for ( int p = 0; p < params->getP(); p++ )
      std::fprintf( file, "numConvergedAlleles(%u)\t", p );
    for ( int p = 0; p < params->getP(); p++ )
      std::fprintf( file, "numEpochs(%u)\t", p );
    for ( int p = 0; p < params->getP(); p++ )
      std::fprintf( file, "numGenerations(%u)\t", p );
    std::fprintf( file, "cpuTime\tbestFitness\n" );

    std::fprintf( file, "%s\t%u\t", uid.c_str(), params->getN() );
    std::fprintf( file, "%u\t%u\t", params->getSumA(), params->getMaxA() );
    std::fprintf( file, "%.3f\t%.3f\t", params->getEpsilon(), params->getConvergenceThreshold() );
    std::fprintf( file, "%d\t%d\t", params->getP(), params->getDelta() );
    std::fprintf( file, "%.3f\t%u\t%u\t", params->getMu(), params->getGamma(), params->getEpochGenerationLimit() );
    std::fprintf( file, "%u\t%u\t%u\t%u\t", params->getIntervalUpdateParameters(), params->getIntervalCheckEpoch(), params->getIntervalStoppingLocal(), params->getIntervalStoppingGlobal() );
    for ( int p = 0; p < params->getP(); p++ )
      std::fprintf( file, "%u\t", gs->getNumConvergedAlleles( p ) );
    for ( int p = 0; p < params->getP(); p++ )
      std::fprintf( file, "%u\t", gs->getNumEpochs( p ) );
    for ( int p = 0; p < params->getP(); p++ )
      std::fprintf( file, "%u\t", gs->getNumGenerations( p ) );
    std::fprintf( file, "%u\t%.3f\n\n", stats->getCpuTime(), bestFitness );

    std::fclose( file );
    log( LOGLEVEL_DEBUG, "Wrote report to file <%s>", filenameReport );
  }

  file = std::fopen( filenameMsgs, append ? "at" : "wt" );
  assert( file );
  if ( rank == 0 ) {
    std::fprintf( file, "p\trank" );
    std::fprintf( file, "\tALLELES sent\tALLELES rcvd" );
    std::fprintf( file, "\tLOCAL_STOP sent\tLOCAL_STOP rcvd" );
    std::fprintf( file, "\tGLOBAL_STOP sent\tGLOBAL_STOP rcvd" );
    std::fprintf( file, "\tLOCAL_SOLUTION sent\tLOCAL_SOLUTION rcvd" );
    std::fprintf( file, "\tTERMINATION_TOKEN sent\tTERMINATION_TOKEN rcvd" );
    std::fprintf( file, "\n" );
  }
  std::fprintf( file, "%d\t%d", params->getP(), rank );
  const unsigned int *data = ms->getData();
  for ( unsigned int i = 0; i < ms->getLength(); i++ )
    std::fprintf( file, "\t%u", data[i] );
  std::fprintf( file, "\n" );

  std::fclose( file );
  log( LOGLEVEL_DEBUG, "Wrote messages report to file <%s>", filenameMsgs );
}
