package simpledb;

import java.io.*;
import java.util.*;
import java.text.ParseException;

/**
 * HeapFile is an implementation of a DbFile that stores a collection
 * of tuples in no particular order.  Tuples are stored on pages, each of 
 * which is a fixed size, and the file is simply a collection of those 
 * pages. HeapFile works closely with HeapPage.  The format of HeapPages 
 * is described in the HeapPage constructor.
 *
 * @see simpledb.HeapPage#HeapPage
 * @author Sam Madden
 */
public class HeapFile implements DbFile {
 
    File f = null;
    int tableid = -1;
    
    // a hack to remember the last page that had a free slot
    int lastEmptyPage = -1;

    /**
     * Constructor.
     * Creates a new heap file that stores pages in the specified buffer pool.
     *
     * @param f The file that stores the on-disk backing store for this DbFile.
     */
    public HeapFile(File f) {
	this.f = f;
	this.tableid = f.getAbsoluteFile().hashCode();
    }
    
    /**
     * @return an ID uniquely identifying this HeapFile
     */
    public int id() {
	return tableid;
    }
    
    /**
     * Returns a Page from the file.
     */
    public HeapPage readPage(PageId pid) throws NoSuchElementException {
	HeapPageId id = (HeapPageId) pid;
	BufferedInputStream br = null;
	
	try {
	    br = new BufferedInputStream(new FileInputStream(f));
	    br.skip(id.pageno() * bytesPerPage());
	    
	    byte pageBuf[] = new byte[bytesPerPage()];
	    if(br.read(pageBuf, 0, bytesPerPage()) == -1){
		br.close();
		throw new NoSuchElementException("Read past end of table.");
	    }
	    br.close();
	    Debug.println("HeapFile.readPage: read page " + id.pageno(), 1);
	    HeapPage p = new HeapPage(id, pageBuf);
	    return p;
	    
	} catch(IOException e) {
	    // e.printStackTrace();
	    try {
		if (br != null) br.close();
	    } catch (IOException ioe) {
	    }
	    throw new NoSuchElementException(e.getMessage());
	}
    }
    
    /**
     * Writes the given page to the appropriate location in the file.
     */
    public void writePage(Page page) throws IOException {
	HeapPage p = (HeapPage) page; 	
	byte[] data = p.getPageData();
	RandomAccessFile rf = new RandomAccessFile(f, "rw");
	rf.seek(p.id().pageno() * bytesPerPage());
	rf.write(data);
	rf.close();
    }
    
    /**
     * Returns the number of pages in this HeapFile.
     */
    public int numPages() {
	return (int)(f.length() / bytesPerPage());
    }
    
    /**
     * Adds the specified tuple to the table under the specified TransactionId.
     *
     * @throws DbException
     * @throws IOException
     */
    public Vector<Page> addTuple(TransactionId tid, Tuple t) 
	throws DbException, IOException, TransactionAbortedException {
	// some code goes here
	return null;
	// not necessary for lab1
    }
    
    /**
     * Deletes the specified tuple from the table, under the specified
     * TransactionId.
     */
    public Page deleteTuple(TransactionId tid, Tuple t) 
	throws DbException, TransactionAbortedException {
	// some code goes here
	return null;
	// not necessary for lab1
    }
    
    /**
     * an iterator over all tuples on this file, over all pages.
     */
    public DbFileIterator iterator(TransactionId tid) {
        return new HeapFileIterator(tid);
    }
    
    /**
     * Returns the number of bytes in the header of a page in the HeapFile.
     * Assumes sizeof(int) == 4.
     */
    private int headerBytes() {
	TupleDesc td = Database.getCatalog().getTupleDesc(tableid);
	int hb = (((BufferPool.PAGE_SIZE / td.getSize()) / 32) +1) * 4;
	return hb;
    }

    /**
     * Returns the number of bytes on a page, including the number of bytes
     * in the header.
     */
    public int bytesPerPage() {
	return BufferPool.PAGE_SIZE + headerBytes();
    }

    /**
     * Iterator over all tuples in all pages in this table.
     */
    private class HeapFileIterator implements DbFileIterator {
        /*
         * The strategy for iterating is to keep 'page' as the next
         * page in the file that has tuples available, and 'pid' its
         * id. pageIter is an iterator over the tuples in that
         * page. When pageIter runs out of items, we should call
         * findNextPage to move to the next page.
         */
        private TransactionId tid;
        private Page page;
        private HeapPageId pid;
        private Iterator pageIter;

        public HeapFileIterator(TransactionId tid) {
            this.tid = tid;
        }

        public TransactionId getTransactionId() {
            return tid;
        }

        public void open() throws
            DbException, TransactionAbortedException {
            
            rewind();
        }

        public Tuple getNext() throws TransactionAbortedException,
                                      DbException, NoSuchElementException
        {
            if (pageIter == null) {
                throw new NoSuchElementException();
            }

            Tuple t = (Tuple) pageIter.next();

            if (!pageIter.hasNext()) {
                findNextPage();
            }

            return t;
        }
        
        public void rewind() throws DbException,
                                    TransactionAbortedException {
            page = null;
            pid = null;
            pageIter = null;
            
            findNextPage();
        }

        public TupleDesc getTupleDesc() {
            return Database.getCatalog().getTupleDesc(tableid);
        }

        public void close() {
            page = null;
            pid = null;
            pageIter = null;
        }

        private void findNextPage() throws DbException,
                                           TransactionAbortedException {
            // Select a page to load
            int pageNo;

            if (pid == null) {
                pageNo = 0;
            } else {
                pageNo = pid.pageno() + 1;
            }

            while (true) {
                if (pageNo >= numPages()) {
                    // Ran out of pages
                    pid = null;
                    page = null;
                    pageIter = null;
                    
                    return;
                }

                // Load the page
                pid = new HeapPageId(tableid, pageNo);
                page = Database.getBufferPool().getPage(null, pid, null);
                pageIter = page.iterator();

                
                if (pageIter.hasNext()) {
                    // Page has tuples; use it
                    return;
                } else {
                    // No tuples, keep trying.
                    pageNo++;
                }
            }
        }
    }
}

