#include "DecLogFile.h"
#include "SquidLogFile.h"
#include "Request.h"
#include "cache.h"

// Tracks number of urls processed.
static long urlsProcessed = 0;

// Every reportFrequency urls processed, print a report.
// 0 indicates no reports until the end.
long reportFrequency = 0;

// Number of hits, misses, and unclassifieds so far.
long hits = 0, misses = 0, unknowns = 0;

// Just used in reporting.  Should be updated as records are read.
long currentTime = 0;

Cache *cache = NULL;

// The format of the log files.
enum {decFormat, squidFormat} logFormat = squidFormat;


// Reporting function.
void reportProgress(void);

int processSquidFile(const char* fname);
int processDecFile(const char* fname);

void main(int argc, char *argv[])
{
  long long cacheSize = 1000LL * 1024 * 1024;
  int hashTableBits = 22;

  if (argc < 2)
    {
      fprintf(stderr, "usage: %s <options> <input file> ...\n", argv[0]);
      fprintf(stderr, "\n<options> can be:\n");
      fprintf(stderr, "    -r <freq>  Report on progress every <freq> urls processed.\n");
      fprintf(stderr, "               0 means report only at end.  Default = 0.\n");
      fprintf(stderr, "    -c <size>  The disk space used by the cache will never exceed <size> MB.\n");
      fprintf(stderr, "               Default = 1,000.\n");
      fprintf(stderr, "    -d         Log files are in Dec, not Squid, format.\n");
      fprintf(stderr, "    -h <size>  Hash table will have 2^<size> slots or initially\n");
      fprintf(stderr, "               4*2^<size> bytes.  Default = 22 (16MB).\n");
      fprintf(stderr, "\n<input file> is a proxy log.  If it ends in \".gz\", it is assumed\n");
      fprintf(stderr, "to be compressed.  If it is \"-\", stdin is used.\n\n");
      
      exit(1);
    }

  // Test command line arguments.
  // -r means reporting frequency.
  int argNum = 1;
  while (argNum < argc)
    {
      if (argv[argNum][0] != '-') break;

      if (argv[argNum][1] == 'r')
	{
	  reportFrequency = atol(argv[argNum+1]);
	  argNum += 2;
	  // printf("Set report Frequency to %ld\n", reportFrequency);
	  continue;
	}

      if (argv[argNum][1] == 'c')
	{
	  cacheSize = (long long)atol(argv[argNum+1]) * 1024 * 1024;
          argNum += 2;
          continue;
	}

      if (argv[argNum][1] == 'h')
	{
	  hashTableBits = atoi(argv[argNum+1]);
	  argNum += 2;
	  continue;
	}

      if (argv[argNum][1] == 'd')
	{
	  logFormat = decFormat;
	  argNum++;
	  continue;
	}

      // If this argument is not recognized, assume it's a file.
      break;
    }

  cache = new Cache(cacheSize, hashTableBits);

  // Loop through all files on command line.
  int fileNumber = argNum;
  while (fileNumber < argc)
    {
      if (logFormat == squidFormat) 
	{
	  if (processSquidFile(argv[fileNumber]) != 0)
	    fprintf(stderr, "Error reading squid file '%s'.\n", argv[fileNumber]);
	}
      else
	{
	  if (processDecFile(argv[fileNumber]) != 0)
	    fprintf(stderr, "Error reading dec file '%s'.\n", argv[fileNumber]);
	}

      fileNumber++;
    }
  
  reportProgress();

  delete cache;
}


void reportProgress(void)
{
  printf("Report: %ld %ld / %ld = %2.2lf %%, %ld unknown.  Cache size %lld.\n", currentTime, hits, urlsProcessed, (double)hits/(hits+misses)*100, unknowns, cache->getCurrentSize());
}


int processDecFile(const char* fname)
{
  DecRequest currentRequest;

  DecLogFile ifile;

  if (ifile.open(fname) < 0)
    {
      return -1;
    }
  
  while (true)
    {
      int ret = ifile.read(currentRequest);

      // End of file reached
      if (ret == 1)
        break;

      if (ret < 0)
        {
          ifile.close();
          return -2;
        }

      if (currentRequest.lastModified == 0) continue;

      // We have a 5 minute tolerance for reordered events.
      if (currentRequest.startTime < currentTime - 300) 
	{
	  printf("Time out of sequence.  Earlier record at %ld, but current at %ld\n", currentTime, currentRequest.startTime);
	  continue;
	}

      // printf("time = %lx\n", currentRequest.startTime);

      SimulationResult result = cache->processRequest(currentRequest);

      if (result == simHit)
        hits++;
      else if (result == simMiss)
        misses++;
      else if (result == simUnknown)
        {
	  unknowns++;
          // printf("unknown result for %ld, httpResult = %d\n", currentRequest.urlHash, currentRequest.httpResult);
        }
      else if (result == simError)
	{
	  // printf("error result for %ld - ", currentRequest.urlHash);
	  // printf("httpResult = %d.\n", currentRequest.httpResult);
	}
      else
	{
          printf("unexpected return value from processDecRequest() = %d\n", result);
	}

      urlsProcessed++;
      
      currentTime = currentRequest.startTime;

      if (reportFrequency > 0 && urlsProcessed % reportFrequency == 0)
        reportProgress();
    }

  ifile.close();
      
  return 0;
}


// Returns 0 on success, -1 on error.
int processSquidFile(const char* fname)
{
  SquidRequest currentRequest;

  SquidLogFile ifile;

  if (ifile.open(fname) < 0)
    {
      return -1;
    }

  while (true)
    {
      int ret = ifile.read(currentRequest);

      // End of file reached
      if (ret == 1)
	break;

      if (ret < 0)
	{
	  ifile.close();
	  return -2;
	}

      SimulationResult result = cache->processRequest(currentRequest);
      if (result == simHit)
	hits++;
      else if (result == simMiss)
	misses++;
      else if (result == simUnknown)
	{
	  unknowns++;
	  // printf("unknown result for %s - ", currentRequest.url);
	  // printf("httpResult = %d, proxyResult = %d.\n", currentRequest.httpResult, currentRequest.proxyResult);
	}
      else if (result == simError)
	{
	  // printf("error result for %s - ", currentRequest.url);
	  // printf("httpResult = %d, proxyResult = %d.\n", currentRequest.httpResult, currentRequest.proxyResult);
	}
      else
	{
          printf("unexpected return value from processSquidRequest() = %d\n", result);
	}

      urlsProcessed++;

      currentTime = currentRequest.endTime;

      if (reportFrequency > 0 && urlsProcessed % reportFrequency == 0)
	reportProgress();
    }

  ifile.close();

  return 0;
}
