use std::io::{stderr, IsTerminal as _, Stdout};
use std::pin::pin;
use std::time::Duration;
use crossterm::event::{KeyModifiers, MouseButton, MouseEvent, MouseEventKind};
use futures::pin_mut;
use quinn::rustls::crypto::aws_lc_rs::default_provider;
use quinn::VarInt;
use ratatui::layout::Position;
use ratatui::prelude::{Backend, CrosstermBackend};
use ratatui::Terminal;
use tokio::sync::mpsc::unbounded_channel;
use super::app::App;
use super::event::{Event, EventHandler};
use super::handlers::{handle_confirm_action, handle_key_events};
use super::tui::Tui;
use crate::cli_utils::configure_logging;
use crate::common::client::connection::conn::run_client;
use crate::common::result::DynResult;
use super::ClientOptions;
pub async fn main(module: &'static str, options: &ClientOptions) -> DynResult<()> {
default_provider()
.install_default()
.expect("Installing default provider should work!");
if !stderr().is_terminal() {
configure_logging(module)?;
} else {
eprintln!("Redirect stderr to a file to activate logging.");
}
let (connection, writer, reader) = super::init::initialize_connection(options).await?;
let (umove_send, umove_recv) = unbounded_channel();
let mut app = pin!(App::new(umove_send, options));
let backend = CrosstermBackend::new(std::io::stdout());
let terminal = Terminal::new(backend)?;
let events = EventHandler::new(Duration::from_millis(250));
let conn_fut = tokio::spawn(run_client(
options.get_timeout(),
writer,
reader,
events.get_sender(),
events.get_sender(),
umove_recv,
));
let mut tui = Tui::new(terminal, events);
tui.init()?;
let main_loop_fut = async {
let res = run_main_loop(&mut app, &mut tui).await;
tui.exit()?;
res
};
pin_mut!(main_loop_fut);
let result = futures::future::select(main_loop_fut, conn_fut).await;
Tui::<CrosstermBackend<Stdout>>::reset()?;
connection.close(VarInt::from_u32(1), b"Exiting");
use futures::future::Either::*;
let _: () = match result {
Left((res, _)) => match res {
Ok(()) => (),
Err(err) => {
println!("{err}");
Err(err)?;
}
},
Right((res, _)) => {
println!("Disconnected");
log::info!("Disconnected");
res??
}
};
Ok(())
}
#[inline]
async fn run_main_loop<B: Backend>(app: &mut App, tui: &mut Tui<B>) -> DynResult<()> {
log::debug!("run_main_loop on thread {:?}", std::thread::current().id());
while app.running {
tui.draw(app)?;
match tui.events.next().await.ok_or("Events channel closed")? {
Event::Tick => app.tick(),
Event::Key(key_event) => handle_key_events(key_event, app).await?,
Event::Mouse(MouseEvent {
kind: MouseEventKind::Up(MouseButton::Left),
column: c,
row: r,
modifiers: KeyModifiers::NONE,
}) => {
if let Some(button) = app.confirm_button {
if button.contains(Position::new(c, r)) {
handle_confirm_action(app).await?;
}
}
if let Some(pop_up) = app.pop_up {
if !pop_up.contains(Position::new(c, r)) {
app.close_err_pop_up();
}
}
}
Event::Mouse(_) => {}
Event::Resize(_, _) => {}
Event::ServerEvent(evt) => {
use super::event::ServerEvent::*;
match evt {
Other => (),
Error(error) => {
if let Some(old) = app.submit_error.as_mut() {
old.push('\n');
old.push_str(&error);
} else {
app.submit_error = Some(error);
}
app.user_tried_to_change_state_already = false;
}
}
}
Event::ClientStateUpdated(client_state) => {
if client_state.timed_out {
return Err("Timed out!".into());
}
if app.client_state.state != client_state.state {
app.user_tried_to_change_state_already = false;
}
if app.client_state.current_plays != client_state.current_plays {
if let Some(curr_play) = client_state.current_plays.as_ref() {
app.old_plays.push(curr_play.clone());
}
}
app.client_state = client_state;
}
}
}
Ok(())
}