////////////////////////////////////////////////////////////////////////
// COMP1521 26T2 --- Assignment 2: `mipsee', a simple MIPS emulator
// <https://cgi.cse.unsw.edu.au/~cs1521/26T2/assignments/ass2/index.html>
//
// Written by YOUR-NAME-HERE (z5555555) on INSERT-DATE-HERE.
// INSERT-DESCRIPTION-OF-PROGAM-HERE
//
// 2027-07-10   v1.0    Team COMP1521 <cs1521 at cse.unsw.edu.au>


#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
#include <string.h>


// Do not rename or modify this struct! It's directly used
// by the subset 1 autotests.

struct mipsee_file {
    uint32_t num_instructions;
    uint32_t entry_point;
    uint32_t *instructions;
    uint32_t *debug_offsets;
    uint16_t memory_size;
    uint8_t *initial_data;
};

void read_mipsee_file(char *path, struct mipsee_file *executable);
void execute_mipsee(struct mipsee_file *executable, int trace_mode, char *path);
void print_uint32_in_hexadecimal(FILE *stream, uint32_t value);
void print_int32_in_decimal(FILE *stream, int32_t value);

// Add prototypes for your functions here

int main(int argc, char *argv[]) {

    // Do NOT CHANGE this function
    // put your code in read_mipsee_file, execute_mipsee and your own functions

    char *pathname;
    int trace_mode = 0;

    if (argc == 2) {
        pathname = argv[1];
    } else if (argc == 3 && strcmp(argv[1], "-t") == 0) {
        trace_mode = 1;
        pathname = argv[2];
    } else {
        fprintf(stderr, "Usage: mipsee [-t] <executable>\n");
        exit(1);
    }

    struct mipsee_file executable = {0};
    read_mipsee_file(pathname, &executable);

    execute_mipsee(&executable, trace_mode, pathname);

    free(executable.debug_offsets);
    free(executable.instructions);
    free(executable.initial_data);

    return 0;
}


// Read a MIPSEE executable file from the file at `path` into `executable`.
// Exits the program if the file can't be accessed or is not well-formed.
void read_mipsee_file(char *path, struct mipsee_file *executable) {


    // Put your code here


    // Hints:
    // * Open the file with fopen (using path)
    //      * Check that the file succesfully opened
    // * Read in the first 4 bytes (e.g. using fgetc)
    //     checking that they are  0x49, 0x4d, 0x50, 0x53
    // * Read in num_instructions (see last hint)
    // * Read in entry_point  (see last hint)
    //  malloc space for an array of uint32_t's with length num_instructions
    // * Loop num_instructions times, reading in an instruction
    //     and placing the result in your instructions array
    // * Allocate and read in the debug_offsets in the same way as the
    //     instructions
    // * Read in the memory_size (be careful, it's only 2 bytes)
    // * Allocate and read in the initial_data based on memory_size
    // * Close the file
    //
    // * Don't forget to store value in all 6 fields of the executable struct
    //
    // * We recommend you write a function which reads in a single little
    //   endian 32 bit integer from a file. One way to do this is to call fgetc
    //   four times, and then assemble the bytes together using << and |
}


// Execute an MIPSEE program
void execute_mipsee(struct mipsee_file *executable, int trace_mode, char *path) {

    // Put your code here
    // Note: you won't need to use the `trace_mode` or `path` arguments
    //       until you get to subset 4.
}


//
// Put your functions here
//


// Printing out exact-width integers in a portable way is slightly tricky,
// since we can't assume that a uint32_t is an unsigned int or that a
// int32_t is an int. So we can't just use %x or %d. A solution is to use
// printf format specifiers from the <inttypes.h> header. The following two
// functions are provided for your convenience so that you just call them
// without worring about <inttypes.h>, although you don't have to use use them.

// Print out a 32 bit integer in hexadecimal, including the leading `0x`.
//
// @param stream The file stream to output to.
// @param value The 32 bit integer to output.
void print_uint32_in_hexadecimal(FILE *stream, uint32_t value) {
    fprintf(stream, "0x%08" PRIx32, value);
}

// Print out a signed 32 bit integer in decimal.
//
// @param stream The file stream to output to.
// @param value The 32 bit integer to output.
void print_int32_in_decimal(FILE *stream, int32_t value) {
    fprintf(stream, "%" PRIi32, value);
}
