package gr.iti.mklab.visual.datastructures;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Arrays;
import java.util.Date;

import com.aliasi.util.BoundedPriorityQueue;
import com.sleepycat.bind.tuple.IntegerBinding;
import com.sleepycat.bind.tuple.TupleBinding;
import com.sleepycat.bind.tuple.TupleInput;
import com.sleepycat.bind.tuple.TupleOutput;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DiskOrderedCursorConfig;
import com.sleepycat.je.ForwardCursor;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;

import gnu.trove.list.array.TByteArrayList;
import gnu.trove.list.array.TIntArrayList;
import gnu.trove.list.array.TShortArrayList;
import gr.iti.mklab.visual.aggregation.AbstractFeatureAggregator;
import gr.iti.mklab.visual.datastructures.PQ.TransformationType;
import gr.iti.mklab.visual.utilities.RandomPermutation;
import gr.iti.mklab.visual.utilities.RandomRotation;
import gr.iti.mklab.visual.utilities.Result;

/**
 * This class implements indexing and non-exhaustive approximate nearest neighbor search using the combination
 * of Product Quantization with an inverted file structure (IVFADC) as described in:<br>
 * 
 * <em>Jégou, H., Douze, M., & Schmid, C. (2011). Product quantization for nearest neighbor search. IEEE Transactions on Pattern Analysis and Machine Intelligence.</em>
 * 
 * @author Eleftherios Spyromitros-Xioufis
 * 
 */
public class IVFPQ extends AbstractSearchStructure {

	/**
	 * BDB store for persistent storage of the ADC index.
	 */
	private Database iidToIvfpqDB;

	/**
	 * The number of sub-vectors.
	 */
	private int numSubVectors;

	/**
	 * The length of each subvector (= vectorLength/numSubVectors).
	 */
	private int subVectorLength;

	/**
	 * The number of centroids used to quantize each sub-vector. (Depending on this number we use a different
	 * type for storing the quantization code of each sub-vector. For k<=256=2^8 centroids we use a byte (8
	 * bits per subvector), for k>256 we use a short (16 bits per subvector).
	 * 
	 */
	private int numProductCentroids;

	/**
	 * The product-quantization codes for all vectors are stored in this list if the code can fit in the byte
	 * range.
	 */
	private TByteArrayList[] pqByteCodes;

	/**
	 * The product-quantization codes for all vector are stored in this list if the code cannot fit in the
	 * byte range.
	 */
	private TShortArrayList[] pqShortCodes;

	/**
	 * The inverted lists containing the internal ids of the vectors qunatized in each list.
	 */
	private TIntArrayList[] invertedLists;

	/**
	 * Number of centroids in the coarse quantizer.
	 */
	private int numCoarseCentroids;

	/**
	 * Number of lists to be visited during nn search.
	 */
	private int w;

	public void setW(int w) {
		this.w = w;
	}

	/**
	 * The coarse quantizer.<br>
	 * 
	 * A two dimensional array storing the coarse quantizer. The 1st dimension goes from
	 * 1...numCoarseCentroids and indexes the centroids of the coarse quantizer. The 2nd dimension goes from
	 * 1...vectorLength and indexes the dimensions of each centroid.
	 */
	private double[][] coarseQuantizer;

	/**
	 * The sub-quantizers of the product quantizer. They are needed for indexing and search using PQ.<br>
	 * 
	 * A three dimensional array storing the sub-quantizers of the product quantizer. The first dimension goes
	 * from 1..numSubquantizers and indexes the sub-quantizers. The second dimension goes from
	 * 1..numProductCentroids and indexes the centroids of each sub-quantizer of the product quantizer. The
	 * third dimension goes from 1...subVectorLength and indexes the components of each centroid.
	 */
	private double[][][] productQuantizer;

	/**
	 * The type of transformation to perform on the vectors prior to product quantization.
	 */
	private PQ.TransformationType transformation;

	/**
	 * This object is used for applying random permutation prior to product quantization.
	 */
	private RandomPermutation rp;

	/**
	 * This object is used for applying random rotation prior to product quantization.
	 */
	private RandomRotation rr;

	/**
	 * The seed used in random transformations. Should be the same as the one used at learning time.
	 */
	public final int seed = 1;

	/**
	 * Whether to use a disk ordered cursor or not. This setting changes how fast the index will be loaded in
	 * main memory.
	 */
	public final boolean useDiskOrderedCursor = false;

	/**
	 * Advanced constructor.
	 * 
	 * @param vectorLength
	 *            The dimensionality of the VLAD vectors being indexed
	 * @param maxNumVectors
	 *            The maximum allowable size (number of vectors) of the index
	 * @param readOnly
	 *            If true the persistent store will opened only for read access (allows multiple opens)
	 * @param BDBEnvHome
	 *            The BDB environment home directory
	 * @param numSubVectors
	 *            The number of subvectors
	 * @param numProductCentroids
	 *            The number of centroids used to quantize each sub-vector
	 * @param transformation
	 *            The type of transformation to perform on each vector
	 * @param numCoarseCentroids
	 *            The number of centroids of the coarse quantizer
	 * @param countSizeOnLoad
	 *            Whether the load counter will be initialized by the size of the persistent store
	 * @param loadCounter
	 *            The initial value of the load counter
	 * @param loadIndexInMemory
	 *            Whether to load the index in memory, we can avoid loading the index in memory when we only
	 *            want to perform indexing
	 * @param cacheSize
	 *            the size of the cache in Megabytes
	 * @throws Exception
	 */
	public IVFPQ(int vectorLength, int maxNumVectors, boolean readOnly, String BDBEnvHome, int numSubVectors,
			int numProductCentroids, TransformationType transformation, int numCoarseCentroids,
			boolean countSizeOnLoad, int loadCounter, boolean loadIndexInMemory, long cacheSize)
					throws Exception {
		super(vectorLength, maxNumVectors, readOnly, countSizeOnLoad, loadCounter, loadIndexInMemory,
				cacheSize);
		this.numSubVectors = numSubVectors;
		if (vectorLength % numSubVectors > 0) {
			throw new Exception("The given number of subvectors is not valid!");
		}
		this.subVectorLength = vectorLength / numSubVectors;
		this.numProductCentroids = numProductCentroids;
		this.transformation = transformation;
		this.numCoarseCentroids = numCoarseCentroids;
		w = (int) (numCoarseCentroids * 0.1); // by default set w to 10% of the lists

		if (transformation == TransformationType.RandomRotation) {
			this.rr = new RandomRotation(seed, vectorLength);
		} else if (transformation == TransformationType.RandomPermutation) {
			this.rp = new RandomPermutation(seed, vectorLength);
		}

		createOrOpenBDBEnvAndDbs(BDBEnvHome);

		// configuration of the persistent index
		DatabaseConfig dbConf = new DatabaseConfig();
		dbConf.setReadOnly(readOnly);
		dbConf.setTransactional(transactional);
		dbConf.setAllowCreate(true); // db will be created if it does not exist
		iidToIvfpqDB = dbEnv.openDatabase(null, "ivfadc", dbConf); // create/open the db using config

		if (loadIndexInMemory) {// load the existing persistent index in memory
			// create the memory objects with the appropriate initial size
			invertedLists = new TIntArrayList[numCoarseCentroids];

			if (numProductCentroids <= 256) {
				pqByteCodes = new TByteArrayList[numCoarseCentroids];
			} else {
				pqShortCodes = new TShortArrayList[numCoarseCentroids];
			}

			int initialListCapacity = (int) ((double) maxNumVectors / numCoarseCentroids);
			System.out.println("Calculated list size " + initialListCapacity);

			for (int i = 0; i < numCoarseCentroids; i++) {
				if (numProductCentroids <= 256) {
					// fixed initial size allows space efficiency measurements
					// pqByteCodes[i] = new TByteArrayList(initialListCapacity * numSubVectors);
					pqByteCodes[i] = new TByteArrayList();

				} else {
					// fixed initial size allows space efficiency measurements
					// pqShortCodes[i] = new TShortArrayList(initialListCapacity * numSubVectors);
					pqShortCodes[i] = new TShortArrayList();

				}
				// fixed initial size for each list, allows space efficiency measurements
				// invertedLists[i] = new TIntArrayList(initialListCapacity);
				invertedLists[i] = new TIntArrayList();
			}
			// load any existing persistent index in memory
			loadIndexInMemory();
		}
	}

	/**
	 * 
	 * @param vectorLength
	 *            The dimensionality of the VLAD vectors being indexed
	 * @param maxNumVectors
	 *            The maximum allowable size (number of vectors) of the index
	 * @param readOnly
	 *            If true the persistent store will opened only for read access (allows multiple opens)
	 * @param BDBEnvHome
	 *            The BDB environment home directory
	 * @param numSubVectors
	 *            The number of subvectors
	 * @param numProductCentroids
	 *            The number of centroids used to quantize each sub-vector
	 * @param transformation
	 *            The type of transformation to perform on each vector
	 * @param numCoarseCentroids
	 *            The number of centroids of the coarse quantizer
	 * @param cacheSize
	 *            the size of the cache in Megabytes
	 * @throws Exception
	 */
	public IVFPQ(int vectorLength, int maxNumVectors, boolean readOnly, String BDBEnvHome, int numSubVectors,
			int numProductCentroids, TransformationType transformation, int numCoarseCentroids,
			long cacheSize) throws Exception {
		this(vectorLength, maxNumVectors, readOnly, BDBEnvHome, numSubVectors, numProductCentroids,
				transformation, numCoarseCentroids, true, 0, true, cacheSize);
	}

	/**
	 * Load the product quantizer from the given file.
	 * 
	 * @param filename
	 *            Full path to the file containing the product quantizer
	 * @throws Exception
	 */
	public void loadProductQuantizer(String filename) throws Exception {
		productQuantizer = new double[numSubVectors][numProductCentroids][subVectorLength];
		BufferedReader in = new BufferedReader(new FileReader(new File(filename)));
		for (int i = 0; i < numSubVectors; i++) {
			for (int j = 0; j < numProductCentroids; j++) {
				String line = in.readLine();
				String[] centroidString = line.split(",");
				for (int k = 0; k < subVectorLength; k++) {
					productQuantizer[i][j][k] = Double.parseDouble(centroidString[k]);
				}
			}
		}
		in.close();
	}

	/**
	 * Load the coarse quantizer from the given file.
	 * 
	 * @param filname
	 *            Full path to the file containing the coarse quantizer
	 * @throws Exception
	 */
	public void loadCoarseQuantizer(String filename) throws IOException {
		coarseQuantizer = new double[numCoarseCentroids][vectorLength];
		coarseQuantizer = AbstractFeatureAggregator.readQuantizer(filename, numCoarseCentroids, vectorLength);
	}

	/**
	 * Append the IVFPQ index with the given vector.
	 * 
	 * @param vector
	 *            The vector to be indexed
	 * @throws Exception
	 */
	public void indexVectorInternal(double[] vector) throws Exception {
		if (vector.length != vectorLength) {
			throw new Exception("The dimensionality of the vector is wrong!");
		}

		// quantize to the closest centroid of the coarse quantizer and compute residual vector
		int nearestCoarseCentroidIndex = computeNearestCoarseIndex(vector);
		double[] residualVector = computeResidualVector(vector, nearestCoarseCentroidIndex);

		// apply a random transformation if needed
		if (transformation == TransformationType.RandomRotation) {
			residualVector = rr.rotate(residualVector);
		} else if (transformation == TransformationType.RandomPermutation) {
			residualVector = rp.permute(residualVector);
		}

		// transform the residual vector into a PQ code
		int[] pqCode = new int[numSubVectors];

		for (int i = 0; i < numSubVectors; i++) {
			// take the appropriate sub-vector
			int fromIdex = i * subVectorLength;
			int toIndex = fromIdex + subVectorLength;
			double[] subvector = Arrays.copyOfRange(residualVector, fromIdex, toIndex);
			// assign the sub-vector to the nearest centroid of the respective sub-quantizer
			pqCode[i] = computeNearestProductIndex(subvector, i);
		}

		if (loadIndexInMemory) { // append the ram-based index
			// add a new entry to the corresponding inverted list
			invertedLists[nearestCoarseCentroidIndex].add(loadCounter);
		}

		if (numProductCentroids <= 256) {
			byte[] pqByteCode = PQ.transformToByte(pqCode);
			if (loadIndexInMemory) { // append the ram-based index
				pqByteCodes[nearestCoarseCentroidIndex].add(pqByteCode);
			}
			appendPersistentIndex(nearestCoarseCentroidIndex, pqByteCode); // append the disk-based index
		} else {
			short[] pqShortCode = PQ.transformToShort(pqCode);
			if (loadIndexInMemory) { // append the ram-based index
				pqShortCodes[nearestCoarseCentroidIndex].add(pqShortCode); // append the ram-based index
			}
			appendPersistentIndex(nearestCoarseCentroidIndex, pqShortCode); // append the disk-based index
		}
	}

	public synchronized boolean indexPQCode(String id, int listId, byte[] code) throws Exception {
		if (numProductCentroids > 256) {
			throw new Exception(
					"Byte is not sufficient to enumerate the centroids of the product quantizer!");
		}
		// check if we can index more vectors
		if (loadCounter >= maxNumVectors) {
			System.out.println("Maximum index capacity reached, no more vectors can be indexed!");
			return false;
		}
		// check if name is already indexed
		if (isIndexed(id)) {
			System.out.println("Vector '" + id + "' already indexed!");
			return false;
		}
		// do the indexing
		// persist id to name and the reverse mapping
		createMapping(id);
		if (loadIndexInMemory) { // append the ram-based index
			invertedLists[listId].add(loadCounter);
			pqByteCodes[listId].add(code);
		}
		appendPersistentIndex(listId, code); // append the disk-based index

		loadCounter++; // increase the loadCounter
		if (loadCounter % 100 == 0) { // debug message
			System.out.println(new Date() + " # indexed vectors: " + loadCounter);
		}
		return true;
	}

	protected BoundedPriorityQueue<Result> computeNearestNeighborsInternal(int k, double[] query)
			throws Exception {
		return computeKnnIVFADC(k, query);
	}

	protected BoundedPriorityQueue<Result> computeNearestNeighborsInternal(int k, int internalId)
			throws Exception {
		return computeKnnIVFSDC(k, internalId);
	}

	/**
	 * Computes and returns the k nearest neighbors of the query vector using the IVFADC approach.
	 * 
	 * @param k
	 *            The number of nearest neighbors to be returned
	 * @param qVector
	 *            The query vector
	 * @return
	 * @throws Exception
	 */
	private BoundedPriorityQueue<Result> computeKnnIVFADC(int k, double[] qVector) throws Exception {
		BoundedPriorityQueue<Result> nn = new BoundedPriorityQueue<Result>(new Result(), k);

		// find the w nearest coarse centroids
		int[] nearestCoarseCentroidIndices = computeNearestCoarseIndices(qVector, w);

		for (int i = 0; i < w; i++) { // for each assignment
			// quantize to the i-th closest centroid of the coarse quantizer and compute residual vector
			int nearestCoarseIndex = nearestCoarseCentroidIndices[i];
			double[] residualVectorQuery = computeResidualVector(qVector, nearestCoarseIndex);

			// apply a random transformation if needed
			if (transformation == TransformationType.RandomRotation) {
				residualVectorQuery = rr.rotate(residualVectorQuery);
			} else if (transformation == TransformationType.RandomPermutation) {
				residualVectorQuery = rp.permute(residualVectorQuery);
			}

			// compute lookup table
			double[][] lookUpTable = computeLookupADC(residualVectorQuery);

			for (int j = 0; j < invertedLists[nearestCoarseIndex].size(); j++) {
				int iid = invertedLists[nearestCoarseIndex].getQuick(j);
				double l2distance = 0;
				int codeStart = j * numSubVectors;
				if (numProductCentroids <= 256) {
					byte[] pqCode = pqByteCodes[nearestCoarseIndex].toArray(codeStart, numSubVectors);
					for (int m = 0; m < pqCode.length; m++) {
						// plus 128 because byte range is -128..127
						l2distance += lookUpTable[m][pqCode[m] + 128];
					}
				} else {
					short[] pqCode = pqShortCodes[nearestCoarseIndex].toArray(codeStart, numSubVectors);
					for (int m = 0; m < pqCode.length; m++) {
						l2distance += lookUpTable[m][pqCode[m]];
					}
				}
				nn.offer(new Result(iid, l2distance));
			}
		}

		return nn;
	}

	/**
	 * Utility methods that computes the distance between a query vector and the pq code associated with the
	 * given id using the IVFADC approach. <br>
	 * TODO: The computation of the lookUpTable is not needed in this case.
	 * 
	 * @param qVector
	 *            The query vector
	 * @param existingVecId
	 *            The id of an already indexed vector
	 * @return
	 * @throws Exception
	 */
	public double computeDistanceIVFADC(double[] qVector, String existingVecId) throws Exception {
		double distance = 0;
		// find the coarse centroid where the specified id is quantized as well as its pq code
		int existingCoarseCentroidIndex = getInvertedListId(existingVecId);

		// quantize the given vector to the centroid of the coarse quantizer where the existing vector is
		// quantized and compute the residual vector
		double[] residualVectorQuery = computeResidualVector(qVector, existingCoarseCentroidIndex);

		// apply a random transformation if needed
		if (transformation == TransformationType.RandomRotation) {
			residualVectorQuery = rr.rotate(residualVectorQuery);
		} else if (transformation == TransformationType.RandomPermutation) {
			residualVectorQuery = rp.permute(residualVectorQuery);
		}

		// compute lookup table
		double[][] lookUpTable = computeLookupADC(residualVectorQuery);

		if (numProductCentroids <= 256) {
			byte[] pqCode = getPQCodeByte(existingVecId);
			for (int m = 0; m < pqCode.length; m++) {
				// plus 128 because byte range is -128..127
				distance += lookUpTable[m][pqCode[m] + 128];
			}
		} else {
			short[] pqCode = getPQCodeShort(existingVecId);
			for (int m = 0; m < pqCode.length; m++) {
				distance += lookUpTable[m][pqCode[m]];
			}
		}

		return distance;
	}

	/**
	 * TODO: implement this method
	 * 
	 * @param k
	 *            The number of nearest neighbors to be returned
	 * @param iid
	 *            The internal id of the query vector (code actually)
	 * @return A bounded priority queue of Result objects, which contains the k nearest neighbors along with
	 *         their iids and distances from the query vector, ordered by lowest distance.
	 */
	private BoundedPriorityQueue<Result> computeKnnIVFSDC(int k, int iid) {
		return null;
	}

	/**
	 * Takes a (residual) query vector as input and returns a lookup table containing the distance between
	 * each sub-vector from each centroid of the corresponding sub-quantizer. The calculation of this look-up
	 * table requires numSubVectors*numProductCentroids*subVectorLength multiplications. After this
	 * calculation, the distance between the query and any vector in the database can be computed in constant
	 * time.
	 * 
	 * @param qVector
	 *            The (residual) query vector
	 * @return A lookup table of size numSubVectors * numProductCentroids with the distance of each sub-vector
	 *         from the centroids of each sub-quantizer
	 */
	private double[][] computeLookupADC(double[] queryVector) {
		double[][] distances = new double[numSubVectors][numProductCentroids];

		for (int i = 0; i < numSubVectors; i++) {
			int subvectorStart = i * subVectorLength;
			for (int j = 0; j < numProductCentroids; j++) {
				for (int k = 0; k < subVectorLength; k++) {
					distances[i][j] += (queryVector[subvectorStart + k] - productQuantizer[i][j][k])
							* (queryVector[subvectorStart + k] - productQuantizer[i][j][k]);
				}
			}
		}
		return distances;
	}

	/**
	 * Returns the index of the coarse centroid which is closer to the given vector.
	 * 
	 * @param vector
	 *            The vector
	 * @return The index of the nearest coarse centroid
	 */
	private int computeNearestCoarseIndex(double[] vector) {
		int centroidIndex = -1;
		double minDistance = Double.MAX_VALUE;
		for (int i = 0; i < numCoarseCentroids; i++) {
			double distance = 0;
			for (int j = 0; j < vectorLength; j++) {
				distance += (coarseQuantizer[i][j] - vector[j]) * (coarseQuantizer[i][j] - vector[j]);
				if (distance >= minDistance) {
					break;
				}
			}
			if (distance < minDistance) {
				minDistance = distance;
				centroidIndex = i;
			}
		}
		return centroidIndex;
	}

	/**
	 * Returns the indices of the k coarse centroids which are closer to the given vector.
	 * 
	 * @param vector
	 *            The vector
	 * @param k
	 *            The number of nearest centroids to return
	 * @return The indices of the k nearest coarse centroids
	 */
	protected int[] computeNearestCoarseIndices(double[] vector, int k) {
		BoundedPriorityQueue<Result> bpq = new BoundedPriorityQueue<Result>(new Result(), k);

		double lowest = Double.MAX_VALUE;
		for (int i = 0; i < numCoarseCentroids; i++) {
			boolean skip = false;
			double l2distance = 0;
			for (int j = 0; j < vectorLength; j++) {
				l2distance += (coarseQuantizer[i][j] - vector[j]) * (coarseQuantizer[i][j] - vector[j]);
				if (l2distance > lowest) {
					skip = true;
					break;
				}
			}
			if (!skip) {
				bpq.offer(new Result(i, l2distance));
				if (i >= k) {
					lowest = bpq.last().getDistance();
				}
			}
		}
		int[] nn = new int[k];
		for (int i = 0; i < k; i++) {
			nn[i] = bpq.poll().getId();
		}
		return nn;
	}

	/**
	 * Finds and returns the index of the centroid of the subquantizer with the given index which is closer to
	 * the given subvector.
	 * 
	 * @param subvector
	 *            The subvector
	 * @param subQuantizerIndex
	 *            The index of the the subquantizer
	 * @return The index of the nearest centroid
	 */
	private int computeNearestProductIndex(double[] subvector, int subQuantizerIndex) {
		int centroidIndex = -1;
		double minDistance = Double.MAX_VALUE;
		for (int i = 0; i < numProductCentroids; i++) {
			double distance = 0;
			for (int j = 0; j < subVectorLength; j++) {
				distance += (productQuantizer[subQuantizerIndex][i][j] - subvector[j])
						* (productQuantizer[subQuantizerIndex][i][j] - subvector[j]);
				if (distance >= minDistance) {
					break;
				}
			}
			if (distance < minDistance) {
				minDistance = distance;
				centroidIndex = i;
			}
		}
		return centroidIndex;
	}

	/**
	 * Computes the residual vector.
	 * 
	 * @param vector
	 *            The original vector
	 * @param centroidIndex
	 *            The centroid of the coarse quantizer from which the original vector is subtracted
	 * @return The residual vector
	 */
	private double[] computeResidualVector(double[] vector, int centroidIndex) {
		double[] residualVector = new double[vectorLength];
		for (int i = 0; i < vectorLength; i++) {
			residualVector[i] = coarseQuantizer[centroidIndex][i] - vector[i];
		}
		return residualVector;
	}

	/**
	 * Utility method that calculates and prints the min, max and avg number of items per inverted list of the
	 * index.
	 */
	public void outputItemsPerList() {
		int max = 0;
		int min = Integer.MAX_VALUE;
		double sum = 0;
		for (int i = 0; i < numCoarseCentroids; i++) {
			// System.out.println("List " + (i + 1) + ": " + perListLoadCounter[i]);
			if (invertedLists[i].size() > max) {
				max = invertedLists[i].size();
			}
			if (invertedLists[i].size() < min) {
				min = invertedLists[i].size();
			}
			sum += invertedLists[i].size();
		}

		System.out.println("Maximum number of vectors: " + max);
		System.out.println("Minimum number of vectors: " + min);
		System.out.println("Average number of vectors: " + (sum / numCoarseCentroids));

	}

	/**
	 * Loads the persistent index in memory.
	 * 
	 * @throws Exception
	 */
	private void loadIndexInMemory() throws Exception {
		long start = System.currentTimeMillis();
		System.out.println("Loading persistent index in memory.");

		DatabaseEntry foundKey = new DatabaseEntry();
		DatabaseEntry foundData = new DatabaseEntry();

		ForwardCursor cursor = null;
		if (useDiskOrderedCursor) { // disk ordered cursor
			DiskOrderedCursorConfig docc = new DiskOrderedCursorConfig();
			cursor = iidToIvfpqDB.openCursor(docc);
		} else {
			cursor = iidToIvfpqDB.openCursor(null, null);
		}

		int counter = 0;
		while (cursor.getNext(foundKey, foundData, LockMode.DEFAULT) == OperationStatus.SUCCESS
				&& counter < maxNumVectors) {
			TupleInput input = TupleBinding.entryToInput(foundData);
			int listId = input.readInt();
			// The following code assumes that internal ids are consequtive (as they should be).
			// It is possible, however, tha an indexed with non-consequtive ids was constructed.
			// invertedLists[listId].add(counter); // update ram based index
			// The following code works for non-consequitve internal ids as well.
			int iid = IntegerBinding.entryToInt(foundKey);
			invertedLists[listId].add(iid); // update ram based index

			if (numProductCentroids <= 256) {
				byte[] code = new byte[numSubVectors];
				for (int i = 0; i < numSubVectors; i++) {
					code[i] = input.readByte();
				}
				pqByteCodes[listId].add(code); // update ram based index
			} else {
				short[] code = new short[numSubVectors];
				for (int i = 0; i < numSubVectors; i++) {
					code[i] = input.readShort();
				}
				pqShortCodes[listId].add(code); // update ram based index
			}
			counter++;
			if (counter % 1000 == 0) {
				System.out.println(counter + " vectors loaded in memory!");
			}
		}
		cursor.close();
		long end = System.currentTimeMillis();
		System.out.println(counter + " images loaded in " + (end - start) + " ms!");
	}

	/**
	 * This is a utility method that can be used to dump the contents of the iidToIvfpqDB to a txt file.<br>
	 * Currently, only the list id of each item is dumped.
	 * 
	 * @param dumpFilename
	 *            Full path to the file where the dump will be written.
	 * @throws Exception
	 */
	public void dumpIidToIvfpqDB(String dumpFilename) throws Exception {
		DatabaseEntry foundKey = new DatabaseEntry();
		DatabaseEntry foundData = new DatabaseEntry();

		ForwardCursor cursor = iidToIvfpqDB.openCursor(null, null);
		BufferedWriter out = new BufferedWriter(new FileWriter(new File(dumpFilename)));
		while (cursor.getNext(foundKey, foundData, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
			int iid = IntegerBinding.entryToInt(foundKey);
			TupleInput input = TupleBinding.entryToInput(foundData);
			int listId = input.readInt();
			out.write(iid + " " + listId + "\n");
		}
		cursor.close();
		out.close();
	}

	/**
	 * Appends the persistent index with the given (byte) code.
	 * 
	 * @param code
	 *            The code
	 */
	private void appendPersistentIndex(int listId, byte[] code) {
		// write id, listId and code
		TupleOutput output = new TupleOutput();
		output.writeInt(listId);
		for (int i = 0; i < numSubVectors; i++) {
			output.writeByte(code[i]);
		}
		DatabaseEntry data = new DatabaseEntry();
		TupleBinding.outputToEntry(output, data);
		DatabaseEntry key = new DatabaseEntry();
		IntegerBinding.intToEntry(loadCounter, key);
		iidToIvfpqDB.put(null, key, data);
	}

	/**
	 * Appends the persistent index with the given (short) code.
	 * 
	 * @param code
	 *            The code
	 */
	private void appendPersistentIndex(int listId, short[] code) {
		// write id, listId and code
		TupleOutput output = new TupleOutput();
		output.writeInt(listId);
		for (int i = 0; i < numSubVectors; i++) {
			output.writeShort(code[i]);
		}
		DatabaseEntry data = new DatabaseEntry();
		TupleBinding.outputToEntry(output, data);
		DatabaseEntry key = new DatabaseEntry();
		IntegerBinding.intToEntry(loadCounter, key);
		iidToIvfpqDB.put(null, key, data);
	}

	/**
	 * Returns the pq code of the image with the given id.
	 * 
	 * @param id
	 * @return
	 * @throws Exception
	 */
	public byte[] getPQCodeByte(String id) throws Exception {
		int iid = getInternalId(id);
		if (iid == -1) {
			throw new Exception("Id does not exist!");
		}
		if (numProductCentroids > 256) {
			throw new Exception("Call the short variant of the method!");
		}

		DatabaseEntry key = new DatabaseEntry();
		IntegerBinding.intToEntry(iid, key);
		DatabaseEntry data = new DatabaseEntry();
		if ((iidToIvfpqDB.get(null, key, data, null) == OperationStatus.SUCCESS)) {
			TupleInput input = TupleBinding.entryToInput(data);
			input.readInt(); // skip the list id
			byte[] code = new byte[numSubVectors];
			for (int i = 0; i < numSubVectors; i++) {
				code[i] = input.readByte();
			}
			return code;
		} else {
			throw new Exception("Id does not exist!");
		}
	}

	/**
	 * Returns the pq code of the image with the given id.
	 * 
	 * @param id
	 * @return
	 * @throws Exception
	 */
	public short[] getPQCodeShort(String id) throws Exception {
		int iid = getInternalId(id);
		if (iid == -1) {
			throw new Exception("Id does not exist!");
		}
		if (numProductCentroids <= 256) {
			throw new Exception("Call the short variant of the method!");
		}

		DatabaseEntry key = new DatabaseEntry();
		IntegerBinding.intToEntry(iid, key);
		DatabaseEntry data = new DatabaseEntry();
		if ((iidToIvfpqDB.get(null, key, data, null) == OperationStatus.SUCCESS)) {
			TupleInput input = TupleBinding.entryToInput(data);
			input.readInt(); // skip the list id
			short[] code = new short[numSubVectors];
			for (int i = 0; i < numSubVectors; i++) {
				code[i] = input.readShort();
			}
			return code;
		} else {
			throw new Exception("Id does not exist!");
		}
	}

	/**
	 * Returns the inverted list of the image with the given id.
	 * 
	 * @param id
	 * @return
	 * @throws Exception
	 */
	public int getInvertedListId(String id) throws Exception {
		int iid = getInternalId(id);
		if (iid == -1) {
			throw new Exception("Id does not exist!");
		}

		DatabaseEntry key = new DatabaseEntry();
		IntegerBinding.intToEntry(iid, key);
		DatabaseEntry data = new DatabaseEntry();
		if ((iidToIvfpqDB.get(null, key, data, null) == OperationStatus.SUCCESS)) {
			TupleInput input = TupleBinding.entryToInput(data);
			int listId = input.readInt();
			return listId;
		} else {
			throw new Exception("Id does not exist!");
		}
	}

	@Override
	public void outputIndexingTimesInternal() {
	}

	@Override
	public void closeInternal() {
		iidToIvfpqDB.close();
	}

}