use super::Command;
use crate::cache::models::Problem;
use crate::err::Error;
use async_trait::async_trait;
use clap::{Arg, ArgAction, ArgMatches, Command as ClapCommand};
pub struct PickCommand;
static QUERY_HELP: &str = r#"Filter questions by conditions:
Uppercase means negative
e = easy E = m+h
m = medium M = e+h
h = hard H = e+m
d = done D = not done
l = locked L = not locked
s = starred S = not starred"#;
#[async_trait]
impl Command for PickCommand {
fn usage() -> ClapCommand {
ClapCommand::new("pick")
.about("Pick a problem")
.visible_alias("p")
.arg(
Arg::new("name")
.short('n')
.long("name")
.value_parser(clap::value_parser!(String))
.help("Problem name")
.num_args(1),
)
.arg(
Arg::new("id")
.value_parser(clap::value_parser!(i32))
.help("Problem id")
.num_args(1),
)
.arg(
Arg::new("plan")
.short('p')
.long("plan")
.num_args(1)
.help("Invoking python scripts to filter questions"),
)
.arg(
Arg::new("query")
.short('q')
.long("query")
.num_args(1)
.help(QUERY_HELP),
)
.arg(
Arg::new("tag")
.short('t')
.long("tag")
.num_args(1)
.help("Filter questions by tag"),
)
.arg(
Arg::new("daily")
.short('d')
.long("daily")
.help("Pick today's daily challenge")
.action(ArgAction::SetTrue),
)
}
async fn handler(m: &ArgMatches) -> Result<(), Error> {
use crate::cache::Cache;
use rand::Rng;
let cache = Cache::new()?;
let mut problems = cache.get_problems()?;
if problems.is_empty() {
cache.download_problems().await?;
Self::handler(m).await?;
return Ok(());
}
#[cfg(feature = "pym")]
{
if m.contains_id("plan") {
let ids = crate::pym::exec(m.get_one::<String>("plan").unwrap_or(&"".to_string()))?;
crate::helper::squash(&mut problems, ids)?;
}
}
if m.contains_id("tag") {
let ids = cache
.clone()
.get_tagged_questions(m.get_one::<String>("tag").unwrap_or(&"".to_string()))
.await?;
crate::helper::squash(&mut problems, ids)?;
}
if m.contains_id("query") {
let query = m.get_one::<String>("query").ok_or(Error::NoneError)?;
crate::helper::filter(&mut problems, query.to_string());
}
let daily_id = if m.contains_id("daily") {
Some(cache.get_daily_problem_id().await?)
} else {
None
};
let fid = match m.contains_id("name") {
true => {
match m.get_one::<String>("name").map(|name| name) {
Some(quesname) => match closest_named_problem(&problems, quesname) {
Some(p) => p,
None => 1,
},
None => {
let problem = &problems[rand::thread_rng().gen_range(0..problems.len())];
problem.fid
}
}
}
false => {
m.get_one::<i32>("id")
.copied()
.or(daily_id)
.unwrap_or_else(|| {
let problem = &problems[rand::thread_rng().gen_range(0..problems.len())];
problem.fid
})
}
};
let r = cache.get_question(fid).await;
match r {
Ok(q) => println!("{}", q.desc()),
Err(e) => {
eprintln!("{:?}", e);
if let Error::NetworkError(_) = e {
Self::handler(m).await?;
}
}
}
Ok(())
}
}
fn closest_named_problem(problems: &Vec<Problem>, lookup_name: &str) -> Option<i32> {
let max_name_size: usize = problems.iter().map(|p| p.name.len()).max()?;
let mut table: Vec<usize> = vec![0; (max_name_size + 1) * (lookup_name.len() + 1)];
assert!(problems.len() > 0);
let mut max_score = 0;
let mut current_problem = &problems[0];
for problem in problems {
if problem.name == lookup_name {
return Some(problem.fid);
}
let this_lcs = longest_common_subsequence(&mut table, &problem.name, lookup_name);
let this_score = this_lcs * (max_name_size - problem.name.len());
if this_score > max_score {
max_score = this_score;
current_problem = &problem;
}
}
Some(current_problem.fid)
}
fn longest_common_subsequence(table: &mut Vec<usize>, text1: &str, text2: &str) -> usize {
assert!(table.len() >= (text1.len() + 1) * (text2.len() + 1));
let height: usize = text1.len() + 1;
let width: usize = text2.len() + 1;
for i in 0..height {
table[i * width + (width - 1)] = 0;
}
for j in 0..width {
table[((height - 1) * width) + j] = 0;
}
let mut i: usize = height - 1;
let mut j: usize;
for c0 in text1.chars().rev() {
i -= 1;
j = width - 1;
for c1 in text2.chars().rev() {
j -= 1;
if c0.to_lowercase().next() == c1.to_lowercase().next() {
table[i * width + j] = 1 + table[(i + 1) * width + j + 1];
} else {
let a = table[(i + 1) * width + j];
let b = table[i * width + j + 1];
table[i * width + j] = std::cmp::max(a, b);
}
}
}
table[0]
}