#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include "common.h"


/* #define GUESSED_NUM_OF_UNIQUE_OBJECTS(total_requests) (total_requests * 95/100) */
#define GUESSED_NUM_OF_UNIQUE_OBJECTS(total_requests) (total_requests)
        /* Assume all caches have a hit rate of 5% at the least, so there are */
        /* at the most 95% of all the requests are unique ones.               */
        /* If this number is still small, then you may increast it to 100% .  */

FILE *fpout;

INT AveConnect;
INT Echo = 0, From, To, Step;

INT    TotalRequests; /* the actual total number of requests */
INT    UniqueRequests = 0;
double TotalCost  = 0.0, TotalBytes  = 0.0, TotalPackets  = 0.0;
double UniqueCost = 0.0, UniqueBytes = 0.0, UniquePackets = 0.0;

double InfCacheHitRate, InfCacheByteHitRate, InfCacheReducedLatency,
       InfCacheReducedPackets;


REQUEST *RequestsTable = NULL; /* An array holding all the requests read from */
                               /* the trace file. Avoid fgets later on */

static void display_help_info(char *program_name)
{
    ERROR_LOG("\n");
    fprintf(stderr, "Usage: %s [options]\n", program_name);
    ERROR_LOG("        -h                 (print help message)\n");
    ERROR_LOG("        -echo              (echo every step of ");
    ERROR_LOG("the cache status)\n");
    ERROR_LOG("        -request <num>     (number of requests you ");
    ERROR_LOG("want to process)\n");
    ERROR_LOG("        -script <filename> (if not specified, will ");
    ERROR_LOG("only do infinite cache simu)\n");
    ERROR_LOG("        -trace  <filename> (if not specified, will ");
    ERROR_LOG("use the standard input)\n");
    ERROR_LOG("        -output <filename> (if not specified, will ");
    ERROR_LOG("use the standard output)\n");
    ERROR_LOG("\n");
}

static void check_para_value(int i, int argc, char **argv)
{
    if ( (i>=argc) || (argv[i][0]=='-') ) {
        fprintf(stderr,"\nError: missing value for the parameter '%s'!\n\n",
                argv[i-1]);
        exit(-1);
    }
}

static void process_parameters(int argc, char **argv, char **script,
                               INT *num_requests, char **trace, char **output)
{
    int i;
    char *parameter_name;
  
    i = 1;
    while (i<argc) {
        if (argv[i][0] != '-') {
            fprintf(stderr, "\nError: unknown parameter: '%s'!\n\n", argv[i]);
            exit(-1);
        }
        else {
            parameter_name = argv[i]+1;
            if ( strcmp(parameter_name,"request")==0 ) {
                i++;
                check_para_value(i, argc, argv);
                (*num_requests) = strtoull(argv[i],(char**)NULL,10);
            }
            else if ( strcmp(parameter_name,"script")==0 ) {
                i++;
                check_para_value(i, argc, argv);
                (*script) = strdup(argv[i]);
            }
            else if ( strcmp(parameter_name,"trace")==0 ) {
                i++;
                check_para_value(i, argc, argv);
                (*trace) = strdup(argv[i]);
            }
            else if ( strcmp(parameter_name,"output")==0 ) {
                i++;
                check_para_value(i, argc, argv);
                (*output) = strdup(argv[i]);
            }
            else if ( strcmp(parameter_name,"echo")==0 ) {
                Echo = 1;
                i++;
                check_para_value(i, argc, argv);
                From = strtoull(argv[i],(char**)NULL,10);
                From = From<=0 ? 1 : From;
                i++;
                check_para_value(i, argc, argv);
                To = strtoull(argv[i],(char**)NULL,10);
                To = To<=0 ? 1 : To;
                i++;
                check_para_value(i, argc, argv);
                Step = strtoull(argv[i],(char**)NULL,10);
                Step = Step<=0 ? 1 : Step;
                fprintf(stderr,"\nPrint cache status from request %lld ",From);
                fprintf(stderr,"to %lld for every %lld requests.\n\n",To,Step);
            }
            else if ( strcmp(parameter_name,"h")==0 ) {
                /* display_help_info(argv[0]); */
                exit(-1); 
            }
            else {
                fprintf(stderr,"\nError: unknown parameter: '%s'!\n\n",argv[i]);
                exit(-1);
            }
        }
        i++;
    }
}

static void revise_num_of_requests(char *filename, INT *num_requests)
{
    INT linect;
    int fd, len;
    unsigned char *C, buf[MAX_LINE_LENGTH];

    if ((fd = open(filename, O_RDONLY, 0)) < 0) {
        fprintf(stderr, "\nError: cannot open file %s!\n\n", filename);
        exit(-1);
    }
    linect = 0;
    while(len = read(fd, buf, MAX_LINE_LENGTH)) {
        if (len == -1) {
            fprintf(stderr, "\nError in reading file %s for revising!\n\n",
                    filename);
            exit(-2);
        }
        for (C = buf; len--; ++C) {
            if (*C == '\n') {
                ++linect;
            }
        }
    }
    close(fd);

    if ( linect >= (*num_requests) ) {
        fprintf(stderr,"\n    The number_of_request is not changed.\n\n");
    }
    else {
        fprintf(stderr,"\n    The specified (or default) number_of_request ");
        fprintf(stderr,"is %ld.\n", (*num_requests) );
        fprintf(stderr,"    However, the trace file contains only %ld ",linect);
        fprintf(stderr,"lines of requests.\n");
        fprintf(stderr,"    So number_of_request is adjusted to: ");
        fprintf(stderr,"number_of_request = %ld\n\n", linect);

        (*num_requests) = linect;
    }
}

/******************************************************************************
 * This procedure may modify the global variable: fpout                       *
 ******************************************************************************/
void open_files(char *script, char *trace, char *output,
                FILE **fp_script, FILE **fpin)
{
    if ( script == NULL ) {
        (*fp_script) = NULL;  /* read trace from standard input */
    }
    else {
        (*fp_script) = fopen(script, "r");
        if ( (*fp_script) == NULL) {
            fprintf(stderr, "\nError: cannot open script file %s\n\n", script);
            exit(-2);
        }
    }

    if ( trace == NULL ) {
        (*fpin) = stdin;  /* read trace from standard input */
    }
    else {    
        (*fpin) = fopen(trace, "r");
    }
    if ( (*fpin) == NULL) {
        fprintf(stderr, "\nError: cannot open trace file %s\n\n", trace);
        exit(-2);
    }

    if ( output == NULL ) {
        fpout = stdout; /* put result to standard output */
    }
    else {
        fpout = fopen(output, "w");
    }
    if ( fpout == NULL) {
        fprintf(stderr, "\nError: cannot open output file %s\n\n", output);
        exit(-2);
    }
}

#define SCAN_ONE_LINE_INTO_A_REQUEST(line, request) \
        sscanf(  line, "%lld %lld %lld %lld %lld %lld", \
                       &(request.object.server), \
                       &(request.object.path), \
                       &(request.size), \
                       &(request.cost), \
                       &(request.last_modify), \
                       &(request.time)              )

#define DO_INFINITE_CACHE_AND_SOME_STATISTICS(request) \
{ \
    if (request.size < CONN) { \
        num_connect++; \
        total_connect += ((double)request.cost/1e6); \
    } \
    TotalRequests++; \
    TotalBytes   += request.size; \
    TotalCost    += ( (double)request.cost / 1e6 ); \
    TotalPackets += GET_PACKET(request.size); \
    /* printf("total request = %lld\n", TotalRequests); */ \
 \
    miss = 0; \
    index = find_object_in_hash_table(request.object); \
    /* printf("hash index = %lld\n", index); */ \
    if (index < 0) { \
        miss = 1; \
        index = add_object_into_hash_table( request.object, request.size, \
                                            request.last_modify ); \
    } \
    else { \
        IF_OBJECT_CHANGED(request, ObjectsHashTable[index]) { \
            miss = 1; \
        } \
        ObjectsHashTable[index].size = request.size; \
        ObjectsHashTable[index].last_modify = request.last_modify; \
    } \
 \
    if (miss) { \
        UniqueRequests++; \
        UniqueBytes   += request.size; \
        UniqueCost    += ( (double)request.cost / 1e6 ); \
        UniquePackets += GET_PACKET(request.size); \
    } \
}

#define PRINT_STATISTICS_INFO_AND_INFINITE_CACHE_RESULT(fp) \
{ \
    fprintf(fp, "\n"); \
    fprintf(fp, "Trace file : %s\n", tracefilename); \
    /* fprintf(fp, "Script file : %s\n", scriptfilename); */ \
 \
    fprintf(fp, "\n"); \
    fprintf(fp, "                 Total          Unique\n"); \
    fprintf(fp, "            ==============  ==============\n"); \
    fprintf(fp, "Requests :   %-12lld    %-12lld\n",TotalRequests,UniqueRequests); \
    fprintf(fp, "   Bytes :   %-12g    %-12g\n",TotalBytes,   UniqueBytes); \
    fprintf(fp, "    Cost :   %-12g    %-12g\n",TotalCost,    UniqueCost); \
    fprintf(fp, " Packets :   %-12g    %-12g\n",TotalPackets, UniquePackets); \
 \
    fprintf(fp, "\n"); \
    if (num_connect > 0) { \
            AveConnect = (INT) ((total_connect*1e6)/num_connect); \
    } \
    else { \
            AveConnect = 50000;    /* 50 ms */ \
    } \
    fprintf(fp, "  Number of small pages = %lld\n", num_connect); \
    fprintf(fp, "Average connection time = %lld\n", AveConnect); \
 \
    fprintf(fp, "\n"); \
    InfCacheHitRate = (double)(TotalRequests - UniqueRequests)/ \
                                                      (double)TotalRequests; \
    InfCacheByteHitRate    = (TotalBytes - UniqueBytes) / TotalBytes; \
    InfCacheReducedLatency = (TotalCost - UniqueCost) / TotalCost; \
    InfCacheReducedPackets = (TotalPackets - UniquePackets) / TotalPackets; \
    fprintf(fp, "Infinite Cache =========================================="); \
    fprintf(fp, "===============\n"); \
    fprintf(fp, "           Hit Rate = %6.3f%% (%f)\n", \
            InfCacheHitRate*100,  InfCacheHitRate ); \
    fprintf(fp, "      Byte Hit Rate = %6.3f%% (%f)\n", \
            InfCacheByteHitRate*100,  InfCacheByteHitRate ); \
    fprintf(fp, "    Reduced Latency = %6.3f%% (%f)\n", \
            InfCacheReducedLatency*100,  InfCacheReducedLatency ); \
    fprintf(fp, "    Reduced Packets = %6.3f%% (%f)\n", \
            InfCacheReducedPackets*100,  InfCacheReducedPackets ); \
 \
    fprintf(fp, "\n"); \
    fflush(fp); \
}

int
main(int argc, char **argv)
{
    FILE *fp_script, *fp_trace;
    char *scriptfilename, *tracefilename, *outputfilename;
    char line_buffer[MAX_LINE_LENGTH];
    INT  i, index, num_requests, num_connect, *counts;
    double total_connect;
    char miss;

    display_help_info(argv[0]);

/** Parse command line parameters */
    num_requests = 1000000; /* Default value */
    scriptfilename = NULL;
    tracefilename  = NULL;
    outputfilename = NULL;
    process_parameters(argc, argv, &scriptfilename, &num_requests,
                       &tracefilename, &outputfilename);
    if (tracefilename != NULL) {
        ERROR_LOG("\nGoing to revise the value of num_of_requests. ");
        ERROR_LOG("Please wait for a while...\n");
        revise_num_of_requests(tracefilename, &num_requests);
    }
        /* printf("Request=%lld, trace=%s, output=%s\n", num_requests,
                  tracefilename, outputfilename); */

/** Allocate memory space */
    RequestsTable = (REQUEST *) malloc( num_requests * sizeof(REQUEST) );
    IF_OUT_OF_MEM(RequestsTable, "Requests Table")

    init_objects_hash_table(GUESSED_NUM_OF_UNIQUE_OBJECTS(num_requests));

    counts = (INT *) malloc( GUESSED_NUM_OF_UNIQUE_OBJECTS(num_requests)
                             * sizeof(INT) );
    IF_OUT_OF_MEM(counts, "counts")
    for(i=0;i<GUESSED_NUM_OF_UNIQUE_OBJECTS(num_requests);i++)  counts[i] = 0;

/** Open files; Read in trace to RequestsTable */
/** Do infinite cache and some statistics */
    open_files(scriptfilename, tracefilename, outputfilename,
               &fp_script, &fp_trace);
    total_connect = 0;
    num_connect = 0;
    for (i=0; i<num_requests; i++) {
        if (fgets(line_buffer,MAX_LINE_LENGTH,fp_trace) != NULL) {
            SCAN_ONE_LINE_INTO_A_REQUEST(line_buffer, RequestsTable[i]);
            /* fprintf(stderr, "%9lld: ", i);
               PR_REQUEST(RequestsTable[i]);
             */
            DO_INFINITE_CACHE_AND_SOME_STATISTICS(RequestsTable[i]);
        }
        else {
            break;
        }   
    }
    free(counts);
    fclose(fp_trace);
    printf("Infinite cache finished!\n");

    PRINT_STATISTICS_INFO_AND_INFINITE_CACHE_RESULT(fpout);

    if ( fp_script != NULL) {
        do_other_cache_simulation_according_to_the_script_file(fp_script);
        fclose(fp_script);
    }
/*
    for (i=0; i<num_requests; i++) {
        fprintf(stderr, "%9lld: ", i);
        PR_REQUEST(RequestsTable[i]);
    }
    print_hash_table();
 */
    fclose(fpout);

    return(0);
}

