package mil.nga.giat.geowave.adapter.vector.index;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;

import org.opengis.feature.simple.SimpleFeature;

import mil.nga.giat.geowave.core.index.ByteArrayId;
import mil.nga.giat.geowave.core.index.IndexUtils;
import mil.nga.giat.geowave.core.index.sfc.data.MultiDimensionalNumericData;
import mil.nga.giat.geowave.core.store.CloseableIterator;
import mil.nga.giat.geowave.core.store.adapter.statistics.DataStatistics;
import mil.nga.giat.geowave.core.store.index.Index;
import mil.nga.giat.geowave.core.store.index.PrimaryIndex;
import mil.nga.giat.geowave.core.store.query.BasicQuery;

/**
 * This Query Strategy chooses the index that satisfies the most dimensions of
 * the underlying query first and then if multiple are found it will choose the
 * one that most closely preserves locality. It won't be optimized for a single
 * prefix query but it will choose the index with the most dimensions defined,
 * enabling more fine-grained contraints given a larger set of indexable ranges.
 *
 *
 */
public class ChooseHeuristicMatchIndexQueryStrategy implements
		IndexQueryStrategySPI
{
	public static final String NAME = "Heuristic Match";

	@Override
	public String toString() {
		return NAME;
	}

	@Override
	public CloseableIterator<Index<?, ?>> getIndices(
			final Map<ByteArrayId, DataStatistics<SimpleFeature>> stats,
			final BasicQuery query,
			final PrimaryIndex[] indices ) {
		return new CloseableIterator<Index<?, ?>>() {
			PrimaryIndex nextIdx = null;
			boolean done = false;
			int i = 0;

			@Override
			public boolean hasNext() {
				double bestIndexBitsUsed = -1;
				int bestIndexDimensionCount = -1;
				PrimaryIndex bestIdx = null;
				while (!done && (i < indices.length)) {
					nextIdx = indices[i++];
					if (nextIdx.getIndexStrategy().getOrderedDimensionDefinitions().length == 0) {
						continue;
					}
					final List<MultiDimensionalNumericData> queryRanges = query.getIndexConstraints(nextIdx
							.getIndexStrategy());
					if (IndexUtils.isFullTableScan(queryRanges)) {
						// keep this is as a default in case all indices
						// result in a full table scan
						if (bestIdx == null) {
							bestIdx = nextIdx;
						}
					}
					else {
						double currentBitsUsed = 0;
						int currentDimensionCount = nextIdx.getIndexStrategy().getOrderedDimensionDefinitions().length;

						if (currentDimensionCount >= bestIndexDimensionCount) {
							for (final MultiDimensionalNumericData qr : queryRanges) {
								final double[] dataRangePerDimension = new double[qr.getDimensionCount()];
								for (int d = 0; d < dataRangePerDimension.length; d++) {
									dataRangePerDimension[d] = qr.getMaxValuesPerDimension()[d]
											- qr.getMinValuesPerDimension()[d];
								}
								currentBitsUsed += IndexUtils.getDimensionalBitsUsed(
										nextIdx.getIndexStrategy(),
										dataRangePerDimension);
							}

							if (currentDimensionCount > bestIndexDimensionCount || currentBitsUsed > bestIndexBitsUsed) {
								bestIndexBitsUsed = currentBitsUsed;
								bestIndexDimensionCount = currentDimensionCount;
								bestIdx = nextIdx;
							}
						}
					}
				}
				nextIdx = bestIdx;
				done = true;
				return nextIdx != null;
			}

			@Override
			public Index<?, ?> next()
					throws NoSuchElementException {
				if (nextIdx == null) {
					throw new NoSuchElementException();
				}
				final Index<?, ?> returnVal = nextIdx;
				nextIdx = null;
				return returnVal;
			}

			@Override
			public void remove() {}

			@Override
			public void close()
					throws IOException {}
		};
	}
}
