use std::{
    fs::File,
    io::Write,
    path::PathBuf,
    sync::Arc,
    time::Duration,
};
use accesskit::NodeId as AccessibilityId;
use dioxus_core::{
    Event,
    VirtualDom,
};
use freya_core::{
    accessibility::AccessibilityTree,
    dom::SafeDOM,
    event_loop_messages::{
        EventLoopMessage,
        TextGroupMeasurement,
    },
    events::{
        process_events,
        EventName,
        NodesState,
        PlatformEvent,
        PlatformEventData,
    },
    layout::process_layout,
    render::{
        Compositor,
        RenderPipeline,
    },
    states::AccessibilityNodeState,
    style::default_fonts,
    types::{
        EventEmitter,
        EventReceiver,
        EventsQueue,
        NativePlatformReceiver,
        NativePlatformSender,
    },
};
use freya_engine::prelude::{
    raster_n32_premul,
    Color,
    Data,
    EncodedImageFormat,
    FontCollection,
    FontMgr,
};
use freya_native_core::{
    dioxus::NodeImmutableDioxusExt,
    prelude::NodeImmutable,
};
use tokio::{
    sync::{
        broadcast,
        mpsc::{
            UnboundedReceiver,
            UnboundedSender,
        },
    },
    time::{
        interval,
        timeout,
    },
};
use torin::{
    geometry::{
        Area,
        Size2D,
    },
    prelude::CursorPoint,
};
use winit::{
    event::MouseButton,
    window::CursorIcon,
};
use crate::{
    config::TestingConfig,
    test_node::TestNode,
    test_utils::TestUtils,
    SCALE_FACTOR,
};
pub struct TestingHandler<T: 'static + Clone> {
    pub(crate) vdom: VirtualDom,
    pub(crate) utils: TestUtils,
    pub(crate) event_emitter: EventEmitter,
    pub(crate) event_receiver: EventReceiver,
    pub(crate) platform_event_emitter: UnboundedSender<EventLoopMessage>,
    pub(crate) platform_event_receiver: UnboundedReceiver<EventLoopMessage>,
    pub(crate) events_queue: EventsQueue,
    pub(crate) nodes_state: NodesState,
    pub(crate) platform_sender: NativePlatformSender,
    pub(crate) platform_receiver: NativePlatformReceiver,
    pub(crate) font_collection: FontCollection,
    pub(crate) font_mgr: FontMgr,
    pub(crate) accessibility_tree: AccessibilityTree,
    pub(crate) config: TestingConfig<T>,
    pub(crate) ticker_sender: broadcast::Sender<()>,
    pub(crate) cursor_icon: CursorIcon,
}
impl<T: 'static + Clone> TestingHandler<T> {
    pub(crate) fn init_dom(&mut self) {
        self.provide_vdom_contexts();
        let sdom = self.utils.sdom();
        let mut fdom = sdom.get_mut();
        fdom.init_dom(&mut self.vdom, SCALE_FACTOR as f32);
    }
    pub fn config(&mut self) -> &mut TestingConfig<T> {
        &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(self.platform_receiver.clone()));
        self.vdom
            .insert_any_root_context(Box::new(Arc::new(self.ticker_sender.subscribe())));
        let accessibility_generator = {
            let sdom = self.sdom();
            let fdom = sdom.get();
            fdom.accessibility_generator().clone()
        };
        self.vdom
            .insert_any_root_context(Box::new(accessibility_generator));
        if let Some(state) = self.config.state.clone() {
            self.vdom.insert_any_root_context(Box::new(state));
        }
    }
    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_events = self.event_receiver.try_recv();
            if vdom_events.is_err() && platform_ev.is_err() {
                break;
            }
            if let Ok(ev) = platform_ev {
                match ev {
                    EventLoopMessage::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();
                        }
                    }
                    EventLoopMessage::FocusAccessibilityNode(strategy) => {
                        let fdom = self.utils.sdom.get();
                        let rdom = fdom.rdom();
                        self.accessibility_tree
                            .focus_node_with_strategy(strategy, rdom);
                    }
                    EventLoopMessage::SetCursorIcon(icon) => {
                        self.cursor_icon = icon;
                    }
                    EventLoopMessage::RemeasureTextGroup(text_measurement) => {
                        self.measure_text_group(text_measurement);
                    }
                    _ => {}
                }
            }
            if let Ok(events) = vdom_events {
                let fdom = self.utils.sdom().get();
                let rdom = fdom.rdom();
                for event in events {
                    if let Some(element_id) =
                        rdom.get(event.node_id).and_then(|node| node.mounted_id())
                    {
                        let name = event.name.into();
                        let data = event.data.any();
                        let event = Event::new(data, event.bubbles);
                        self.vdom.runtime().handle_event(name, event, element_id);
                        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)
    }
    fn wait_for_work(&mut self, size: Size2D) {
        process_layout(
            &self.utils.sdom().get(),
            Area {
                origin: (0.0, 0.0).into(),
                size,
            },
            &mut self.font_collection,
            SCALE_FACTOR as f32,
            &default_fonts(),
        );
        let fdom = &self.utils.sdom().get_mut();
        {
            let rdom = fdom.rdom();
            let layout = fdom.layout();
            let mut dirty_accessibility_tree = fdom.accessibility_dirty_nodes();
            let (tree, node_id) = self.accessibility_tree.process_updates(
                rdom,
                &layout,
                &mut dirty_accessibility_tree,
            );
            self.platform_sender.send_modify(|state| {
                state.focused_accessibility_id = tree.focus;
                let node_ref = rdom.get(node_id).unwrap();
                let node_accessibility = node_ref.get::<AccessibilityNodeState>().unwrap();
                let layout_node = layout.get(node_id).unwrap();
                state.focused_accessibility_node =
                    AccessibilityTree::create_node(&node_ref, layout_node, &node_accessibility)
            });
        }
        process_events(
            fdom,
            &mut self.events_queue,
            &self.event_emitter,
            &mut self.nodes_state,
            SCALE_FACTOR,
            self.accessibility_tree.focused_node_id(),
        );
    }
    fn measure_text_group(&self, text_measurement: TextGroupMeasurement) {
        let sdom = self.utils.sdom();
        sdom.get()
            .measure_paragraphs(text_measurement, SCALE_FACTOR);
    }
    pub fn push_event(&mut self, event: impl Into<PlatformEvent>) {
        self.events_queue.push(event.into());
    }
    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)
            .get(0)
    }
    pub fn focus_id(&self) -> AccessibilityId {
        self.accessibility_tree.focused_id
    }
    pub fn resize(&mut self, size: Size2D) {
        self.config.size = size;
        self.platform_sender.send_modify(|state| {
            state.information.viewport_size = size;
        });
        self.utils.sdom().get_mut().layout().reset();
        self.utils
            .sdom()
            .get_mut()
            .compositor_dirty_area()
            .unite_or_insert(&Area::new((0.0, 0.0).into(), size));
    }
    pub fn cursor_icon(&self) -> CursorIcon {
        self.cursor_icon
    }
    pub fn sdom(&self) -> &SafeDOM {
        self.utils.sdom()
    }
    pub fn create_snapshot(&mut self) -> Data {
        let fdom = self.utils.sdom.get();
        let (width, height) = self.config.size.to_i32().to_tuple();
        let mut surface =
            raster_n32_premul((width, height)).expect("Failed to create the surface.");
        surface.canvas().clear(Color::WHITE);
        let mut dirty_surface = surface
            .new_surface_with_dimensions((width, height))
            .expect("Failed to create the dirty surface.");
        dirty_surface.canvas().clear(Color::WHITE);
        let mut compositor = Compositor::default();
        let mut render_pipeline = RenderPipeline {
            canvas_area: Area::from_size((width as f32, height as f32).into()),
            rdom: fdom.rdom(),
            compositor_dirty_area: &mut fdom.compositor_dirty_area(),
            compositor_dirty_nodes: &mut fdom.compositor_dirty_nodes(),
            compositor_cache: &mut fdom.compositor_cache(),
            layers: &mut fdom.layers(),
            layout: &mut fdom.layout(),
            background: Color::WHITE,
            surface: &mut surface,
            dirty_surface: &mut dirty_surface,
            compositor: &mut compositor,
            scale_factor: SCALE_FACTOR as f32,
            selected_node: None,
            font_collection: &mut self.font_collection,
            font_manager: &self.font_mgr,
            default_fonts: &["Fira Sans".to_string()],
            images_cache: &mut fdom.images_cache(),
        };
        render_pipeline.run();
        let image = surface.image_snapshot();
        let mut context = surface.direct_context();
        image
            .encode(context.as_mut(), EncodedImageFormat::PNG, None)
            .expect("Failed to encode the snapshot.")
    }
    pub fn save_snapshot(&mut self, snapshot_path: impl Into<PathBuf>) {
        let mut snapshot_file =
            File::create(snapshot_path.into()).expect("Failed to create the snapshot file.");
        let snapshot_data = self.create_snapshot();
        snapshot_file
            .write_all(&snapshot_data)
            .expect("Failed to save the snapshot file.");
    }
    pub async fn move_cursor(&mut self, cursor: impl Into<CursorPoint>) {
        self.push_event(PlatformEvent {
            name: EventName::MouseMove,
            data: PlatformEventData::Mouse {
                cursor: cursor.into(),
                button: Some(MouseButton::Left),
            },
        });
        self.wait_for_update().await;
    }
    pub async fn click_cursor(&mut self, cursor: impl Into<CursorPoint> + Clone) {
        self.push_event(PlatformEvent {
            name: EventName::MouseDown,
            data: PlatformEventData::Mouse {
                cursor: cursor.clone().into(),
                button: Some(MouseButton::Left),
            },
        });
        self.wait_for_update().await;
        self.push_event(PlatformEvent {
            name: EventName::MouseUp,
            data: PlatformEventData::Mouse {
                cursor: cursor.into(),
                button: Some(MouseButton::Left),
            },
        });
        self.wait_for_update().await;
    }
}