use anyhow::Result;
use crossterm::{
    event::{DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyModifiers},
    execute,
    terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use dioxus_core::exports::futures_channel::mpsc::unbounded;
use dioxus_core::*;
use futures::{channel::mpsc::UnboundedSender, pin_mut, StreamExt};
use std::{
    collections::HashMap,
    io,
    time::{Duration, Instant},
};
use stretch2::{prelude::Size, Stretch};
use style::RinkStyle;
use tui::{backend::CrosstermBackend, Terminal};

mod attributes;
mod config;
mod hooks;
mod layout;
mod render;
mod style;
mod widget;

pub use attributes::*;
pub use config::*;
pub use hooks::*;
pub use layout::*;
pub use render::*;

pub fn launch(app: Component<()>) {
    launch_cfg(app, Config::default())
}

pub fn launch_cfg(app: Component<()>, cfg: Config) {
    let mut dom = VirtualDom::new(app);
    let (tx, rx) = unbounded();

    let cx = dom.base_scope();

    let (handler, state) = RinkInputHandler::new(rx, cx);

    cx.provide_root_context(state);

    dom.rebuild();

    render_vdom(&mut dom, tx, handler, cfg).unwrap();
}

pub struct TuiNode<'a> {
    pub layout: stretch2::node::Node,
    pub block_style: RinkStyle,
    pub tui_modifier: TuiModifier,
    pub node: &'a VNode<'a>,
}

pub fn render_vdom(
    vdom: &mut VirtualDom,
    ctx: UnboundedSender<TermEvent>,
    handler: RinkInputHandler,
    cfg: Config,
) -> Result<()> {
    // Setup input handling
    let (tx, mut rx) = unbounded();
    std::thread::spawn(move || {
        let tick_rate = Duration::from_millis(100);
        let mut last_tick = Instant::now();
        loop {
            // poll for tick rate duration, if no events, sent tick event.
            let timeout = tick_rate
                .checked_sub(last_tick.elapsed())
                .unwrap_or_else(|| Duration::from_secs(0));

            if crossterm::event::poll(timeout).unwrap() {
                let evt = crossterm::event::read().unwrap();
                tx.unbounded_send(InputEvent::UserInput(evt)).unwrap();
            }

            if last_tick.elapsed() >= tick_rate {
                tx.unbounded_send(InputEvent::Tick).unwrap();
                last_tick = Instant::now();
            }
        }
    });

    tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()?
        .block_on(async {
            /*
            Get the terminal to calcualte the layout from
            */
            enable_raw_mode().unwrap();
            let mut stdout = std::io::stdout();
            execute!(stdout, EnterAlternateScreen, EnableMouseCapture).unwrap();
            let backend = CrosstermBackend::new(io::stdout());
            let mut terminal = Terminal::new(backend).unwrap();

            terminal.clear().unwrap();

            loop {
                /*
                -> collect all the nodes with their layout
                -> solve their layout
                -> resolve events
                -> render the nodes in the right place with tui/crosstream
                -> while rendering, apply styling

                use simd to compare lines for diffing?


                todo: reuse the layout and node objects.
                our work_with_deadline method can tell us which nodes are dirty.
                */
                let mut layout = Stretch::new();
                let mut nodes = HashMap::new();

                let root_node = vdom.base_scope().root_node();
                layout::collect_layout(&mut layout, &mut nodes, vdom, root_node);
                /*
                Compute the layout given the terminal size
                */
                let node_id = root_node.try_mounted_id().unwrap();
                let root_layout = nodes[&node_id].layout;
                let mut events = Vec::new();

                terminal.draw(|frame| {
                    // size is guaranteed to not change when rendering
                    let dims = frame.size();
                    let width = dims.width;
                    let height = dims.height;
                    layout
                        .compute_layout(
                            root_layout,
                            Size {
                                width: stretch2::prelude::Number::Defined(width as f32),
                                height: stretch2::prelude::Number::Defined(height as f32),
                            },
                        )
                        .unwrap();

                    // resolve events before rendering
                    events = handler.get_events(vdom, &layout, &mut nodes, root_node);
                    render::render_vnode(
                        frame,
                        &layout,
                        &mut nodes,
                        vdom,
                        root_node,
                        &RinkStyle::default(),
                        cfg,
                    );
                    assert!(nodes.is_empty());
                })?;

                for e in events {
                    vdom.handle_message(SchedulerMsg::Event(e));
                }

                use futures::future::{select, Either};
                {
                    let wait = vdom.wait_for_work();
                    pin_mut!(wait);

                    match select(wait, rx.next()).await {
                        Either::Left((_a, _b)) => {
                            //
                        }
                        Either::Right((evt, _o)) => {
                            match evt.as_ref().unwrap() {
                                InputEvent::UserInput(event) => match event {
                                    TermEvent::Key(key) => {
                                        if matches!(key.code, KeyCode::Char('C' | 'c'))
                                            && key.modifiers.contains(KeyModifiers::CONTROL)
                                        {
                                            break;
                                        }
                                    }
                                    TermEvent::Resize(_, _) | TermEvent::Mouse(_) => {}
                                },
                                InputEvent::Tick => {} // tick
                                InputEvent::Close => break,
                            };

                            if let InputEvent::UserInput(evt) = evt.unwrap() {
                                ctx.unbounded_send(evt).unwrap();
                            }
                        }
                    }
                }

                vdom.work_with_deadline(|| false);
            }

            disable_raw_mode()?;
            execute!(
                terminal.backend_mut(),
                LeaveAlternateScreen,
                DisableMouseCapture
            )?;
            terminal.show_cursor()?;

            Ok(())
        })
}

enum InputEvent {
    UserInput(TermEvent),
    Tick,

    #[allow(dead_code)]
    Close,
}
