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(())
}