#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

#define OFFSETBITS 12
#define PAGESIZE (1 << OFFSETBITS)
#define PAGETABLEBITS 10
#define ARCH 32
int framesSize;
unsigned int pageFault = 0;
unsigned int pageHits = 0;
unsigned int pagesSwapped = 0;
int time = 0;

typedef struct frameEntry {
	unsigned int pageNumber;
	int lastUsed;
	bool dirty;
} frameEntry;

frameEntry *frames;

int getPageOffsetAddress(unsigned long logicalAddress) {
	// Last 12 bits of logical address
	// Page size is 4096 bytes, and we bitwise AND with 4095 (111111111111) to get the last 12 bits
	return logicalAddress & (PAGESIZE - 1);
}

int getPageNumber(unsigned long logicalAddress) {
	// First 20 bits of logical address
	// Bitwise shift right 12 bits to get the first 20 bits, as the last 12 bits are discarded
	return logicalAddress >> OFFSETBITS;

}

void printPhysicalAddress(int frameID, unsigned long logicalAddress) {
	printf("%lu -> %i\n", logicalAddress, frameID * PAGESIZE + getPageOffsetAddress(logicalAddress));
}

double computeFormula() {
	// probabilityOfNoPageFault ∗ 10 + probabilityOfMajorPageFaultWithOneCopy ∗ 1000 + probabilityOfMajorPageFaultWithTwoCopyOperations ∗ 3000
	double probabilityOfNoPageFault = 1 - (double) pageFault / (pageFault + pageHits);
	double probabilityOfMajorPageFaultWithOneCopy = (double) pageFault / (pageFault + pageHits) * (1 - (double) pagesSwapped / pageFault);
	double probabilityOfMajorPageFaultWithTwoCopyOperations = (double) pageFault / (pageFault + pageHits) * (double) pagesSwapped / pageFault;
	return probabilityOfNoPageFault * 10 + probabilityOfMajorPageFaultWithOneCopy * 1000 + probabilityOfMajorPageFaultWithTwoCopyOperations * 3000;
}

// Int so we can return the index of the frame if it is in the frames array
int isInFrames(int pageNumber) {
	for (int i = 0; i < framesSize; i++) {
		if (frames[i].pageNumber == pageNumber) {
			return i;
		}
	}
	return -1;
}

// Swap out the least recently used frame
// Returns the index of the frame that was swapped out
int swapOut() {
	// Find the least recently used frame
	int leastRecentlyUsed = 0x7FFFFFFF;
	int leastRecentlyUsedIndex = -1;
	for (int i = 0; i < framesSize; i++) {
		if (frames[i].lastUsed < leastRecentlyUsed) {
			leastRecentlyUsed = frames[i].lastUsed;
			leastRecentlyUsedIndex = i;
		}
	}
	// Swap out is implied, so we increment pageSwapped
	// If the frame is dirty, we need to write it to disk, so we increment pagesSwapped
	if (frames[leastRecentlyUsedIndex].dirty) {
		pagesSwapped++;
	}

	return leastRecentlyUsedIndex;
}

// Swap in a page
// Returns the index of the frame that was swapped in
int swapIn(int pageNumber) {
	// Find the first empty frame
	for (int i = 0; i < framesSize; i++) {
		if (frames[i].pageNumber == -1) {
			pageFault++;
			frames[i].pageNumber = pageNumber;
			return i;
		}
	}
	// If no empty frames, swap out a frame
	int frameID = swapOut();
	frames[frameID].pageNumber = pageNumber;
	pageFault++;
	return frameID;
}

// Read from an address
void readFromAddress(unsigned long logicalAddress) {
	unsigned int pageNumber = getPageNumber(logicalAddress);
	int frameID = isInFrames(pageNumber);
	if (isInFrames(pageNumber) == -1) {
		frameID = swapIn(pageNumber);
	} else {
		pageHits++;
	}
	frames[frameID].lastUsed = time;
	printPhysicalAddress(frameID, logicalAddress);
}

// Write to an address, only difference is that we set the dirty bit to true
void writeToAddress(unsigned long logicalAddress) {
	unsigned int pageNumber = getPageNumber(logicalAddress);
	int frameID = isInFrames(pageNumber);
	if (isInFrames(pageNumber) == -1) {
		frameID = swapIn(pageNumber);
	} else {
		pageHits++;
	}
	frames[frameID].dirty = true;
	frames[frameID].lastUsed = time;
	printPhysicalAddress(frameID, logicalAddress);
}


int main(int argc, char **argv) {
	//Disable printf buffering for easier debugging
	setbuf(stdout, NULL);

	framesSize = atoi(argv[1]);
	frames = calloc(framesSize, sizeof(frameEntry));
	// Initialize frames to -1, so we can check if a frame is empty
	for (int i = 0; i < framesSize; i++) {
		frames[i].pageNumber = -1;
		frames[i].lastUsed = -1;
		frames[i].dirty = false;
	}
	unsigned long logicalAddress;
	char operation;
	printf("Logical addresses -> Physical addresses:\n");
	while (EOF != scanf("%c %lu\n", &operation, &logicalAddress)) {
		if (operation == 'r') {
			readFromAddress(logicalAddress);
		} else {
			writeToAddress(logicalAddress);
		}
		time++;
	}

	printf("\nStats:\nmajor page faults = %u\npage hits = %u\npages swapped out = %u\nEffective Access Time = %.3lf\n",
		   pageFault,
		   pageHits,
		   pagesSwapped,
		   computeFormula()
		   );
	return EXIT_SUCCESS;
}
