// Carnegie Mellon University
//   Information Networking Institute and
//   School of Computer Science
//
// Master Thesis: A Monitoring Tool for Overlay Network
// By: TungFai Chan and Annie Cheng
//
// File: VirtualMemory.java
// Path: eventbase/VirtualMemory
// Description: Defines VM with buffer pool of pages, disk, and page replacement algorithm


package eventbase.virtualMemory;
import java.util.*;
import java.io.*;

public class VirtualMemory {

  // number of records per page
  public static final int RECORDSPERPAGE = 256;
  // number of page in memory
    // setting this value to 1 will caused threashing to the file system
    // which is an undisirable behavior to monitor
  public static final int PAGESINMEM = 128;
  // number of byes in a record
  public static final int BYTESPERREC = 32;

  // stress testing
  /*
  public static final int RECORDSPERPAGE = 2;
  public static final int PAGESINMEM = 3;
  public static final int BYTESPERREC = 20;
  */

  private Page[] memoryPool;
  private boolean[] referenceBitMap;
  private Disk disk;
  // given a key (page_no), returns index of page in memory
  private Hashtable pageTable;
  private int pageInDiskCount;
  private int pageInMemoryCount;
  private Page curEmptyPage;
  private int clockPtr;
  // keep track of where curEmptyPage is in the memory, never swap it out
  private int curEmptyPageIndexInMemory;
  private int lastInsertedRecordID;

  public VirtualMemory() {
    memoryPool = new Page[PAGESINMEM];
    referenceBitMap = new boolean[PAGESINMEM];
    try {
      disk = new Disk();
    } catch (IOException e) {
      System.err.println("Error Creating Disk");
    }
    pageTable = new Hashtable();
    pageInDiskCount = 0;
    pageInMemoryCount = 0;
    clockPtr = 0;
    for (int i = 0; i < PAGESINMEM; i++)
      this.referenceBitMap[i] = false;
    curEmptyPage = null;
    curEmptyPageIndexInMemory = -1;
    lastInsertedRecordID = -1;
  }

  // return number of bytes in Memory buffer
  public int getMemorySize() {
    if (pageInMemoryCount < PAGESINMEM)
      return ((pageInMemoryCount-1) * RECORDSPERPAGE + lastInsertedRecordID + 1) * BYTESPERREC;
    return ((PAGESINMEM - 1) * RECORDSPERPAGE + lastInsertedRecordID + 1) * BYTESPERREC;
  }

  // return number of bytes in Disk
  public int getDiskSize() {
    if (pageInMemoryCount < PAGESINMEM)
      return 0;   // haven't dump to disk yet
    return ((pageInDiskCount - 1) * RECORDSPERPAGE + lastInsertedRecordID) * BYTESPERREC;
  }

  private Page getEmptyPage() {
    if (curEmptyPage == null || curEmptyPage.isFull()) {
      curEmptyPage = new Page(this.pageInDiskCount);
      this.pageInDiskCount++;
      curEmptyPageIndexInMemory = insertToMemoryPool(curEmptyPage);
    }
    return curEmptyPage;
  }

  public int getAvailableRID() {
    return this.getEmptyPage().getNextRecordID();
  }

  // returns the index of where the new page is inserted in the memory pool
  private int insertToMemoryPool(Page p) {
    if (this.pageInMemoryCount < this.PAGESINMEM) { // still empty spot in memory pool
      this.memoryPool[this.pageInMemoryCount] = p;
      this.pageTable.put(new Integer(p.getPageID()), new Integer(this.pageInMemoryCount));
      this.referenceBitMap[this.pageInMemoryCount] = true;
      this.pageInMemoryCount++;
      return this.pageInMemoryCount-1;  // return index
    }else { // need to find a victim
      int victimIndex = this.FindVictim();
      Page victim = memoryPool[victimIndex];
      if (this.pageTable.remove(new Integer(victim.getPageID())) == null)
        System.err.println("VM ERROR:insertToBufferPool:PageNotInMemoryPool()");
      // dump victim to disk
      try {
        disk.writePage(victim);
      } catch (IOException e) {
        System.err.println("ERROR:VM:insertTOMemoryPool:" + e);
      }
      this.memoryPool[victimIndex] = p;
      this.referenceBitMap[victimIndex] = true;
      this.pageTable.put(new Integer(p.getPageID()), new Integer(victimIndex));
      return victimIndex;
    }
  }

  // record needs to be formed before insertion
  // records can be formed using Page.getNextRecordID()
  // returns pageID of record being insert
  public int InsertRecord(Record inRecord) {
    Page p = this.getEmptyPage();
    Record r = p.getRecordFromPage(inRecord.getRecordID());
    r.copyInto(inRecord.getBytes());
    p.insertRecordIntoPage(r);
    // 9/17 provide memory count statistics
    lastInsertedRecordID = inRecord.getRecordID();
    return p.getPageID();
  }

  public Record RetrieveRecord(int pgno, int rid) {
    Page p = getPage(pgno);
    if (p == null) {
      System.err.println("ERROR:VM: cannot find page in DISK/MEM" + pgno);
    }
    return p.getRecordFromPage(rid);
  }

  // check if page is in the memory, if not, retrieve from disk
  private Page getPage(int pgno) {
     // first check if page is in memory
    Integer memory_index = (Integer)this.pageTable.get(new Integer(pgno));
    if (memory_index == null) { // page in disk
      Page retVal = null;
      try {
        retVal = disk.readPage(pgno);
      } catch (IOException e) {
        System.err.println ("ERROR:VM:getPage():" + e);
      }
      insertToMemoryPool(retVal);
      return retVal;
    } else { // page in memory
      // set the reference bit
      this.referenceBitMap[memory_index.intValue()] = true;
      return this.memoryPool[memory_index.intValue()];
    }
  }

  // return victim index, leaset freq. used
  private int FindVictim() {
      // 09/14/00 bug fix, for page in memory equals to 1
      if (PAGESINMEM == 1)
	  return 0;
    int i = clockPtr;
    while (true) {
      if (!referenceBitMap[i%PAGESINMEM]) {
        clockPtr = (i + 1) % PAGESINMEM;
        return (i%PAGESINMEM);
      }
      // pin the current empty page, never swap it in to disk
      if (i%PAGESINMEM != curEmptyPageIndexInMemory)
        referenceBitMap[i%PAGESINMEM] = false;
      i++;
    }
  }

  public void printMemoryBuffer() {
    for (int i = 0; i < this.pageInMemoryCount; i++) {
      this.memoryPool[i].printPageContent();
    }
  }

  public void printReferenceBitMap() {
    System.out.println();
    System.out.println("Clock Pointer: " + this.clockPtr);
    System.out.println("Current Pinned Page: " + curEmptyPageIndexInMemory);
    for (int i =0; i < this.PAGESINMEM; i++ ) {
      System.out.println("["+ i + "]: " + this.referenceBitMap[i]);
    }

  }
}
