package simpledb.unittest;

import java.util.*;
import java.io.*;

import simpledb.*;

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

public class ExHashFileTest {
  private ExHashFile testFile;
  private TupleDesc td;
  private Vector<Tuple> expected;

  private void insertTuple(Tuple t) throws Exception {
    TransactionId tid = new TransactionId();
    Database.getBufferPool().insertTuple(tid, testFile.id(), t);
    Database.getBufferPool().flushAllPages();
    Database.getBufferPool().transactionComplete(tid);
  }

  private Vector<Tuple> hashScan(int val) throws Exception {
    Vector<Tuple> resultTuples = new Vector();
    TransactionId tid = new TransactionId();
    DbFileIterator scan = testFile.indexIterator(tid, new IntField(val));
    scan.open();
    try {
      while (true) {
        Tuple nextTuple = scan.getNext();
        resultTuples.addElement(nextTuple);
      }
    } catch (NoSuchElementException e) {
      // Done with iteration
    }
    Database.getBufferPool().transactionComplete(tid);
    return resultTuples;
  }

  private Vector<Tuple> seqScan() throws Exception {
    Vector<Tuple> resultTuples = new Vector();
    TransactionId tid = new TransactionId();
    DbFileIterator scan = testFile.iterator(tid);
    scan.open();
    try {
      while (true) {
        Tuple nextTuple = scan.getNext();
        resultTuples.addElement(nextTuple);
      }
    } catch (NoSuchElementException e) {
      // Done with iteration
    }
    Database.getBufferPool().transactionComplete(tid);
    return resultTuples;
  }

  private static void match(Vector<Tuple> v1, Vector<Tuple> v2) {
    Iterator it1, it2;
    it1 = v1.iterator();
    for (; it1.hasNext();) {
      Tuple nextV1 = (Tuple)it1.next();
      // Check that nextV1 is equal to at least one element in v2
      it2 = v2.iterator();
      boolean contained = false;
      for (; it2.hasNext();) {
        Tuple nextV2 = (Tuple)it2.next();
        if (Utility.compareTuples(nextV1, nextV2)) {
          contained = true;
          break;
        }
      }
      assertTrue(contained);
    }

    it2 = v2.iterator();
    for (; it2.hasNext();) {
      Tuple nextV2 = (Tuple)it2.next();
      // Check that nextV2 is equal to at least one element in v1
      it1 = v1.iterator();
      boolean contained = false;
      for (; it1.hasNext();) {
        Tuple nextV1 = (Tuple)it1.next();
        if (Utility.compareTuples(nextV1, nextV2)) {
          contained = true;
          break;
        }
      }
      assertTrue(contained);
    }
  }

  private void clearFiles() {
    File dir = new File(".");
    String[] info = dir.list();
    for (int i = 0; i < info.length; i++) {
      File n = new File(info[i]);
      if (!n.isFile())
        continue;
      if (info[i].indexOf("testhash__") == -1)
        continue;
      n.delete();
    }
  }

  private void testInsert(int N) throws Exception {
    for (int i = 0; i < N; i++) {
      Tuple t = new Tuple(td);
      t.setField(0, new IntField(100+i));
      for (int j = 1; j < 50; j++) {
        t.setField(j, new IntField(j));
      }
      insertTuple(t);
      expected.addElement(t);
    }
    Vector<Tuple> actual = seqScan();
    match(expected, actual);
  }

  private void testSearch(int N, int val) throws Exception {
    for (int i = 0; i < N; i++) {
      Tuple t = new Tuple(td);
      t.setField(0, new IntField(i));
      for (int j = 1; j < 50; j++) {
        t.setField(j, new IntField(i*j));
      }
      insertTuple(t);
      if (i == val) {
        expected.addElement(t);
      }
    }
    Vector<Tuple> actual = hashScan(val);
    match(expected, actual);
  }

  @Before public void setUp() throws Exception {
    Type[] ar = new Type[50];
    for (int i = 0; i < 50; i++) {
      ar[i] = Type.INT_TYPE;
    }
    td = new TupleDesc(ar);
    testFile = new ExHashFile(new File("testhash__"), 0);
    Database.getCatalog().addTable(testFile, td);
    testFile.init();
    expected = new Vector();
  }

  @After public void clearUp() throws Exception {
    Database.getBufferPool().flushAllPages();
    clearFiles();
  }

  // Bootstrap test.
  @Test public void bootstrap() throws Exception {
    testInsert(1);
    Database.getBufferPool().flushAllPages();
    clearFiles();
  }

  // Multiple tuples test.
  @Test public void insertMultipleTuples() throws Exception {
    testInsert(3);
    Database.getBufferPool().flushAllPages();
    clearFiles();
  }

  // Multiple pages test.
  @Test public void insertMultiplePages() throws Exception {
    testInsert(25);
    Database.getBufferPool().flushAllPages();
    clearFiles();
  }
  
  // Large test.
  @Test public void insertALot() throws Exception {
    testInsert(80);
    Database.getBufferPool().flushAllPages();
    clearFiles();
  }

  // Test search that exists in file.
  @Test public void searchSuccess() throws Exception {
    testSearch(25,23);
    Database.getBufferPool().flushAllPages();
    clearFiles();
  }

  // Test search that doesn't exist in file.
  @Test public void searchFail() throws Exception {
    testSearch(17,29);
    Database.getBufferPool().flushAllPages();
    clearFiles();
  }

  /**
   * JUnit suite target
   */
  public static junit.framework.Test suite() {
    return new JUnit4TestAdapter(ExHashFileTest.class);
  }
}