#include <memory.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <fcntl.h>
#include <iostream>
#include <stdio.h>
#include "page.h"
#include "buf.h"

#define ASSERT(c)  { if (!(c)) { \
		       cerr << "At line " << __LINE__ << ":" << endl << "  "; \
                       cerr << "This condition should hold: " #c << endl; \
                       exit(1); \
		     } \
                   }

//----------------------------------------
// Constructor of the class BufMgr
//----------------------------------------

BufMgr::BufMgr(const int bufs)
{
    numBufs = bufs;

    bufTable = new BufDesc[bufs];
    memset(bufTable, 0, bufs * sizeof(BufDesc));
    for (int i = 0; i < bufs; i++) 
    {
        bufTable[i].frameNo = i;
        bufTable[i].valid = false;
    }

    bufPool = new Page[bufs];
    memset(bufPool, 0, bufs * sizeof(Page));

    int htsize = ((((int) (bufs * 1.2))*2)/2)+1;
    hashTable = new BufHashTbl (htsize);  // allocate the buffer hash table

    clockHand = bufs - 1;
}


BufMgr::~BufMgr() {

    // flush out all unwritten pages
    for (int i = 0; i < numBufs; i++) 
    {
        BufDesc* tmpbuf = &bufTable[i];
        if (tmpbuf->valid == true && tmpbuf->dirty == true) {

#ifdef DEBUGBUF
            cout << "flushing page " << tmpbuf->pageNo
                 << " from frame " << i << endl;
#endif

            tmpbuf->file->writePage(tmpbuf->pageNo, &(bufPool[i]));
        }
    }

    delete [] bufTable;
    delete [] bufPool;
}


const Status BufMgr::allocBuf(int & frame) 
{
     cout<<"allocating Frame ";
     int counter = 0;
     while(counter <=2* numBufs){
	counter++;
	advanceClock();
	Status status = OK;
	BufDesc* tmpbuf = &(bufTable[clockHand]);
	if (tmpbuf->valid == true){
		if (tmpbuf->refbit == true){
			tmpbuf->refbit = false;
			continue;
		}
		else if(tmpbuf->pinCnt>0){
			continue;
		}
		else if(tmpbuf->dirty == true){
			status = tmpbuf->file->writePage(tmpbuf->pageNo,&bufPool[tmpbuf->frameNo]);
			
			if (status != OK){
				return UNIXERR;
			}
			else{
				status = hashTable->remove(tmpbuf->file,tmpbuf->pageNo);
				if (status == HASHTBLERROR)
					return status;			
				else{
					//memset(&bufPool[tmpbuf->frameNo],0,sizeof(Page));
					frame = tmpbuf->frameNo;
					tmpbuf->Clear();
					return status;
				}
				
			}		
		}
		else{
			status = hashTable->remove(tmpbuf->file,tmpbuf->pageNo);
			cout <<"Removed Page "<<tmpbuf->pageNo<<"From file"<<tmpbuf->file<<endl;
			if (status != OK)
				return status;			
			else{
				//memset(&bufPool[tmpbuf->frameNo],0,sizeof(Page));	
				frame = tmpbuf->frameNo;
				tmpbuf->Clear();
				return status;
			}
		}
	}	
	else{
		frame = tmpbuf->frameNo;
		tmpbuf->Clear();
		return status;
	}

     }
     return BUFFEREXCEEDED;
}

	
const Status BufMgr::readPage(File* file, const int PageNo, Page*& page)
{
	Status status = OK;
	BufDesc* tmpbuf;
	int frameNo;
	status = hashTable->lookup(file,PageNo,frameNo);
	cout << status;
	if (status != OK){
		status = allocBuf(frameNo);
		if(status == OK){
			tmpbuf = &(bufTable[frameNo]);
			status = file->readPage(PageNo,&bufPool[tmpbuf->frameNo]);
			if (status == OK){
				status = hashTable->insert(file,PageNo,tmpbuf->frameNo);
				if(status == OK){
					tmpbuf->Set(file,PageNo);
					tmpbuf->frameNo = frameNo;
					page = &bufPool[frameNo];
					return status;
				}
				else{
					return HASHTBLERROR;
				}
			}
			else{
				return UNIXERR;
			}
		}
		else{
			return status;
		}
	}	
	else{
		tmpbuf = &(bufTable[frameNo]);
		tmpbuf->refbit = true;
		(tmpbuf->pinCnt)++;
		page = &bufPool[frameNo];
		return status;
	}

}


const Status BufMgr::unPinPage(File* file, const int PageNo, 
			       const bool dirty) 
{
	Status status = OK;
	int frameNo;
	status = hashTable->lookup(file,PageNo,frameNo);
	if (status != OK)
		return HASHNOTFOUND;
	else{
		BufDesc* tmpbuf = &(bufTable[frameNo]);
		if (tmpbuf->pinCnt > 0){
			(tmpbuf->pinCnt)--;
			if(dirty == true){
				tmpbuf->dirty = true;
				
			}
			return status;
		}
		else {
			status = PAGENOTPINNED;
			return status;
		}
	}
	
}

const Status BufMgr::allocPage(File* file, int& pageNo, Page*& page) 
{
	cout<<"file alloc Page ";
	Status status = OK;
	status = file->allocatePage(pageNo);
	if (status != OK){
		return UNIXERR;
	}
	int frameNo;
	status = allocBuf(frameNo);
	cout<<"frame No = "<<frameNo<<endl;
	if (status != OK)
		return status;
	status = hashTable->insert(file,pageNo,frameNo);
	if (status != OK)
		return status;
	BufDesc* tmpbuf = &(bufTable[frameNo]);
	tmpbuf->Set(file,pageNo);
	tmpbuf->frameNo = frameNo;
	page = &bufPool[frameNo];
	return status;
}

const Status BufMgr::disposePage(File* file, const int pageNo) 
{
    // see if it is in the buffer pool
    Status status = OK;
    int frameNo = 0;
    status = hashTable->lookup(file, pageNo, frameNo);
    if (status == OK)
    {
        // clear the page
        bufTable[frameNo].Clear();
    }
    status = hashTable->remove(file, pageNo);

    // deallocate it in the file
    return file->disposePage(pageNo);
}

const Status BufMgr::flushFile(const File* file) 
{
  Status status;

  for (int i = 0; i < numBufs; i++) {
    BufDesc* tmpbuf = &(bufTable[i]);
    if (tmpbuf->valid == true && tmpbuf->file == file) {

      if (tmpbuf->pinCnt > 0)
	  return PAGEPINNED;

      if (tmpbuf->dirty == true) {
#ifdef DEBUGBUF
	cout << "flushing page " << tmpbuf->pageNo
             << " from frame " << i << endl;
#endif
	if ((status = tmpbuf->file->writePage(tmpbuf->pageNo,
					      &(bufPool[i]))) != OK)
	  return status;

	tmpbuf->dirty = false;
      }

      hashTable->remove(file,tmpbuf->pageNo);

      tmpbuf->file = NULL;
      tmpbuf->pageNo = -1;
      tmpbuf->valid = false;
    }

    else if (tmpbuf->valid == false && tmpbuf->file == file)
      return BADBUFFER;
  }
  
  return OK;
}


void BufMgr::printSelf(void) 
{
    BufDesc* tmpbuf;
  
    cout << endl << "Print buffer...\n";
    for (int i=0; i<numBufs; i++) {
        tmpbuf = &(bufTable[i]);
        cout << i << "\t" << (char*)(&bufPool[i]) 
             << "\tpinCnt: " << tmpbuf->pinCnt;
    
        if (tmpbuf->valid == true)
            cout << "\tvalid\n";
        cout << endl;
    };
}


