extern crate time;
use core::result;
use std::{ fmt, fs::read, io::{self, Read}, path };
use rusqlite::{ Connection, Error as SqliteError, OpenFlags, ffi::SQLITE_OPEN_READONLY };
use time::UtcDateTime;
use super::data_structures::{
CharacterCard,
StatGradeRating,
RunningStyle,
SupportCard,
SupportCardRarity,
SupportCardType,
Factor,
FactorGrade,
FactorType,
Skill,
SkillCategory,
SkillRarity,
SkillCondition,
SkillAbilityTarget,
AbilityType,
TargetType,
TargetValue,
Race,
RaceGrade,
RaceDistance,
RaceGround
};
pub type Result<T> = result::Result<T, DBError>;
pub type QueryResult<T> = Option<Vec<T>>;
const CHARA_CARD_DATA_SQL: &str = "
SELECT
crd.id,
crd.card_id,
cd.chara_id,
n.text,
cd.default_rarity,
crd.rarity,
crd.skill_set,
cd.talent_speed,
crd.speed,
crd.max_speed,
cd.talent_stamina,
crd.stamina,
crd.max_stamina,
cd.talent_pow,
crd.pow,
crd.max_pow,
cd.talent_guts,
crd.guts,
crd.max_guts,
cd.talent_wiz,
crd.wiz,
crd.max_wiz,
cd.running_style,
crd.proper_ground_turf,
crd.proper_ground_dirt,
crd.proper_distance_short,
crd.proper_distance_mile,
crd.proper_distance_middle,
crd.proper_distance_long
FROM card_rarity_data crd
JOIN card_data cd ON cd.id = crd.card_id
JOIN text_data n ON n.\"index\" = cd.id AND n.category = 4
ORDER BY cd.id ASC, crd.rarity
";
const SUPPORT_CARD_DATA_SQL: &str = "
SELECT c.id, c.chara_id, c.rarity, c.command_id, c.start_date, n.text
FROM support_card_data c
JOIN text_data n ON n.\"index\" = c.id AND n.category = 75
ORDER BY c.rarity DESC, c.start_date, c.id
";
const FACTOR_DATA_SQL: &str = "
SELECT f.factor_id, f.rarity, f.grade, f.factor_type, n.text, d.text
FROM succession_factor f
JOIN text_data n ON n.\"index\" = f.factor_id AND n.category = 147
JOIN text_data d ON d.\"index\" = f.factor_id AND d.category = 172
";
const SKILL_DATA_SQL: &str = "
SELECT
s.id,
s.rarity,
s.skill_category,
n.text,
d.text,
s.grade_value,
s.tag_id,
s.precondition_1,
s.condition_1,
s.ability_type_1_1,
s.target_type_1_1,
s.target_value_1_1,
s.ability_type_1_2,
s.target_type_1_2,
s.target_value_1_2,
s.ability_type_1_3,
s.target_type_1_3,
s.target_value_1_3,
s.precondition_2,
s.condition_2,
s.ability_type_2_1,
s.target_type_2_1,
s.target_value_2_1,
s.icon_id,
s.is_general_skill
FROM skill_data s
JOIN text_data n ON n.\"index\" = s.id AND n.category = 47
JOIN text_data d ON d.\"index\" = s.id AND d.category = 48
ORDER BY s.ability_type_1_1 ASC, s.skill_category
";
const RACE_DATA_SQL: &str = "
SELECT smp.id, ri.id, r.id, n.text, rt.text, r.grade, rcs.distance, rcs.ground, smp.month, smp.half
FROM single_mode_program smp
JOIN race_instance ri ON ri.id = smp.race_instance_id
JOIN race r ON r.id = ri.race_id
JOIN race_course_set rcs ON rcs.id = r.course_set
JOIN text_data n ON n.\"index\" = r.id AND n.category = 33
JOIN text_data rt ON rt.\"index\" = rcs.race_track_id AND rt.category = 34
ORDER BY r.id ASC
";
#[derive(Debug)]
pub enum DBError {
SQLError(SqliteError),
DataTypeError,
FilePathValidationError(io::Result<bool>)
}
impl std::error::Error for DBError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match *self {
DBError::DataTypeError => None,
DBError::FilePathValidationError(_) => None,
DBError::SQLError(ref e) => Some(e)
}
}
}
impl From<SqliteError> for DBError {
fn from(value: SqliteError) -> Self {
Self::SQLError(value)
}
}
impl fmt::Display for DBError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DBError::SQLError(sql_err) =>
write!(f,"An error occured querying the database: {}",sql_err.to_string()),
DBError::DataTypeError =>
write!(f,"An error occured trying to convert a datatype from the database."),
DBError::FilePathValidationError(res) => {
match *res {
Ok(_) => write!(f,"Path provided for directory is invalid"),
Err(_) => write!(f,"Path provided for directory could not be validated")
}
},
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Query {
CharacterCard,
SupportCard,
Factor,
Skill,
Race
}
#[derive(Debug)]
pub struct QueryResponse {
pub character_cards: QueryResult<CharacterCard>,
pub support_cards: QueryResult<SupportCard>,
pub factors: QueryResult<Factor>,
pub skills: QueryResult<Skill>,
pub races: QueryResult<Race>
}
#[derive(PartialEq)]
pub struct QueryOptions {
query_buff: Vec<Query>
}
impl QueryOptions {
pub fn new() -> QueryOptions {
let query_buff: Vec<Query> = Vec::with_capacity(5);
QueryOptions { query_buff }
}
pub fn add(&mut self, query: Query) {
self.query_buff.push(query);
}
pub fn remove(&mut self, query: Query) {
let _ = self.query_buff.extract_if(.., |q| *q == query);
}
pub fn has_query(&mut self, query: Query) -> bool {
self.query_buff.iter().any(|q| *q == query)
}
fn iter(&self) -> impl std::iter::Iterator<Item = &Query> {
self.query_buff.iter()
}
pub fn clear_options(&mut self) {
self.query_buff.clear();
}
}
#[derive(Debug, Clone)]
pub struct DatabaseController {
db_path: String
}
impl DatabaseController {
pub fn new(dir_path: &str) -> Result<DatabaseController> {
let root_path = path::Path::new(dir_path);
let path = root_path.try_exists();
if let Ok(is_valid) = path {
if is_valid {
match root_path.join("master/master.mdb").to_str().and_then(|v| Some(v.to_owned())) {
Some(db_path) => return Ok(DatabaseController { db_path }),
None => return Err(DBError::FilePathValidationError(path))
}
}
return Err(DBError::FilePathValidationError(path))
}
Err(DBError::FilePathValidationError(path))
}
pub fn get_queries(self,options: QueryOptions) -> Result<QueryResponse> {
let mut q_res = QueryResponse {
character_cards: None,
support_cards: None,
factors: None,
skills: None,
races: None
};
for q in options.iter() {
let db: DatabaseController = self.clone();
match q {
Query::CharacterCard => match db.character_card_query() {
Ok(character_cards) => q_res.character_cards = Some(character_cards.to_owned()),
Err(e) => return Err(e)
},
Query::SupportCard => match db.support_card_query() {
Ok(support_cards) => q_res.support_cards = Some(support_cards.to_owned()),
Err(e) => return Err(e)
},
Query::Factor => match db.factor_query() {
Ok(factors) => q_res.factors = Some(factors.to_owned()),
Err(e) => return Err(e)
},
Query::Skill => match db.skill_query() {
Ok(skills) => q_res.skills = Some(skills.to_owned()),
Err(e) => return Err(e)
},
Query::Race => match db.race_query() {
Ok(races) => q_res.races = Some(races.to_owned()),
Err(e) => return Err(e)
}
}
}
Ok(q_res)
}
fn character_card_query(self) -> Result<Vec<CharacterCard>> {
let filename = self.db_path;
let open_flags = OpenFlags::from_bits(SQLITE_OPEN_READONLY).unwrap();
let conn = Connection::open_with_flags(filename, open_flags)?;
let mut request = conn.prepare(CHARA_CARD_DATA_SQL)?;
let to_chara_card = |row: & rusqlite::Row| Ok(
CharacterCard {
id: row.get(0)?,
card_id: row.get(1)?,
chara_id: row.get(2)?,
name: row.get(3)?,
default_rarity: row.get(4)?,
rarity: row.get(5)?,
skill_set: row.get(6)?,
speed_talent: row.get(7)?,
speed: row.get(8)?,
max_speed: row.get(9)?,
stamina_talent: row.get(10)?,
stamina: row.get(11)?,
max_stamina: row.get(12)?,
power_talent: row.get(13)?,
power: row.get(14)?,
max_power: row.get(15)?,
guts_talent: row.get(16)?,
guts: row.get(17)?,
max_guts: row.get(18)?,
wis_talent: row.get(19)?,
wis: row.get(20)?,
max_wis: row.get(21)?,
default_running_style: RunningStyle::try_from(row.get::<usize,i32>(22)?).unwrap(),
turf_grade: StatGradeRating::try_from(row.get::<usize,i32>(23)?).unwrap(),
dirt_grade: StatGradeRating::try_from(row.get::<usize,i32>(24)?).unwrap(),
sprint_grade: StatGradeRating::try_from(row.get::<usize,i32>(25)?).unwrap(),
mile_grade: StatGradeRating::try_from(row.get::<usize,i32>(26)?).unwrap(),
medium_grade: StatGradeRating::try_from(row.get::<usize,i32>(27)?).unwrap(),
long_grade: StatGradeRating::try_from(row.get::<usize,i32>(28)?).unwrap(),
}
);
let chara_cards_qry = request.query_map([], to_chara_card)?;
chara_cards_qry.map(|val| -> Result<CharacterCard> {
match val {
Ok(card) => Ok(card),
Err(e) => Err(DBError::SQLError(e))
}
}).collect()
}
fn support_card_query(self) -> Result<Vec<SupportCard>> {
let filename = self.db_path;
let open_flags = OpenFlags::from_bits(SQLITE_OPEN_READONLY).unwrap();
let conn = Connection::open_with_flags(filename, open_flags)?;
let mut request = conn.prepare(SUPPORT_CARD_DATA_SQL)?;
let to_support_card = |row: & rusqlite::Row| Ok(
SupportCard {
id: row.get(0)?,
chara_id: row.get(1)?,
name: row.get(5)?,
rarity: SupportCardRarity::try_from(row.get::<usize,i32>(2)?).unwrap(),
card_type: SupportCardType::try_from(row.get::<usize,i32>(3)?).unwrap(),
timestamp: UtcDateTime::from_unix_timestamp(row.get(4)?).unwrap()
}
);
let support_cards_qry = request.query_map([], to_support_card)?;
support_cards_qry.map(|val| -> Result<SupportCard> {
match val {
Ok(card) => Ok(card),
Err(e) => Err(DBError::SQLError(e))
}
}).collect()
}
fn factor_query(self) -> Result<Vec<Factor>> {
let filename = self.db_path;
let open_flags = OpenFlags::from_bits(SQLITE_OPEN_READONLY).unwrap();
let conn = Connection::open_with_flags(filename, open_flags)?;
let mut request = conn.prepare(FACTOR_DATA_SQL)?;
let to_factor = |row: & rusqlite::Row| Ok(
Factor {
id: row.get(0)?,
name: row.get(4)?,
desc: row.get(5)?,
rarity: row.get(1)?,
grade: FactorGrade::try_from(row.get::<usize,i32>(2)?).unwrap(),
factor_type: FactorType::try_from(row.get::<usize,i32>(3)?).unwrap()
}
);
let factor_qry = request.query_map([], to_factor)?;
factor_qry.map(|val| -> Result<Factor> {
match val {
Ok(card) => Ok(card),
Err(e) => Err(DBError::SQLError(e))
}
}).collect()
}
fn skill_query(self) -> Result<Vec<Skill>> {
let filename = self.db_path;
let open_flags = OpenFlags::from_bits(SQLITE_OPEN_READONLY).unwrap();
let conn = Connection::open_with_flags(filename, open_flags)?;
let mut request = conn.prepare(SKILL_DATA_SQL)?;
let to_skill = |row:& rusqlite::Row| Ok({
let id: i32 = row.get(0)?;
let rarity: i32 = row.get(1)?;
let skill_category: i32 = row.get(2)?;
let name: String = row.get(3)?;
let desc: String = row.get(4)?;
let grade_val: i32 = row.get(5)?;
let tag_id: String = row.get(6)?;
let precond_1: String = row.get(7)?;
let cond_1: String = row.get(8)?;
let ability_type_1_1: i32 = row.get(9)?;
let target_type_1_1: i32 = row.get(10)?;
let target_value_1_1: i32 = row.get(11)?;
let ability_type_1_2: i32 = row.get(12)?;
let target_type_1_2: i32 = row.get(13)?;
let target_value_1_2: i32 = row.get(14)?;
let ability_type_1_3: i32 = row.get(15)?;
let target_type_1_3: i32 = row.get(16)?;
let target_value_1_3: i32 = row.get(17)?;
let precond_2: String = row.get(18)?;
let cond_2: String = row.get(19)?;
let ability_type_2_1: i32 = row.get(20)?;
let target_type_2_1: i32 = row.get(21)?;
let target_value_2_1: i32 = row.get(22)?;
let icon_id: i32 = row.get(23)?;
let is_general_skill: i32 = row.get(24)?;
let skill_ability_target_1_1 = SkillAbilityTarget {
ability_type: AbilityType::try_from(ability_type_1_1).ok(),
target_type: TargetType::try_from(target_type_1_1).ok(),
target_value: TargetValue::try_from(target_value_1_1).ok(),
};
let skill_ability_target_1_2 = SkillAbilityTarget {
ability_type: AbilityType::try_from(ability_type_1_2).ok(),
target_type: TargetType::try_from(target_type_1_2).ok(),
target_value: TargetValue::try_from(target_value_1_2).ok()
};
let skill_ability_target_1_3 = SkillAbilityTarget {
ability_type: AbilityType::try_from(ability_type_1_3).ok(),
target_type: TargetType::try_from(target_type_1_3).ok(),
target_value: TargetValue::try_from(target_value_1_3).ok()
};
let skill_cond_1 = SkillCondition {
precondition: precond_1,
condition: cond_1,
ability_targets: vec![skill_ability_target_1_1,skill_ability_target_1_2,skill_ability_target_1_3]
};
let mut skill_cond_2: Option<SkillCondition> = None;
if !cond_2.is_empty() {
let skill_ability_target_2_1 = SkillAbilityTarget {
ability_type: AbilityType::try_from(ability_type_2_1).ok(),
target_type: TargetType::try_from(target_type_2_1).ok(),
target_value: TargetValue::try_from(target_value_2_1).ok()
};
skill_cond_2 = Some(SkillCondition {
precondition: precond_2,
condition: cond_2,
ability_targets: vec![skill_ability_target_2_1]
});
}
Skill {
id,
category: SkillCategory::into_cat(skill_category, rarity).unwrap(),
name,
desc,
grade_val,
tag_id,
skill_conditions: (skill_cond_1,skill_cond_2),
icon_id,
is_general_skill: bool::try_from(is_general_skill).unwrap()
}
});
let skill_qry = request.query_map([], to_skill)?;
skill_qry.map(|val| -> Result<Skill> {
match val {
Ok(card) => Ok(card),
Err(e) => Err(DBError::SQLError(e))
}
}).collect()
}
fn race_query(self) -> Result<Vec<Race>> {
let filename = self.db_path;
let open_flags = OpenFlags::from_bits(SQLITE_OPEN_READONLY).unwrap();
let conn = Connection::open_with_flags(filename, open_flags).unwrap();
let mut request = conn.prepare(RACE_DATA_SQL).unwrap();
let to_response = |row: & rusqlite::Row| Ok(
Race {
program_id: row.get_unwrap(0),
race_inst_id: row.get_unwrap(1),
race_id: row.get_unwrap(2),
race_name: row.get_unwrap(3),
track_name: row.get_unwrap(4),
race_grade: RaceGrade::try_from(row.get_unwrap::<usize,i32>(5)).unwrap(),
race_dist: RaceDistance::try_from(row.get_unwrap::<usize,i32>(6)).unwrap(),
track_ground: RaceGround::try_from(row.get_unwrap::<usize,i32>(7)).unwrap(),
month: row.get_unwrap(8),
half: row.get_unwrap(9)
}
);
let race_qry = request.query_map([], to_response).unwrap();
race_qry.map(|val| -> Result<Race> {
match val {
Ok(card) => Ok(card),
Err(e) => Err(DBError::SQLError(e))
}
}).collect()
}
}