tfpl 0.1.0

FPL, in the terminal
use color_eyre::eyre::Result;
use crossterm::event::{KeyCode, KeyEvent};
use fpl_api;
use ratatui::prelude::Rect;
use ratatui_image::{picker::Picker, protocol::StatefulProtocol, StatefulImage};
use serde::{Deserialize, Serialize};
use tokio::sync::mpsc;
use tokio::sync::mpsc::UnboundedSender;
use image::io::Reader;
use image::DynamicImage;
use bytes::Bytes; 

use crate::{
    action::Action,
    event::Event,
    components::{fps::FpsCounter, home::Home, Component},
    config::Config,
    mode::Mode,
    tui,
};

pub struct App {
    pub config: Config,
    pub tick_rate: f64,
    pub frame_rate: f64,
    pub components: Vec<Box<dyn Component>>,
    pub should_quit: bool,
    pub should_suspend: bool,
    pub mode: Mode,
    pub last_tick_key_events: Vec<KeyEvent>,
    fpl_client: fpl_api::FPLClient,
    bootstrap_data: fpl_api::bootstrap::BootstrapData,
}

fn decode_bytes_to_image(data: Bytes) -> Result<DynamicImage, image::ImageError> {
    Reader::new(std::io::Cursor::new(data)).with_guessed_format()?.decode()
}

async fn get_player_image(pc: i64, tx: UnboundedSender<Event>)  {
    //let dyn_img = image::ImageReader::open("./p223094.png").unwrap().decode().unwrap();
    // tx.send(Event::PlayerImage(pc, dyn_img));
    let resp = reqwest::get(format!("https://resources.premierleague.com/premierleague/photos/players/110x140/p{}.png", pc)).await;
    if let Ok(ok_resp) = resp {
        if let Ok(bytes) = ok_resp.bytes().await {
            if let Ok(image) = decode_bytes_to_image(bytes) {
                tx.send(Event::PlayerImage(pc, image));
            }
        }
    } 
}

#[cfg(unix)]
fn get_picker() -> Option<Picker> {
    Picker::from_termios()
        .ok()
        .map(|mut picker| {
            picker.guess_protocol();
            picker
        })
        // .filter(|picker| picker.protocol_type != ProtocolType::Halfblocks)
}

#[cfg(target_os = "windows")]
fn get_picker() -> Option<Picker> {
    use windows_sys::Win32::System::Console::GetConsoleWindow;
    use windows_sys::Win32::UI::HiDpi::GetDpiForWindow;

    struct FontSize {
        pub width: u16,
        pub height: u16,
    }
    impl Default for FontSize {
        fn default() -> Self {
            FontSize {
                width: 17,
                height: 38,
            }
        }
    }

    let size: FontSize = match unsafe { GetDpiForWindow(GetConsoleWindow()) } {
        96 => FontSize {
            width: 9,
            height: 20,
        },
        120 => FontSize {
            width: 12,
            height: 25,
        },
        144 => FontSize {
            width: 14,
            height: 32,
        },
        _ => FontSize::default(),
    };

    let mut picker = Picker::new((size.width, size.height));

    let protocol = picker.guess_protocol();

    if protocol == ProtocolType::Halfblocks {
        return None;
    }
    Some(picker)
}

impl App {
    pub async fn new(tick_rate: f64, frame_rate: f64, player_id: String) -> Result<Self> {
        let fps = FpsCounter::default();
        let config = Config::new()?;
        let mode = Mode::Home;
        let fpl_client = fpl_api::FPLClient::new();
        let bootstrap_data = fpl_client.get_bootstrap_data().await?;
        let manager = fpl_client.get_manager_details(&player_id).await?;
        let gw_picks = fpl_client.get_manager_team_for_gw(&player_id, &manager.current_event.to_string()).await?;

        let mut picker = get_picker(); 

        // TODO
        // Load an image with the image crate.
        // let dyn_img = image::ImageReader::open("./p223094.png")?.decode()?;

        // // Create the Protocol which will be used by the widget.
        // let mut image2 = picker.map( |p| p.new_resize_protocol(dyn_img));

        let home = Home::new(manager, bootstrap_data.clone(), gw_picks, picker);
        Ok(Self {
            tick_rate,
            frame_rate,
            components: vec![Box::new(home), Box::new(fps)],
            should_quit: false,
            should_suspend: false,
            config,
            mode,
            last_tick_key_events: Vec::new(),
            fpl_client,
            bootstrap_data,
        })
    }

    pub async fn run(&mut self) -> Result<()> {
        let (action_tx, mut action_rx) = mpsc::unbounded_channel();
        let (event_tx, mut event_rx) = mpsc::unbounded_channel();

        let mut tui = tui::Tui::new(event_tx.clone())?.tick_rate(self.tick_rate).frame_rate(self.frame_rate);
        // tui.mouse(true);
        tui.enter()?;

        for component in self.components.iter_mut() {
            component.register_action_handler(action_tx.clone())?;
        }

        for component in self.components.iter_mut() {
            component.register_config_handler(self.config.clone())?;
        }

        for component in self.components.iter_mut() {
            component.init(tui.size()?)?;
        }

        loop {
            if let Some(e) = event_rx.recv().await {
                match e {
                    Event::Quit => action_tx.send(Action::Quit)?,
                    Event::Tick => action_tx.send(Action::Tick)?,
                    Event::Render => action_tx.send(Action::Render)?,
                    Event::Resize(x, y) => action_tx.send(Action::Resize(x, y))?,
                    Event::Key(key) => {
                        match key.code {
                            KeyCode::Char('q') => action_tx.send(Action::Quit)?,
                            _ => {},
                        }
                    },
                    _ => {},
                }
                for component in self.components.iter_mut() {
                    if let Some(action) = component.handle_events(Some(e.clone()))? {
                        action_tx.send(action)?;
                    }
                }
            }

            while let Ok(action) = action_rx.try_recv() {
                if action != Action::Tick && action != Action::Render {
                    log::debug!("{action:?}");
                }
                match action {
                    Action::Tick => {
                        self.last_tick_key_events.drain(..);
                    },
                    Action::Quit => self.should_quit = true,
                    Action::Suspend => self.should_suspend = true,
                    Action::Resume => self.should_suspend = false,
                    Action::GetPlayerImage(player_code) => {
                        let task_event = event_tx.clone();
                        tokio::spawn(
                            get_player_image(player_code, task_event)

                        );
                    },
                    Action::Resize(w, h) => {
                        tui.resize(Rect::new(0, 0, w, h))?;
                        tui.draw(|f| {
                            for component in self.components.iter_mut() {
                                let r = component.draw(f, f.size());
                                if let Err(e) = r {
                                    action_tx.send(Action::Error(format!("Failed to draw: {:?}", e))).unwrap();
                                }
                            }
                        })?;
                    },
                    Action::Render => {
                        tui.draw(|f| {
                            for component in self.components.iter_mut() {
                                let r = component.draw(f, f.size());
                                if let Err(e) = r {
                                    action_tx.send(Action::Error(format!("Failed to draw: {:?}", e))).unwrap();
                                }
                            }
                        })?;
                    },
                    _ => {},
                }
                for component in self.components.iter_mut() {
                    if let Some(action) = component.update(action.clone())? {
                        action_tx.send(action)?
                    };
                }
            }
            if self.should_suspend {
                tui.suspend()?;
                action_tx.send(Action::Resume)?;
                tui = tui::Tui::new(event_tx.clone())?.tick_rate(self.tick_rate).frame_rate(self.frame_rate);
                // tui.mouse(true);
                tui.enter()?;
            } else if self.should_quit {
                tui.stop()?;
                break;
            }
        }
        tui.exit()?;
        Ok(())
    }
}