package com.johnstarich.moviematcher.models;

import com.johnstarich.moviematcher.app.HttpException;
import com.johnstarich.moviematcher.app.HttpStatus;
import com.johnstarich.moviematcher.store.MovieMatcherDatabase;
import de.caluga.morphium.annotations.Entity;
import de.caluga.morphium.annotations.Id;
import de.caluga.morphium.annotations.Transient;
import de.caluga.morphium.query.Query;
import org.bson.types.ObjectId;

import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Base for models stored in the database
 * Created by johnstarich on 4/19/16.
 */
@Entity
public abstract class AbstractModel<T extends AbstractModel> {
	protected static final int MODEL_DEFAULT_RESULTS = 20, MODEL_MAX_RESULTS = 100, MODEL_MIN_PAGE = 1;

	@Id
	public final ObjectId id;

	@Transient
	private final Class<T> clazz;

	public AbstractModel(Class<T> clazz, ObjectId id) {
		this.id = id;
		this.clazz = clazz;
	}

	public boolean exists() {
		return load().isPresent();
	}

	public T save() {
		MovieMatcherDatabase.morphium.store(this);
		return (T) this;
	}

	public Optional<T> load() {
		List<T> results = MovieMatcherDatabase.morphium.findByField(clazz, "id", id);
		if(results.isEmpty()) return Optional.empty();
		else return Optional.of(results.get(0));
	}

	public T delete() {
		MovieMatcherDatabase.morphium.delete(this);
		return (T) this;
	}

	public List<T> search(String query) throws HttpException {
		return search(clazz, query, MODEL_DEFAULT_RESULTS, MODEL_MIN_PAGE);
	}

	public List<T> search(String query, int results, int page) throws HttpException {
		return search(clazz, query, results, page);
	}

	public static final <T extends AbstractModel> List<T> search(Class<T> clazz, String query) throws HttpException {
		return search(clazz, query, MODEL_DEFAULT_RESULTS, MODEL_MIN_PAGE);
	}

	public static final <T extends AbstractModel> List<T> search(Class<T> clazz, String query, int results, int page) throws HttpException {
		if(results < 1 || results > MODEL_MAX_RESULTS) {
			throw new HttpException(HttpStatus.BAD_REQUEST, "Results must be a positive integer no greater than " + MODEL_MAX_RESULTS);
		}
		if(page < MODEL_MIN_PAGE) {
			throw new HttpException(HttpStatus.BAD_REQUEST, "Page must be a positive integer");
		}
		MovieMatcherDatabase.morphium.ensureIndicesFor(clazz);
		Query<T> searchQuery = MovieMatcherDatabase.morphium.createQueryFor(clazz)
			.text(query);
		return MovieMatcherDatabase.morphium.createAggregator(clazz, clazz)
			.match(searchQuery)
			.project(getProjectFields(clazz))
			.sort("-score")
			.skip(results * (page - 1))
			.limit(results)
			.aggregate();
	}

	private static Map<String, Object> getProjectFields(Class clazz) {
		Map<String, Object> map = new HashMap<>();
		map.put("score", Collections.singletonMap("$meta", "textScore"));
		getFieldNames(clazz).forEach(f -> map.put(f, true));
		return map;
	}

	private static List<String> getFieldNames(Class clazz) {
		return Arrays.stream(clazz.getFields())
			.map(Field::getName)
			.collect(Collectors.toList());
	}
}

@@ -823,22 +823,16 @@
 private 
-final 
 Class%3CT%3E
@@ -1247,16 +1247,97 @@
 %0A%09%09else 
+%7B%0A%09%09%09T result = results.get(0);%0A%09%09%09((AbstractModel%3CT%3E) result).clazz = clazz;%0A%09%09%09
 return O
@@ -1364,16 +1364,20 @@
 et(0));%0A
+%09%09%7D%0A
 %09%7D%0A%0A%09pub

Fix AbstractModel load not setting class