/*
 * Copyright 2022 B2i Healthcare Pte Ltd, http://b2i.sg
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.b2international.index.query;

import com.b2international.collections.PrimitiveLists;
import com.b2international.collections.floats.FloatList;

/**
 * @since 8.5
 */
public final class Knn<T> {

	private Class<T> from;
	private String field;
	private Expression filter;
	private int k;
	private int numCandidates;
	private FloatList queryVector;
	
	public Knn() {
	}
	
	public Class<T> getFrom() {
		return from;
	}
	
	public String getField() {
		return field;
	}
	
	public Expression getFilter() {
		return filter;
	}
	
	public int getK() {
		return k;
	}
	
	public int getNumCandidates() {
		return numCandidates;
	}
	
	public FloatList getQueryVector() {
		return queryVector;
	}
	
	public static <T> KnnBuilder<T> select(Class<T> from) {
		return new KnnBuilder<>(from);
	}
	
	public Knn<T> withFilter(Expression filter) {
		return Knn.select(from)
				.field(field)
				.filter(filter)
				.k(k)
				.numCandidates(numCandidates)
				.queryVector(queryVector)
				.build();
	}
	
	public static final class KnnBuilder<T> implements Buildable<Knn<T>> {

		private final Class<T> from;
		private String field;
		private Expression filter;
		private int k;
		private int numCandidates;
		private FloatList queryVector;

		public KnnBuilder(Class<T> from) {
			this.from = from;
		}
		
		public KnnBuilder<T> field(String field) {
			this.field = field;
			return this;
		}
		
		public KnnBuilder<T> filter(Expression filter) {
			this.filter = filter;
			return this;
		}
		
		public KnnBuilder<T> k(int k) {
			this.k = k;
			return this;
		}
		
		public KnnBuilder<T> numCandidates(int numCandidates) {
			this.numCandidates = numCandidates;
			return this;
		}
		
		public KnnBuilder<T> queryVector(float...values) {
			return queryVector(PrimitiveLists.newFloatArrayList(values));
		}
		
		public KnnBuilder<T> queryVector(FloatList queryVector) {
			this.queryVector = queryVector;
			return this;
		}
		
		@Override
		public Knn<T> build() {
			Knn<T> knn = new Knn<>();
			knn.from = from;
			knn.field = field;
			knn.filter = filter;
			knn.k = k;
			knn.numCandidates = numCandidates;
			knn.queryVector = queryVector;
			return knn;
		}
		
		
		
	}

}

@@ -769,16 +769,83 @@
 oatList;
+%0Aimport com.b2international.commons.exceptions.BadRequestException;
 %0A%0A/**%0A *
@@ -2280,19 +2280,23 @@
 didates(
-i
+I
 nt
+eger
  numCand
@@ -2338,24 +2338,52 @@
 umCandidates
+ != null ? numCandidates : k
 ;%0A%09%09%09return 
@@ -2676,16 +2676,449 @@
 ild() %7B%0A
+%09%09%09if (numCandidates %3C k) %7B%0A%09%09%09%09throw new BadRequestException(%22numCandidates ('%25s') cannot be less than the selected limit of '%25s'.%22, numCandidates, k);%0A%09%09%09%7D%0A%09%09%09%0A%09%09%09if (k %3E 10000) %7B%0A%09%09%09%09throw new BadRequestException(%22knn search cannot return more than 10k results.%22);%0A%09%09%09%7D%0A%09%09%09%0A%09%09%09if (numCandidates %3E 10000) %7B%0A%09%09%09%09throw new BadRequestException(%22numCandidates cannot be greater than 10000. It was '%25s'%22, numCandidates);%0A%09%09%09%7D%0A%09%09%09%09%0A%09%09%09%09%0A
 %09%09%09Knn%3CT
@@ -3256,16 +3256,16 @@
 idates;%0A
-
 %09%09%09knn.q
@@ -3282,32 +3282,40 @@
  = queryVector;%0A
+%09%09%09%0A%09%09%09%0A
 %09%09%09return knn;%0A%09

add more API validation to Knn query