/*
  $Id$

  Copyright (C) 2008 by The Regents of the University of California
	
  Redistribution of this file is permitted under
  the terms of the BSD license
    
  Date: 04/04/2008
  Author: Alexander Behm <abehm (at) ics.uci.edu>
*/

#include "ftsearchermem.h"
#include "common/query.h"
#include "common/simmetric.h"
#include "listmerger/divideskipmerger.h"
#include <fstream>

using namespace std;
using namespace tr1;

// global vars for performing unittests
StringContainerVector strContainer;
vector<Query*> queries;
vector<string> queryStrings;
vector<unsigned> expectedResults;
GramGenFixedLen gramGen(3);
SimMetricEd simMetric(gramGen);

void init();
void deinit();
bool compareResults(vector<unsigned>& results, const string& identifier);

bool testFtIndexerSimple();

int main() {
  init();

  bool passed = false;

  cout << "NOTE: THESE TESTS MAY TAKE SOME MINUTES, PLEASE BE PATIENT" << endl << endl;

  cout << "TEST FtIndexerSimple:" << endl;
  passed = testFtIndexerSimple();  
  if(passed) cout << "--- PASSED ---" << endl;
  else cout << "--- FAILED ---" << endl;
  cout << endl;

  deinit();
  return 0;
}

void init() {
  cout << "INITIALIZING UNITTEST" << endl;

  vector<string> prefixes;
  prefixes.push_back("string");
  prefixes.push_back("example");  
  prefixes.push_back("test");
  prefixes.push_back("hello");
  prefixes.push_back("world");
  prefixes.push_back("foo");
  prefixes.push_back("bar");
  
  vector<string> suffixes;
  suffixes.push_back("1");
  suffixes.push_back("10");
  suffixes.push_back("100");
  suffixes.push_back("2");
  suffixes.push_back("20");
  suffixes.push_back("200");
  suffixes.push_back("3");
  suffixes.push_back("30");
  suffixes.push_back("300");
  
  for(unsigned j = 0; j < prefixes.size(); j++)
    for(unsigned i = 0; i < suffixes.size(); i++)
      strContainer.insertString(prefixes.at(j) + suffixes.at(i));
  
  // create queries
  queries.push_back(new Query("xample", simMetric, 2.0f));
  queries.push_back(new Query("ring1", simMetric, 2.0f));
  queries.push_back(new Query("wrld", simMetric, 2.0f));
  queries.push_back(new Query("fooa", simMetric, 2.0f));
  queries.push_back(new Query("br", simMetric, 2.0f));  

  for(unsigned i = 0; i < 10; i++) {
    queryStrings.push_back("xample");
    queryStrings.push_back("ring1");
    queryStrings.push_back("wrld");
    queryStrings.push_back("fooa");
    queryStrings.push_back("br");
  }

  // execute queries on simple index without filters to get expected results
  FtIndexerSimple<> indexer(&strContainer, &gramGen);
  indexer.buildIndex(false);      
  DivideSkipMerger<> merger;
  FtSearcherMem<> searcher(&merger, &indexer);

  for(vector<Query*>::iterator iter = queries.begin(); iter != queries.end(); iter++)
    searcher.search(**iter, expectedResults);
    
  // sort expected results
  sort(expectedResults.begin(), expectedResults.end());
  
  cout << "UNITTEST INITIALIZED" << endl << endl;
}

void deinit() {
  for(vector<Query*>::iterator iter = queries.begin(); iter != queries.end(); iter++)
    delete *iter;  
}

bool compareResults(vector<unsigned>& results, const string& identifier) {
  // compare results
  sort(results.begin(), results.end());
  if(results.size() != expectedResults.size()) {
    cout << "FAILED IN " << identifier << endl;
    return false;
  }

  for(unsigned i = 0; i < results.size(); i++)
    if(results.at(i) != expectedResults.at(i)) {
      cout << "FAILED IN " << identifier << endl;
      return false;
    }

  return true;
}

bool testFtIndexerSimple() {  
  GramGenFixedLen gramGen(3);
  SimMetricEd simMetric(gramGen);
  DivideSkipMerger<> merger;
  
  FtSearcherMem<> searcher(&merger);

  bool success = true;

  // try different filters with different fanouts and values for max string length
  for(unsigned maxStrLength = 10; maxStrLength <= 200; maxStrLength += 10) {
    for(unsigned fanout = 1; fanout <= 10; fanout++) {
      vector<unsigned> results;      

      // begin block for indexer with lengthfilter
      {
	FtIndexerSimple<> indexer(&strContainer, &gramGen, maxStrLength, fanout);
	indexer.addFilter(new LengthFilter(maxStrLength));
	indexer.buildIndex(false);      

	// execute queries and compute results
	results.clear();
	searcher.setFtIndexer(&indexer);	
	for(vector<Query*>::iterator iter = queries.begin(); iter != queries.end(); iter++)
	  searcher.search(**iter, results);
	
	success = success && compareResults(results, "FtIndexerSimple, LENGTH FILTER BUILT");

	// save index, load it into differrent indexer and repeat
	indexer.saveIndex("UnittestIndex.ix");
	FtIndexerSimple<> loadedIndexer(&strContainer);
	loadedIndexer.loadIndex("UnittestIndex.ix");

	if(loadedIndexer.filterTypes.at(0)->getType() != FT_LENGTH) {
	  cout << "FtIndexerSimple, LENGTH FILTER LOADED INCORRECTLY" << endl;
	  success = false;
	}

	results.clear();
	searcher.setFtIndexer(&loadedIndexer);	
	for(vector<Query*>::iterator iter = queries.begin(); iter != queries.end(); iter++)
	  searcher.search(**iter, results);
	
	success = success && compareResults(results, "FtIndexerSimple, LENGTH FILTER LOADED");
      }

      // begin block for indexer with checksum filter
      {
	FtIndexerSimple<> indexer(&strContainer, &gramGen, maxStrLength, fanout);
	indexer.addFilter(new ChecksumFilter(maxStrLength));
	indexer.buildIndex(false);      

	// execute queries and compute results
	searcher.setFtIndexer(&indexer);	
	results.clear();
	for(vector<Query*>::iterator iter = queries.begin(); iter != queries.end(); iter++)
	  searcher.search(**iter, results);
	
	success = success && compareResults(results, "FtIndexerSimple, CHECKSUM FILTER BUILT");

	// save index, load it into differrent indexer and repeat
	indexer.saveIndex("UnittestIndex.ix");
	FtIndexerSimple<> loadedIndexer(&strContainer);
	loadedIndexer.loadIndex("UnittestIndex.ix");

	if(loadedIndexer.filterTypes.at(0)->getType() != FT_CHECKSUM) {
	  cout << "FtIndexerSimple, CHECKSUM FILTER LOADED INCORRECTLY" << endl;
	  success = false;
	}

	results.clear();
	searcher.setFtIndexer(&loadedIndexer);	
	for(vector<Query*>::iterator iter = queries.begin(); iter != queries.end(); iter++)
	  searcher.search(**iter, results);
	
	success = success && compareResults(results, "FtIndexerSimple, CHECKSUM FILTER LOADED");
      }

      // begin block for indexer with both length and checksum filters
      {
	FtIndexerSimple<> indexer(&strContainer, &gramGen, maxStrLength, fanout);
	indexer.addFilter(new LengthFilter(maxStrLength));
	indexer.addFilter(new ChecksumFilter(maxStrLength));
	indexer.buildIndex(false);      

	// execute queries and compute results
	searcher.setFtIndexer(&indexer);      
	results.clear();
	for(vector<Query*>::iterator iter = queries.begin(); iter != queries.end(); iter++)
	  searcher.search(**iter, results);

	success = success && compareResults(results, "FtIndexerSimple, LENGTH+CHECKSUM FILTER BUILT");

	// save index, load it into differrent indexer and repeat
	indexer.saveIndex("UnittestIndex.ix");
	FtIndexerSimple<> loadedIndexer(&strContainer);
	loadedIndexer.loadIndex("UnittestIndex.ix");

	if(loadedIndexer.filterTypes.at(0)->getType() != FT_LENGTH
	   && loadedIndexer.filterTypes.at(1)->getType() != FT_CHECKSUM) {
	  cout << "FtIndexerSimple, LENGTH+CHECKSUM FILTER LOADED INCORRECTLY" << endl;
	  success = false;
	}

	results.clear();
	searcher.setFtIndexer(&loadedIndexer);	
	for(vector<Query*>::iterator iter = queries.begin(); iter != queries.end(); iter++)
	  searcher.search(**iter, results);
	
	success = success && compareResults(results, "FtIndexerSimple, LENGTH FILTER LOADED");
      }

    }
  }

  return success;
}
