use crate::gather;
use crate::names_database::AsyncConnection;
use crate::DynResult;
use crossterm::{
cursor,
event::{Event, EventStream, KeyCode},
style::{self, StyledContent, Stylize},
terminal, ExecutableCommand, QueueableCommand,
};
use futures::stream::StreamExt;
use std::borrow::Borrow;
use std::cmp::Ordering;
use tokio::sync::mpsc;
type IDedName = (String, i64);
pub async fn pick(
db: &AsyncConnection,
count: u16,
gather: Option<gather::Gender>,
) -> DynResult<()> {
let (ntx, mut nrx) = mpsc::channel((count + count / 2) as usize);
if let Some(gender) = gather {
let tg = ntx.clone();
let gdb = db.clone();
tokio::spawn(async move {
let mut incoming = gather::all_names(gender);
'getpages: while let Some(names) = incoming.recv().await {
let handles: Vec<_> = names
.into_iter()
.map(|name| {
let xdb = gdb.clone();
let xg = tg.clone();
tokio::spawn(async move {
if let Ok((id, true)) =
xdb.add_name(name.clone()).await
{
xg.send((name, id)).await.is_ok()
} else {
true
}
})
})
.collect();
for h in handles {
match h.await {
Ok(true) => (),
_ => break 'getpages,
}
}
}
});
}
{
let db = db.clone();
tokio::spawn(async move {
let names = db.list_names().await;
for name in names.ok().into_iter().flat_map(Vec::into_iter) {
if db.check_depth(name.1, count as i64).await.unwrap_or(0) <= count as i64 {
match ntx.send(name).await {
Ok(_) => (),
Err(_) => break,
}
}
}
});
}
let mut shortlist = Vec::with_capacity(count as usize * 2);
for _ in 0..(count * 2) {
if let Some(name) = nrx.recv().await {
shortlist.push(name);
} else {
break;
}
}
let mut stderr = std::io::stderr();
let mut comparator = CompareContext::new(db.clone(), &mut stderr).await?;
'sponge: loop {
'fetch: while shortlist.len() < count as usize * 2 {
match nrx.recv().await {
Some(name) => {
shortlist.push(name);
}
None => {
break 'fetch;
}
}
}
if shortlist.len() <= count.into() {
break 'sponge;
}
let mut i: *mut IDedName = &mut shortlist[0];
let mut j: *mut IDedName = {
let len = shortlist.len() - 1;
&mut shortlist[len]
};
let threshold: *mut IDedName = unsafe { i.offset(count as isize - 1) };
'sieve: loop {
let pivot: *mut IDedName = std::cmp::max(
unsafe { i.offset(j.offset_from(i) / 2) },
threshold,
);
let pivot = unsafe { comparator.partition(i, j, pivot).await? };
match pivot.cmp(&threshold) {
Ordering::Less => {
i = unsafe { pivot.offset(1) };
}
Ordering::Equal => {
break 'sieve;
}
Ordering::Greater => {
j = pivot;
}
}
}
shortlist.truncate(count as usize);
}
{
let len = shortlist.len();
let mut stack: Vec<(*mut IDedName, *mut IDedName)> =
vec![(&mut shortlist[0], &mut shortlist[len - 1])];
while let Some((lo, hi)) = stack.pop() {
unsafe {
if lo < hi {
let p = lo.offset(hi.offset_from(lo) / 2);
let p = comparator.partition(lo, hi, p).await?;
stack.extend_from_slice(&[(lo, p), (p.offset(1), hi)]);
}
}
}
}
std::mem::drop(comparator);
for (rank, (name, _id)) in shortlist.into_iter().enumerate() {
println!("{}: {}", rank + 1, name);
}
Ok(())
}
struct CompareContext<'a, W: std::io::Write> {
db: AsyncConnection,
events: EventStream,
out: &'a mut W,
}
impl<'a, W: std::io::Write> std::ops::Drop for CompareContext<'a, W> {
fn drop(&mut self) {
self.out.execute(cursor::Show).ok();
terminal::disable_raw_mode().ok();
}
}
impl<'a, W: std::io::Write> CompareContext<'a, W> {
async fn new(
db: AsyncConnection,
out: &'a mut W,
) -> DynResult<CompareContext<'a, W>> {
out.execute(cursor::Hide)?;
terminal::enable_raw_mode()?;
Ok(Self {
db,
events: EventStream::new(),
out,
})
}
async unsafe fn partition(
&mut self,
lo: *mut IDedName,
hi: *mut IDedName,
pivot: *mut IDedName,
) -> DynResult<*mut IDedName> {
use Ordering::*;
let mut i = lo.offset(-1);
let mut j = hi.offset(1);
let pivot = (&*pivot).clone();
loop {
'l: loop {
i = i.offset(1);
if self.compare(&*i, &pivot).await? != Greater {
break 'l;
}
}
'r: loop {
j = j.offset(-1);
if self.compare(&*j, &pivot).await? != Less {
break 'r;
}
}
if i >= j {
return Ok(j);
} else {
i.swap(j);
}
}
}
async fn compare(
&mut self,
a: &IDedName,
b: &IDedName,
) -> DynResult<Ordering> {
match self.db.compare(a.1, b.1).await? {
None => {
print_pair(self.out, a.0.borrow(), b.0.borrow())?;
let (result, better, worse) = loop {
if let Event::Key(key) =
self.events.next().await.ok_or_else(|| {
<std::io::ErrorKind as Into<std::io::Error>>::into(
std::io::ErrorKind::BrokenPipe,
)
})??
{
match key.code {
KeyCode::Left | KeyCode::Char(',' | '<') => {
break (Ordering::Greater, a.1, b.1);
}
KeyCode::Right | KeyCode::Char('.' | '>') => {
break (Ordering::Less, b.1, a.1);
}
KeyCode::Esc => {
return Err(<std::io::ErrorKind as Into<
std::io::Error,
>>::into(
std::io::ErrorKind::Interrupted
)
.into());
}
KeyCode::Char('c') => {
if key.modifiers.contains(
crossterm::event::KeyModifiers::CONTROL,
) {
return Err(<std::io::ErrorKind as Into<
std::io::Error,
>>::into(
std::io::ErrorKind::Interrupted,
)
.into());
}
}
_ => (),
}
}
};
self.out.queue(style::Print('\n'))?;
self.out.execute(cursor::MoveToColumn(0))?;
self.db.insert_preference(better as i64, worse as i64).await?;
Ok(result)
}
Some(c) => Ok(c),
}
}
}
fn print_pair<A, B>(
out: &mut impl std::io::Write,
a: A,
b: B,
) -> std::io::Result<()>
where
A: Stylize<Styled = StyledContent<A>> + std::fmt::Display,
B: Stylize<Styled = StyledContent<B>> + std::fmt::Display,
{
out.queue(style::PrintStyledContent(a.reset()))?
.queue(style::PrintStyledContent(" ◆ ".grey()))?
.queue(style::PrintStyledContent(b.reset()))?
.queue(style::PrintStyledContent(" > ".dark_blue()))?;
out.flush()?;
Ok(())
}