#include "heapfile.h"
#include "error.h"

// Description :
// This function creates an empty heap file. It creates a header page and a data page for the file and flashes it to the disk. The creation is based on the steps mentioned at
//      http://pages.cs.wisc.edu/~cs564-1/project-stage4.html. 
// Signature:
// 	const Status createHeapFile(const string fileName)
// Input:
// 	fileName     : Name of the heap file to be created
// 	               
// Return value:
// 	Status       : OK : If there are no errors
// 		       FILEEXISTS : If a file with same name already exists	
// 		       Approriate error status from the delegate functions 

const Status createHeapFile(const string fileName)
{
    File* 		file;
    Status 		status;
    FileHdrPage*	hdrPage;
    int			hdrPageNo;
    int			newPageNo;
    Page*		newPage;

    // try to open the file. This should return an error
    status = db.openFile(fileName, file);
    if (status != OK)
    {
	// file doesn't exist. First create it and allocate
	// an empty header page and data page	
	if ((status = db.createFile(fileName))== OK){
		//open file
		if ((status  = db.openFile(fileName, file))== OK){
			//allocate page
			if((status = bufMgr->allocPage(file, hdrPageNo, newPage)) == OK){
				hdrPage = (FileHdrPage*) newPage;
				unsigned int i = 0;
				for (i=0; i< fileName.length(); i++){
					hdrPage->fileName[i] = (char)fileName[i];
				}
				hdrPage->fileName[i] = 0;
				hdrPage->pageCnt  = 0;
				hdrPage->recCnt   = 0;

				if((status = bufMgr->allocPage(file, newPageNo, newPage)) == OK){
					newPage->init(newPageNo);
					hdrPage->firstPage = newPageNo;
					hdrPage->lastPage  = newPageNo;
					hdrPage->pageCnt   = 1;		
					
					status = bufMgr->unPinPage(file, hdrPageNo, true);
					if (status != OK){
						//need to close file to have consistent book keeping
						db.closeFile(file);
					 	return status;
					}
					
					status = bufMgr->unPinPage(file, newPageNo, true);	
					if (status != OK){
						//need to close file to have consistent book keeping	
						db.closeFile(file);
					 	return status;
					}


					status = bufMgr->flushFile(file);
					if (status != OK){
						//need to close file to have consistent book keeping
						 db.closeFile(file);
						 return status;
					}
					//need to close file to have consistent book keeping
					db.closeFile(file);	
					return OK;
				}
				else{
					//need to close file to have consistent book keeping
					db.closeFile(file);
					return status;
				}
			}
			else{
				//need to close file to have consistent book keeping
				db.closeFile(file);
				return status;
			}
		}
		return status;		
		
	}
	return status;
		
    }
    return (FILEEXISTS);
}

// routine to destroy a heapfile
const Status destroyHeapFile(const string fileName)
{
	return (db.destroyFile (fileName));
}

// Description :
// 	This is a constructor for heap file. It open the file, reads the header page and data page from the disk into the buffer pool.
// Signature:
// 	 HeapFile(const string &fileName, Status &returnStatus)
// Input:
// 	fileName     : Name of the heap file to be opened
// 	returnStatus : Value Result argument
// 	               OK : If there are no errors
// 		       Approriate error status from the delegate functions 

HeapFile::HeapFile(const string & fileName, Status& returnStatus)
{
    Status 	status;
    Page*	pagePtr;

    cout << "opening file " << fileName << endl;

    // open the file and read in the header page and the first data page
    if ((status = db.openFile(fileName, filePtr)) == OK)
    {
	// get the first page of the file		
	if((status = filePtr->getFirstPage(headerPageNo)) == OK){
		status = bufMgr->readPage(filePtr, headerPageNo, pagePtr);
		if (status != OK) {
			returnStatus =  status;
			return;
		}
		headerPage = (FileHdrPage*) pagePtr;
		hdrDirtyFlag = false;
		
		curPageNo = headerPage->firstPage;
		status = bufMgr->readPage(filePtr, curPageNo, curPage);
		if (status != OK){
			 returnStatus =  status;
			 return;
		}
		curDirtyFlag = false;
		// Initialize record to be null
		curRec = NULLRID;
		returnStatus = OK;
		return;
	}
		
	else{
		returnStatus =  status;
		return;
	}			
    }
    else
    {
    	cerr << "open of heap file failed\n";
		returnStatus = status;
		return;
    }
}

// the destructor closes the file
HeapFile::~HeapFile()
{
    Status status;
    cout << "invoking heapfile destructor on file " << headerPage->fileName << endl;

    // see if there is a pinned data page. If so, unpin it 
    if (curPage != NULL)
    {
    	status = bufMgr->unPinPage(filePtr, curPageNo, curDirtyFlag);
		curPage = NULL;
		curPageNo = 0;
		curDirtyFlag = false;
		if (status != OK) cerr << "error in unpin of date page\n";
    }
	
	 // unpin the header page
    status = bufMgr->unPinPage(filePtr, headerPageNo, hdrDirtyFlag);
    if (status != OK) cerr << "error in unpin of header page\n";
	
	// status = bufMgr->flushFile(filePtr);  // make sure all pages of the file are flushed to disk
	// if (status != OK) cerr << "error in flushFile call\n";
	// before close the file
	status = db.closeFile(filePtr);
    if (status != OK)
    {
		cerr << "error in closefile call\n";
		Error e;
		e.print (status);
    }
}

// Return number of records in heap file

const int HeapFile::getRecCnt() const
{
  return headerPage->recCnt;
}

// Description :
//	This function retrives an arbitrary record from a file. if the record is not on the currently pinned page, the current
//	page is unpinned and the requird page is read into the buffer pool and pinned. 
//
// Signature:
// 	 getRecord(const RID & rid, Record & rec)
// Input:
// 	rid          : Id of the record to be retrieved.
// 	rec 	     : Value Result argument. It contains the record corresponding to the rid.
// 		
// Output: 	     
//	Status	     : OK : if no errors
// 		       Approriate error status from the delegate functions 


const Status HeapFile::getRecord(const RID & rid, Record & rec)
{
    Status status;

    // cout<< "getRecord. record (" << rid.pageNo << "." << rid.slotNo << ")" << endl;
    // check if the record is present in the pinned page
    if(rid.pageNo == curPageNo){
	status = curPage->getRecord(rid,rec);
	if(status != OK) return status;
	curRec = rid;
        return OK;	
    } 
    else{
	//unpin the current page
	status = bufMgr->unPinPage(filePtr, curPageNo, curDirtyFlag);
	if (status != OK) return status;
	curPage = NULL;
	curPageNo = rid.pageNo;
	curDirtyFlag = false;
	status = bufMgr->readPage(filePtr, curPageNo, curPage);
	if (status != OK) return status;
	
	status = curPage->getRecord(rid, rec);
	if(status != OK) return status;
	curRec = rid;
        return OK;		
    }
    
}

HeapFileScan::HeapFileScan(const string & name,
			   Status & status) : HeapFile(name, status)
{
    filter = NULL;
}

const Status HeapFileScan::startScan(const int offset_,
				     const int length_,
				     const Datatype type_, 
				     const char* filter_,
				     const Operator op_)
{
    if (!filter_) {                        // no filtering requested
        filter = NULL;
        return OK;
    }
    
    if ((offset_ < 0 || length_ < 1) ||
        (type_ != STRING && type_ != INTEGER && type_ != FLOAT) ||
        (type_ == INTEGER && length_ != sizeof(int)
         || type_ == FLOAT && length_ != sizeof(float)) ||
        (op_ != LT && op_ != LTE && op_ != EQ && op_ != GTE && op_ != GT && op_ != NE))
    {
        return BADSCANPARM;
    }

    offset = offset_;
    length = length_;
    type = type_;
    filter = filter_;
    op = op_;

    return OK;
}


const Status HeapFileScan::endScan()
{
    Status status;
    // generally must unpin last page of the scan
    if (curPage != NULL)
    {
        status = bufMgr->unPinPage(filePtr, curPageNo, curDirtyFlag);
        curPage = NULL;
        curPageNo = 0;
		curDirtyFlag = false;
        return status;
    }
    return OK;
}

HeapFileScan::~HeapFileScan()
{
    endScan();
}

const Status HeapFileScan::markScan()
{
    // make a snapshot of the state of the scan
    markedPageNo = curPageNo;
    markedRec = curRec;
    return OK;
}

const Status HeapFileScan::resetScan()
{
    Status status;
    if (markedPageNo != curPageNo) 
    {
		if (curPage != NULL)
		{
			status = bufMgr->unPinPage(filePtr, curPageNo, curDirtyFlag);
			if (status != OK) return status;
		}
		// restore curPageNo and curRec values
		curPageNo = markedPageNo;
		curRec = markedRec;
		// then read the page
		status = bufMgr->readPage(filePtr, curPageNo, curPage);
		if (status != OK) return status;
		curDirtyFlag = false; // it will be clean
    }
    else curRec = markedRec;
    return OK;
}

// Description :
//	Returns (via the outRid parameter) the RID of the next record that satisfies the scan predicate.
//	The processing is done as per http://pages.cs.wisc.edu/~cs564-1/project-stage4.html#scanNext
//
// Signature:
// 	 scanNext(RID & outRid)
// Input:
// 	outRid 	     : Value Result argument. It contains the id of the next record that satisfies the filter condition.
// 		
// Output: 	     
//	Status	     : OK : if no errors
//		     : FILEEOF : when end of file is reached after scanning all the pages
// 		       Approriate error status from the delegate functions 
//
const Status HeapFileScan::scanNext(RID& outRid)
{
    Status 	status = OK;
    RID		nextRid;
    //RID		tmpRid;
    int 	nextPageNo;
    Record      rec;

    do{
	//flag to indicate whether file has more pages left to be scanned
	bool morePages = false;
	//if curRec is NULLRID then get the first record of the current page as cur Rec
    	if(curRec.pageNo == -1 && curRec.slotNo == -1) {
		status = curPage->firstRecord(nextRid);
		if(status == NORECORDS) morePages = true;
    	}
    	else {
		//get the next record from the position of the current record in the same page
		status = curPage->nextRecord(curRec, nextRid);
	}
    	while((status == ENDOFPAGE)||(morePages == true)){
		morePages = false;
 		status = curPage->getNextPage(nextPageNo);
		if(status != OK) return status;
		if(nextPageNo == -1) return FILEEOF;
		status = bufMgr->unPinPage(filePtr, curPageNo, curDirtyFlag);
		if(status != OK) return status;
		curPage = NULL;
		curPageNo = nextPageNo;
		curDirtyFlag = false;
		status = bufMgr->readPage(filePtr, curPageNo, curPage);
		if(status != OK) return status;
		status = curPage->firstRecord(nextRid);
		if(status == NORECORDS) morePages = true;
		
	 }	
	 if (status == OK){
	       	 curRec = nextRid;
   	 }	
   	 else{ 
		return status;
    	 }	
		
	status = curPage->getRecord(curRec, rec);
	if (status != OK) return status;

    }while( !matchRec(rec));
    outRid = curRec;
    return OK;
	 	
}


// returns pointer to the current record.  page is left pinned
// and the scan logic is required to unpin the page 

const Status HeapFileScan::getRecord(Record & rec)
{
    return curPage->getRecord(curRec, rec);
}

// delete record from file. 
const Status HeapFileScan::deleteRecord()
{
    Status status;

    // delete the "current" record from the page
    status = curPage->deleteRecord(curRec);
    curDirtyFlag = true;

    // reduce count of number of records in the file
    headerPage->recCnt--;
    hdrDirtyFlag = true; 
    return status;
}


// mark current page of scan dirty
const Status HeapFileScan::markDirty()
{
    curDirtyFlag = true;
    return OK;
}

const bool HeapFileScan::matchRec(const Record & rec) const
{
    // no filtering requested
    if (!filter) return true;

    // see if offset + length is beyond end of record
    // maybe this should be an error???
    if ((offset + length -1 ) >= rec.length)
	return false;

    float diff = 0;                       // < 0 if attr < fltr
    switch(type) {

    case INTEGER:
        int iattr, ifltr;                 // word-alignment problem possible
        memcpy(&iattr,
               (char *)rec.data + offset,
               length);
        memcpy(&ifltr,
               filter,
               length);
        diff = iattr - ifltr;
        break;

    case FLOAT:
        float fattr, ffltr;               // word-alignment problem possible
        memcpy(&fattr,
               (char *)rec.data + offset,
               length);
        memcpy(&ffltr,
               filter,
               length);
        diff = fattr - ffltr;
        break;

    case STRING:
        diff = strncmp((char *)rec.data + offset,
                       filter,
                       length);
        break;
    }

    switch(op) {
    case LT:  if (diff < 0.0) return true; break;
    case LTE: if (diff <= 0.0) return true; break;
    case EQ:  if (diff == 0.0) return true; break;
    case GTE: if (diff >= 0.0) return true; break;
    case GT:  if (diff > 0.0) return true; break;
    case NE:  if (diff != 0.0) return true; break;
    }

    return false;
}

InsertFileScan::InsertFileScan(const string & name,
                               Status & status) : HeapFile(name, status)
{
  //Do nothing. Heapfile constructor will bread the header page and the first
  // data page of the file into the buffer pool
}

InsertFileScan::~InsertFileScan()
{
    Status status;
    // unpin last page of the scan
    if (curPage != NULL)
    {
        status = bufMgr->unPinPage(filePtr, curPageNo, true);
        curPage = NULL;
        curPageNo = 0;
        if (status != OK) cerr << "error in unpin of data page\n";
    }
}

// Description :
//	This function inserts the input record into the last page of the file. If the page is full it allocates new page and inserts the record.
//	The processing is done as per http://pages.cs.wisc.edu/~cs564-1/project-stage4.html#insertRecord
//
// Signature:
// 	 insertRecord(const Record & rec, RID& outRid)
// Input:
// 	rec 	     : Record that needs to be inserted
// 	outRid 	     : Value Result argument. It contains the id of the record inserted
// 		
// Output: 	     
//	Status	     : OK : if no errors
//		     : INVALIDRECLEN : record length is greater than the permissible length
// 		       Approriate error status from the delegate functions 
//
const Status InsertFileScan::insertRecord(const Record & rec, RID& outRid)
{
    Page*	newPage;
    int		newPageNo;
    Status	status;
    Status	unpinstatus;
    //RID		rid;

    // check for very large records
    if ((unsigned int) rec.length > PAGESIZE-DPFIXED)
    {
        // will never fit on a page, so don't even bother looking
        return INVALIDRECLEN;
    }
    // if pinned page is the last page of the file
    if (curPageNo != headerPage->lastPage) {
	if (curPage != NULL){
		unpinstatus = bufMgr->unPinPage(filePtr, curPageNo, curDirtyFlag);
		if (unpinstatus != OK) return unpinstatus;
		curPage = NULL;
		curPageNo = 0;
		curDirtyFlag = false;
	}	
      	//read the last page into the buffer
	curPageNo = headerPage->lastPage;
	status = bufMgr->readPage(filePtr, curPageNo, curPage);
	if (status != OK) return status;
   }
   
   status = curPage->insertRecord(rec, outRid);
   if(status == NOSPACE){
        
	status = bufMgr->allocPage(filePtr, newPageNo, newPage);
	if(status != OK) return status;
	newPage->init(newPageNo);
	
	status = curPage->setNextPage(newPageNo);
	if(status != OK) return status;

	unpinstatus = bufMgr->unPinPage(filePtr, curPageNo, curDirtyFlag);
	if (unpinstatus != OK) return unpinstatus;
	curPage = newPage;
	curPageNo = newPageNo;
	curDirtyFlag = false;
	
	status = curPage->insertRecord(rec, outRid);
	
	if(status != OK) return status;
	
	headerPage->pageCnt++;
	headerPage->recCnt++;
	headerPage->lastPage = curPageNo;
	hdrDirtyFlag = true;
	curDirtyFlag = true;
	
	return OK;
   }
   else if(status == OK){
	curDirtyFlag = true;
	headerPage->recCnt++;
	hdrDirtyFlag = true;

 	return OK;
   }
   else
	return status;	
  
}


