OCP 2.10.19

offline-chess-puzzles - GUI to solve puzzles from the lichess puzzle database
use diesel::prelude::*;
use diesel::sqlite::SqliteConnection;

use crate::{
	config::Puzzle,
	models::NewFavorite,
	openings::{Openings, Variation},
	schema::favs,
	schema::favs::dsl::*,
	search_tab::{OpeningSide, TacticalThemes},
};

pub fn establish_connection() -> SqliteConnection {
	let fav_db = "favorites.db";
	let mut conn = SqliteConnection::establish(fav_db).unwrap_or_else(|_| panic!("Error connecting to {}", &fav_db));
	let init = concat!(
		"CREATE TABLE IF NOT EXISTS favs (",
		"puzzle_id TEXT NOT NULL PRIMARY KEY,",
		"fen TEXT NOT NULL,",
		"moves TEXT NOT NULL,",
		"rating INTEGER NOT NULL,",
		"rd INTEGER NOT NULL,",
		"popularity INTEGER NOT NULL,",
		"nb_plays INTEGER NOT NULL,",
		"themes TEXT NOT NULL,",
		"game_url TEXT NOT NULL NOT NULL,",
		"opening_tags TEXT NOT NULL",
		");",
		"PRAGMA foreign_keys=OFF;",
		"COMMIT;"
	);
	if let Err(err) = diesel::sql_query(init.to_string()).execute(&mut conn) {
		panic!("{:?}: can't initialize {}", err, &fav_db);
	}
	conn
}

pub fn get_favorites(
	min_rating: i32, max_rating: i32, min_popularity: i32, theme: TacticalThemes, opening: Openings, variation: Variation, op_side: Option<OpeningSide>,
	result_limit: usize,
) -> Option<Vec<Puzzle>> {
	let mut conn = establish_connection();
	let results;
	let theme_filter = String::from("%") + theme.get_tag_name() + "%";
	let limit = result_limit as i64;
	if opening == Openings::Any {
		results = favs
			.filter(rating.between(min_rating, max_rating))
			.filter(popularity.ge(min_popularity))
			.filter(themes.like(theme_filter))
			.limit(limit)
			.load::<Puzzle>(&mut conn);
	} else {
		let opening_tag: &str = if variation.name != Variation::ANY_STR { &variation.name } else { opening.get_field_name() };
		let opening_filter = opening_tags.like(String::from("%") + opening_tag + "%");
		let side = match op_side {
			None => OpeningSide::Any,
			Some(x) => x,
		};
		if side == OpeningSide::White {
			results = favs
				.filter(rating.between(min_rating, max_rating))
				.filter(popularity.ge(min_popularity))
				.filter(themes.like(theme_filter))
				.filter(opening_filter)
				.filter(game_url.like("%black%"))
				.limit(limit)
				.load::<Puzzle>(&mut conn);
		} else if side == OpeningSide::Black {
			results = favs
				.filter(rating.between(min_rating, max_rating))
				.filter(popularity.ge(min_popularity))
				.filter(themes.like(theme_filter))
				.filter(opening_filter)
				.filter(game_url.not_like("%black%"))
				.limit(limit)
				.load::<Puzzle>(&mut conn);
		} else {
			results = favs
				.filter(rating.between(min_rating, max_rating))
				.filter(popularity.ge(min_popularity))
				.filter(themes.like(theme_filter))
				.filter(opening_filter)
				.limit(limit)
				.load::<Puzzle>(&mut conn);
		}
	}
	results.ok()
}

pub fn is_favorite(id: &str) -> bool {
	let mut conn = establish_connection();
	let results = favs.filter(puzzle_id.eq(id)).first::<Puzzle>(&mut conn);

	results.is_ok()
}

pub fn toggle_favorite(puzzle: Puzzle) {
	let mut conn = establish_connection();
	let is_fav = favs.filter(puzzle_id.eq(&puzzle.puzzle_id)).first::<Puzzle>(&mut conn).is_ok();

	if is_fav {
		diesel::delete(favs::table).filter(puzzle_id.eq(&puzzle.puzzle_id)).execute(&mut conn).expect("Error removing favorite");
	} else {
		let new_fav = NewFavorite {
			puzzle_id: &puzzle.puzzle_id,
			fen: &puzzle.fen,
			moves: &puzzle.moves,
			rating: puzzle.rating,
			rd: puzzle.rating_deviation,
			popularity: puzzle.popularity,
			nb_plays: puzzle.nb_plays,
			themes: &puzzle.themes,
			game_url: &puzzle.game_url,
			opening_tags: &puzzle.opening,
		};

		diesel::insert_into(favs::table).values(&new_fav).execute(&mut conn).expect("Error saving new favorite");
	}
}