#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include "bool.h"
#include "DecLogFile.h"
#include "SquidLogFile.h"
#include "numvec.h" 
#include "randomSum.h"

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

// The final size we are trying to achieve, as a % between 0 and 100.
static int finalSize = 100;

// How close we need to be to the final size, as a % between 0 and 100.
static int variance = 5;

// How many iterations before we give up.
static int iterations = 100;

// Set to true if output file being used.
boolean outputFile = FALSE; 

// The name of the output file;
static char outputFileName[2000];

void processSquidFiles(char* files[], int numberOfFiles);
void processDecFiles(char* files[], int numberOfFiles);

int main(int argc, char *argv[])
{
  if (argc < 2) {
    fprintf(stderr, "usage: %s <options> <input file> ...\n", argv[0]);
    fprintf(stderr, "\n<options> can be:\n");
    fprintf(stderr, "    -s <size>  Attempt to reduce the file to this size.\n");
    fprintf(stderr, "               Given as a %%. Default = 100 (no reduction).\n");
    fprintf(stderr, "    -v <vari>  Keep the result within this number, + or -.\n");
    fprintf(stderr, "               Given as a %%. Default = 5.\n");
    fprintf(stderr, "    -n <iter>  Make this many iterations before giving up.\n");
    fprintf(stderr, "               Default = 100.\n");
    fprintf(stderr, "    -o <file>  Output file: results are written to this file.\n");
    fprintf(stderr, "               If not specified results are written to stdout.\n");
    fprintf(stderr, "    -d         Log files are in Dec, not Squid, format.\n");
    fprintf(stderr, "\n<input file> is a proxy log.  If it ends in \".gz\", it is assumed\n");
    fprintf(stderr, "to be compressed.\n\n");
    return(1);
  }
  
  setbuf(stdout, 0);
  // Test command line arguments.
  int argNum = 1;
  while (argNum < argc) {
    if (argv[argNum][0] != '-') {
      break;
    }
    
    if (argv[argNum][1] == 's') {
      finalSize = atoi(argv[argNum+1]);
      if(finalSize < 0 || finalSize > 100) {
	fprintf(stderr, "Size (%d) should be a value between 0 and 100\n", finalSize);
	return(-1);
      }
      argNum += 2;
      continue;
    }
    
    if (argv[argNum][1] == 'v') {
      variance = atoi(argv[argNum+1]);
      if(variance < 0 || variance > 100) {
	fprintf(stderr, "Variance (%d) should be a value between 0 and 100\n", variance);
	return(-1);
      }
      argNum += 2;
      continue;
    }
    
    if (argv[argNum][1] == 'n') {
      iterations = atoi(argv[argNum+1]);
      if(iterations < 0) {
	fprintf(stderr, "Iterations (%d) should be greater than 0 and 100\n", iterations);
	return(-1);
      }
      argNum += 2;
      continue;
    }
    
    if (argv[argNum][1] == 'o') {
      outputFile = TRUE;
      strcpy(outputFileName, 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;
  }
  

  if(argc-argNum == 0) {
    fprintf(stderr, "Error: no input files specified\n");
    exit(1);
  }

  // Process all files on command line.
  if (logFormat == squidFormat) {
    processSquidFiles(&argv[argNum], argc-argNum);
  }
  else {
    processDecFiles(&argv[argNum], argc-argNum);
  }
}


void processDecFiles(char* files[], int numberOfFiles) {
  DecRequest currentRequest;
  DecLogFile ifile;
  NumVec * countVec;
  int i;
  countVec = new NumVec();
  int outFile = -1;
  unsigned long totalUrls = 0;
  unsigned long totalUrlsWritten = 0;
  boolean firstOutput = TRUE;
  long startTime;
  long endTime;

  // walk through files counting URLs
  for(i=0; i<numberOfFiles; i++) {
    if (ifile.open(files[i]) < 0) {
      fprintf(stderr, "Error opening file %s\n", files[i]);
      exit(1);
    }
  
    while (TRUE) {
      int ret = ifile.read(currentRequest);
      
      // End of file reached
      if (ret == 1)
	break;
      
      if (ret < 0) {
	ifile.close();
        fprintf(stderr, "Error processing file %s\n", files[i]);
        exit(1);
      }
      
      if(currentRequest.lastModified==0) {
        continue; // ignore requests with last modified of 0
      }

      // count each client's number of requests
      if(currentRequest.client >= countVec->getSize()) {
	countVec->Store(currentRequest.client, 1);
      } else {
	countVec->Store(currentRequest.client, 
			1+countVec->ValueAt(currentRequest.client));
      }
      
      // count total number of requests
      totalUrls++;
    }      
    ifile.close();
  }

  // see if we can find a solution
  RandomSum rs(countVec, 
	       (finalSize*totalUrls)/100+((finalSize*totalUrls)%100>=(totalUrls/2)?1:0), 
	       (variance*totalUrls)/100+((variance*totalUrls)%100>=(totalUrls/2)?1:0), 
	       iterations);
  if(FALSE==rs.Solve()) {
    fprintf(stderr, "Unable to solve in %d iterations\n", iterations);
    exit(0);
  }

  fprintf(stderr, "Solution found at %d%%\n", 
	 ((rs.GetTotal()*100)/totalUrls)+((rs.GetTotal()*100)%totalUrls>=(totalUrls/2)?1:0));

  delete countVec;

  // write output
  if(outputFile) {
    outFile = creat(outputFileName, 0600);
    if(outFile==-1) {
      fprintf(stderr, "Error: can't create output file %s\n", outputFileName);
    }
    close(outFile);
    outFile = open(outputFileName, O_WRONLY, 0);
    if(outFile==-1) {
      fprintf(stderr, "Error: can't open output file %s\n", outputFileName);
    }

    // Write 8K of junk to substitute as the file header.
    char ch = 'b';
    for(i=0; i<8192; i++) {
      write(outFile, &ch, 1);
    }
  }

  for(i=0; i<numberOfFiles; i++) {
    if (ifile.open(files[i]) < 0) {
      fprintf(stderr, "Error opening file %s\n", files[i]);
      exit(1);
    }

    while (TRUE) {
      int ret = ifile.read(currentRequest);
      
      // End of file reached
      if (ret == 1)
	break;
      
      if (ret < 0) {
	ifile.close();
        fprintf(stderr, "Error processing file %s\n", files[i]);
        exit(1);
      }
      
      if(currentRequest.lastModified==0) {
        continue; // ignore requests with last modified of 0
      }

      if(rs.ValueAt(currentRequest.client)==TRUE) {
	if(firstOutput) {
	  startTime = currentRequest.startTime;
	  endTime = currentRequest.startTime;
	  firstOutput = FALSE;
	}
        if(currentRequest.startTime<startTime) {
          startTime = currentRequest.startTime;
        }
        if(currentRequest.startTime>endTime) {
          endTime = currentRequest.startTime;
	}

        if(outputFile) {
	  write(outFile, &(currentRequest.entry), sizeof(TEntry_v2));
          totalUrlsWritten++;
	}
        else {
	  write(1, &(currentRequest.entry), sizeof(TEntry_v2));
          totalUrlsWritten++;
	}
      }      
    }
    ifile.close();
  }

  if (outputFile) {
    close(outFile);
    outputFile = FALSE;
  }
  fprintf(stderr, "%ld out of %ld URLs written in %ld day(s) for an average of %ld URLs per day\n", 
	  totalUrlsWritten, 
	  totalUrls,
	  (endTime-startTime)/(60*60*24)+1,
          totalUrlsWritten/((endTime-startTime)/(60*60*24)+1)); 
}


void processSquidFiles(char* files[], int numberOfFiles) {
  SquidRequest currentRequest;
  SquidLogFile ifile;
  NumVec * countVec;
  int i;
  countVec = new NumVec();
  FILE * outFile = NULL;
  unsigned long totalUrls = 0;
  unsigned long totalUrlsWritten = 0;
  boolean firstOutput = TRUE;
  long startTime;
  long endTime;

  // walk through files counting URLs
  for(i=0; i<numberOfFiles; i++) {
    if (ifile.open(files[i]) < 0) {
      fprintf(stderr, "Error opening file %s\n", files[i]);
      exit(1);
    }

    while (TRUE) {
      int ret = ifile.read(currentRequest);
      
      // End of file reached
      if (ret == 1)
	break;
      
      if (ret < 0) {
	ifile.close();
        fprintf(stderr, "Error processing file %s\n", files[i]);
        exit(1);
      }
      
      // count each client's number of requests
      if(currentRequest.client >= countVec->getSize()) {
	countVec->Store(currentRequest.client, 1);
      } else {
	countVec->Store(currentRequest.client, 
			1+countVec->ValueAt(currentRequest.client));
      }
      
      // count total number of requests
      totalUrls++;
    }
    ifile.close();
  }

  // see if we can find a solution
  RandomSum rs(countVec, 
	       (finalSize*totalUrls)/100+((finalSize*totalUrls)%100>=(totalUrls/2)?1:0), 
	       (variance*totalUrls)/100+((variance*totalUrls)%100>=(totalUrls/2)?1:0), 
	       iterations);
  if(FALSE==rs.Solve()) {
    fprintf(stderr, "Unable to solve in %d iterations\n", iterations);
    exit(0);
  }

  fprintf(stderr, "Solution found at %d%%\n", 
	 ((rs.GetTotal()*100)/totalUrls)+((rs.GetTotal()*100)%totalUrls>=(totalUrls/2)?1:0));

  delete countVec;
  
  // write output
  if(outputFile) {
    outFile = fopen(outputFileName, "w+");
    if(outFile==NULL) {
      fprintf(stderr, "Error: can't create output file %s\n", outputFileName);
    }
  }

  for(i=0; i<numberOfFiles; i++) {
    if (ifile.open(files[i]) < 0) {
      fprintf(stderr, "Error opening file %s\n", files[i]);
      exit(1);
    }

    while (TRUE) {
      int ret = ifile.read(currentRequest);
      
      // End of file reached
      if (ret == 1)
	break;
      
      if (ret < 0) {
	ifile.close();
        fprintf(stderr, "Error processing file %s\n", files[i]);
        exit(1);
      }
      
      if(rs.ValueAt(currentRequest.client)==TRUE) {
        // include in output
	if(firstOutput) {
	  startTime = currentRequest.endTime;
	  endTime = currentRequest.endTime;
	  firstOutput = FALSE;
	}
        if(currentRequest.endTime<startTime) {
          startTime = currentRequest.endTime;
        }
        if(currentRequest.endTime>endTime) {
          endTime = currentRequest.endTime;
	}

        if(outputFile) {
          fprintf(outFile, "%s", currentRequest.line);
          totalUrlsWritten++;
	}
        else {
          printf("%s", currentRequest.line);
          totalUrlsWritten++;
	}
      }      
    }
    ifile.close();
  }

  if (outputFile) {
    fclose(outFile);
    outputFile = FALSE;
  }
  fprintf(stderr, "%ld out of %ld URLs written in %ld day(s) for an average of %ld URLs per day\n", 
	  totalUrlsWritten, 
	  totalUrls,
	  (endTime-startTime)/(60*60*24)+1,
          totalUrlsWritten/((endTime-startTime)/(60*60*24)+1)); 
}
