use std::sync::{Arc, Mutex};
use std::time::Duration;
use accesskit::NodeId as AccessibilityId;
use dioxus_core::VirtualDom;
use freya_common::EventMessage;
use freya_core::prelude::*;
use freya_dom::prelude::SafeDOM;
use freya_engine::prelude::FontCollection;
use freya_hooks::PlatformInformation;
use tokio::sync::broadcast;
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
use tokio::time::{interval, timeout};
use torin::geometry::{Area, Size2D};
use winit::window::CursorIcon;
use crate::test_node::TestNode;
use crate::test_utils::TestUtils;
use crate::{TestingConfig, SCALE_FACTOR};
pub struct TestingHandler {
    pub(crate) vdom: VirtualDom,
    pub(crate) utils: TestUtils,
    pub(crate) event_emitter: EventEmitter,
    pub(crate) event_receiver: EventReceiver,
    pub(crate) platform_event_emitter: UnboundedSender<EventMessage>,
    pub(crate) platform_event_receiver: UnboundedReceiver<EventMessage>,
    pub(crate) events_queue: EventsQueue,
    pub(crate) nodes_state: NodesState,
    pub(crate) font_collection: FontCollection,
    pub(crate) accessibility_manager: SharedAccessibilityManager,
    pub(crate) config: TestingConfig,
    pub(crate) ticker_sender: broadcast::Sender<()>,
    pub(crate) navigation_state: NavigatorState,
    pub(crate) platform_information: Arc<Mutex<PlatformInformation>>,
    pub(crate) cursor_icon: CursorIcon,
}
impl TestingHandler {
    pub(crate) fn init_dom(&mut self) {
        self.provide_vdom_contexts();
        let sdom = self.utils.sdom();
        let mut fdom = sdom.get();
        fdom.init_dom(&mut self.vdom, SCALE_FACTOR as f32);
    }
    pub fn config(&mut self) -> &mut TestingConfig {
        &mut self.config
    }
    fn provide_vdom_contexts(&mut self) {
        self.vdom
            .insert_any_root_context(Box::new(self.platform_event_emitter.clone()));
        self.vdom
            .insert_any_root_context(Box::new(Arc::new(self.ticker_sender.subscribe())));
        self.vdom
            .insert_any_root_context(Box::new(self.navigation_state.clone()));
        self.vdom
            .insert_any_root_context(Box::new(self.platform_information.clone()));
    }
    pub async fn wait_for_update(&mut self) -> (bool, bool) {
        self.wait_for_work(self.config.size());
        let mut ticker = if self.config.event_loop_ticker {
            Some(interval(Duration::from_millis(16)))
        } else {
            None
        };
        loop {
            let platform_ev = self.platform_event_receiver.try_recv();
            let vdom_ev = self.event_receiver.try_recv();
            if vdom_ev.is_err() && platform_ev.is_err() {
                break;
            }
            if let Ok(ev) = platform_ev {
                match ev {
                    EventMessage::RequestRerender => {
                        if let Some(ticker) = ticker.as_mut() {
                            ticker.tick().await;
                            self.ticker_sender.send(()).unwrap();
                            timeout(self.config.vdom_timeout(), self.vdom.wait_for_work())
                                .await
                                .ok();
                        }
                    }
                    EventMessage::FocusAccessibilityNode(node_id) => {
                        self.accessibility_manager
                            .lock()
                            .unwrap()
                            .set_focus_with_update(node_id);
                    }
                    EventMessage::SetCursorIcon(icon) => {
                        self.cursor_icon = icon;
                    }
                    _ => {}
                }
            }
            if let Ok(ev) = vdom_ev {
                self.vdom
                    .handle_event(ev.name.into(), ev.data.any(), ev.element_id, ev.bubbles);
                self.vdom.process_events();
            }
        }
        timeout(self.config.vdom_timeout(), self.vdom.wait_for_work())
            .await
            .ok();
        let (must_repaint, must_relayout) = self
            .utils
            .sdom()
            .get_mut()
            .render_mutations(&mut self.vdom, SCALE_FACTOR as f32);
        self.wait_for_work(self.config.size());
        self.ticker_sender.send(()).unwrap();
        (must_repaint, must_relayout)
    }
    pub fn wait_for_work(&mut self, size: Size2D) {
        self.utils.sdom().get_mut().layout().reset();
        let (layers, viewports) = process_layout(
            &self.utils.sdom().get(),
            Area {
                origin: (0.0, 0.0).into(),
                size,
            },
            &mut self.font_collection,
            SCALE_FACTOR as f32,
        );
        *self.utils.layers().lock().unwrap() = layers;
        *self.utils.viewports().lock().unwrap() = viewports;
        let dom = &self.utils.sdom().get_mut();
        process_accessibility(
            &self.utils.layers().lock().unwrap(),
            &dom.layout(),
            dom.rdom(),
            &mut self.accessibility_manager.lock().unwrap(),
        );
        process_events(
            dom,
            &self.utils.layers().lock().unwrap(),
            &mut self.events_queue,
            &self.event_emitter,
            &mut self.nodes_state,
            &self.utils.viewports().lock().unwrap(),
            SCALE_FACTOR,
        );
    }
    pub fn push_event(&mut self, event: PlatformEvent) {
        self.events_queue.push(event);
    }
    pub fn root(&mut self) -> TestNode {
        let root_id = {
            let sdom = self.utils.sdom();
            let fdom = sdom.get();
            let rdom = fdom.rdom();
            rdom.root_id()
        };
        self.utils.get_node_by_id(root_id)
    }
    pub fn focus_id(&self) -> AccessibilityId {
        self.accessibility_manager.lock().unwrap().focused_id
    }
    pub fn resize(&mut self, size: Size2D) {
        self.config.size = size;
        self.platform_information.lock().unwrap().window_size = size;
    }
    pub fn cursor_icon(&self) -> CursorIcon {
        self.cursor_icon
    }
    pub fn sdom(&self) -> &SafeDOM {
        self.utils.sdom()
    }
}