package simpledb.unittest;

import simpledb.*;
import simpledb.unittest.Utility.SkeletonFile;

import java.util.*;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import junit.framework.JUnit4TestAdapter;

public class HeapPageTest {

    private byte[] data;
    private HeapPageId pid;

    private static final String dataPath = 
	"src/simpledb/unittest/testdata/simple.dat";

    // simple.dat contains two columns. these are the data in order of
    // data[0][0], data[0][1], data[1][0], ... with data[row][col]
    private static final int[] expected = new int[] { 
	31933, 862, 29402, 56883, 1468, 5825, 17876, 52278, 6350, 36090, 
	34784, 43771, 28617, 56874, 19209, 23253, 56462, 24979, 51440, 56685, 
	3596, 62307, 45569, 2719, 22064, 43575, 42812, 44947, 22189, 19724, 
	33549, 36554, 9086, 53184, 42878, 33394, 62778, 21122, 17197, 16388
    };

    /**
     * Set up initial resources for each unit test.
     */
    @Before public void setUp() throws Exception {
	this.data = Utility.readFileBytes(dataPath);
	this.pid = new HeapPageId(-1, -1);
	Database.getCatalog().addTable(new SkeletonFile(-1), Utility.getTupleDesc(2));
    }

    /**
     * Unit test for HeapPage.id()
     */
    @Test public void id() throws Exception {
	HeapPage page = new HeapPage(pid, data);
	assertEquals(pid, page.id());
    }

    /**
     * Unit test for HeapPage.iterator()
     */
    @Test public void testIterator() throws Exception {
	HeapPage page = new HeapPage(pid, data);
	Iterator it = page.iterator();

	int row = 0;
	while (it.hasNext()) {
	    Tuple tup = (Tuple) it.next();
	    IntField f0 = (IntField) tup.getField(0);
	    IntField f1 = (IntField) tup.getField(1);
	    
	    // TODO(ghuo): this is kind of a kludge, but we're using the fact
	    // that hashCode returns the int value of an IntField
	    assertEquals(expected[row * 2], f0.hashCode());
	    assertEquals(expected[row * 2 + 1], f1.hashCode());
	    row++;
	}
    }

    /**
     * Unit test for HeapPage.isDirty()
     */
    @Test public void testDirty() throws Exception {
	TransactionId tid = new TransactionId();
	HeapPage page = new HeapPage(pid, data);
	page.markDirty(true, tid);
	TransactionId dirtier = page.isDirty();
	assertEquals(true, dirtier != null);
	assertEquals(true, dirtier == tid);
	
	page.markDirty(false, tid);
	dirtier = page.isDirty();
	assertEquals(false, dirtier != null);
    }
    
    /**
     * Unit test for HeapPage.getNumEmptySlots()
     */
    @Test public void getNumEmptySlots() throws Exception {
	HeapPage page = new HeapPage(pid, data);
	assertEquals(492, page.getNumEmptySlots());
    }

    /**
     * Unit test for HeapPage.getSlot()
     */
    @Test public void getSlot() throws Exception {
	HeapPage page = new HeapPage(pid, data);

	for (int i = 0; i < 20; ++i)
	    assertTrue(page.getSlot(i));

	for (int i = 20; i < 512; ++i)
	    assertFalse(page.getSlot(i));
    }
    
    /**
     * Unit test for HeapPage.addTuple()
     */
    @Test public void addTuple() throws Exception {
	HeapPage page = new HeapPage(pid, data);
	int free = page.getNumEmptySlots();
	Iterator it;
      
	// NOTE(ghuo): this nested loop existence check is slow, but it
	// shouldn't make a difference for n = 512 slots.
	for (int i = -492; i <= -1; ++i) {
	    Tuple addition = Utility.getHeapTuple(i, 2);
	    page.addTuple(addition);
	    assertEquals(free - (i + 493), page.getNumEmptySlots());
	  
	    // loop through the iterator to ensure that the tuple actually exists
	    // on the page
	    it = page.iterator();
	    boolean found = false;
	    while (it.hasNext()) {
		Tuple tup = (Tuple) it.next();
		if (Utility.compareTuples(addition, tup)) {
		    found = true;

		    // verify that the RecordID is sane
		    assertEquals(page.id(), tup.getRecordID().pageid());
		    break;
		}
	    }
	    assertTrue(found);
	}

	// now, the page should be full.
	try {
	    page.addTuple(Utility.getHeapTuple(0, 2));
	    throw new Exception("page should be full; expected DbException");
	} catch (DbException e) {
	    // explicitly ignored
	}
    }

    /**
     * Unit test for HeapPage.deleteTuple() with false tuples
     */
    @Test(expected=DbException.class)
	public void deleteNonexistentTuple() throws Exception {
	HeapPage page = new HeapPage(pid, data);
	page.deleteTuple(Utility.getHeapTuple(2, 2));
    }

    /**
     * Unit test for HeapPage.deleteTuple()
     */
    @Test public void deleteTuple() throws Exception {
	HeapPage page = new HeapPage(pid, data);
	int free = page.getNumEmptySlots();
	
	// first, build a list of the tuples on the page.
	Iterator it = page.iterator();
	LinkedList<Tuple> tuples = new LinkedList<Tuple>();
	while (it.hasNext())
	    tuples.add((Tuple) it.next());
	Tuple first = tuples.getFirst();
	
	// now, delete them one-by-one from both the front and the end.
	int deleted = 0;
	while (tuples.size() > 0) {
	    page.deleteTuple(tuples.removeFirst());
	    page.deleteTuple(tuples.removeLast());
	    deleted += 2;
	    assertEquals(free + deleted, page.getNumEmptySlots());
	}

	// now, the page should be empty.
	try {
	    page.deleteTuple(first);
	    throw new Exception("page should be empty; expected DbException");
	} catch (DbException e) {
	    // explicitly ignored
	}
    }
    
    /**
     * JUnit suite target
     */
    public static junit.framework.Test suite() {
	return new JUnit4TestAdapter(HeapPageTest.class);
    }
}

