#include "cachelab.h"

#include <ctype.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

typedef struct line_t {
    // a line of the cache
    // since this is just a simulator, we do not need to fetch data from memory really,
    // so I omit the B part
    int valid;
    unsigned long tag;
} line;

typedef struct set_t {
    // a set contains E lines
    // actually, the time complexity of load and eviction of a LRU cache could be O(1)
    // but since there is no std library of hashmap in C, so I did not bother implement
    // it. The solution could be found in a Leetcode question with keyword LRU.
    line *lines;
} set;

set *sets;      // the simulated cache
int s = 0, E = 0, b = 0;            // parameters of the cache
int miss = 0, hit = 0, eviction = 0;            // evaluation
int op_count = 0;                       // count the num of ops. for LRU
int vflag = 0;                      // verbose flag

// fetch miss data from memory
void fetch(unsigned set_index, unsigned long tag);

void load(unsigned set_index, unsigned long tag) {
    int hit_flag = 0;
    for (int i = 0; i < E; ++i) {
        if (sets[set_index].lines[i].valid && (sets[set_index].lines[i].tag == tag)) {
            // hit
            hit_flag = 1;
            sets[set_index].lines[i].valid = op_count;
            break;
        }
    }
    if (hit_flag) {
        ++hit;
        if (vflag) printf("%s", " hit");
    } else {
        ++miss;
        if (vflag) printf("%s", " miss");
        // fetch from memory
        fetch(set_index, tag);
    }
}

void modify(unsigned set_index, unsigned long tag) {
    load(set_index, tag);
    ++hit;      // the store op must hit since the block has been loaded into cache
    if (vflag) printf("%s", " hit");
}

void fetch(unsigned set_index, unsigned long tag) {
    int find_flag = 0;          // whether find a unvalid line
    for (int i = 0; i < E; ++i) {
        if (!sets[set_index].lines[i].valid) {
            sets[set_index].lines[i].valid = op_count;
            sets[set_index].lines[i].tag = tag;
            find_flag = 1;
            break;
        }
    }
    if (find_flag) return;
    // eviction happens. select the LRU line
    ++eviction;
    if (vflag) printf("%s", " eviction");
    int least_time = 0x7fffffff, least = -1;
    for (int i = 0; i < E; ++i) {
        if (sets[set_index].lines[i].valid < least_time) {
            least_time = sets[set_index].lines[i].valid;
            least = i;
        }
    }

    // replace the LRU line with the new fetched line
    sets[set_index].lines[least].valid = op_count;
    sets[set_index].lines[least].tag = tag;
}

int main(int argc, char **argv) {
    // read args
    char *tracefile = NULL;
    char c;      // tmp value
    opterr = 0;

    char usage[] = "Usage: %s [-hv] -s <num> -E <num> -b <num> -t <file>\n\
Options:\n\
  -h         Print this help message.\n\
  -v         Optional verbose flag.\n\
  -s <num>   Number of set index bits.\n\
  -E <num>   Number of lines per set.\n\
  -b <num>   Number of block offset bits.\n\
  -t <file>  Trace file.\n\
\n\
Examples:\n\
  linux>  %s -s 4 -E 1 -b 4 -t traces/yi.trace\n\
  linux>  %s -v -s 8 -E 2 -b 4 -t traces/yi.trace\n";

    while ((c = getopt(argc, argv, "hvs:E:b:t:")) != -1) {
        switch (c) {
            case 'h':
                printf(usage, argv[0], argv[0], argv[0]);
                return 1;
            case 'v':
                vflag = 1;
                break;
            case 's':
                s = atoi(optarg);
                break;
            case 'E':
                E = atoi(optarg);
                break;
            case 'b':
                b = atoi(optarg);
                break;
            case 't':
                tracefile = optarg;
                break;
            case '?':
                if (optopt == 's' || optopt == 'E' || optopt == 'b' || optopt == 't')
                    fprintf(stderr, "Option -%c requires an argument.\n",
                            optopt);
                else if (isprint(optopt))
                    fprintf(stderr, "Unknown option `-%c'.\n", optopt);
                else
                    fprintf(stderr, "Unknown option character `\\x%x'.\n",
                            optopt);
                return 1;
            default:
                abort();
        }
    }

    // check mandatory options
    if (s == 0 || E == 0 || b == 0 || tracefile == NULL) {
        printf("%s: Missing required command line argument\n", argv[0]);
        printf(usage, argv[0], argv[0], argv[0]);
        return 1;
    }
    // validate the file path
    if (access(tracefile, F_OK) == -1) {
        printf("%s: No such file or directory\n", tracefile);
        return 1;
    }

    // construct lines
    int num_sets = 1 << s;
    sets = (set*) malloc(num_sets * sizeof(set));
    for (int i = 0; i < num_sets; ++i) {
        sets[i].lines = (line*) malloc(E * sizeof(line));
        for (int j = 0; j < E; ++j)
            sets[i].lines[j].valid = 0;
    }

    // open trace file and parse
    FILE *fp = fopen(tracefile, "r");
    char buff[255];
    char instruction;
    unsigned long address, tag;
    unsigned set_index;
    int size;       // the size is omitted by the assumption of part A
    while (fgets(buff, 255, (FILE *)fp)) {
        if (buff[0] == 'I') continue;
        sscanf(buff, " %c %lx,%d", &instruction, &address, &size);

        if (vflag) printf("%c %lx,%d", instruction, address, size);  // verbose

        // compute tag, set index and offset
        address >>= b;      // omit offset
        set_index = address & ((1 << s) - 1);
        tag = address >> s;

        // treat L and S the same
        ++op_count;
        if (instruction == 'M') modify(set_index, tag);
        else load(set_index, tag);

        if (vflag) printf("%s", " \n");
    }
    fclose(fp);

    printSummary(hit, miss, eviction);

    // free allocated memory
    for (int i = 0; i < num_sets; ++i) {
        free(sets[i].lines);
    }
    free(sets);

    return 0;
}