use core::{
    cell::RefCell,
    sync::atomic::{AtomicBool, Ordering},
    time::Duration,
};

use alloc::{
    boxed::Box, collections::vec_deque::VecDeque, rc::Rc, string::ToString, sync::Arc, vec::Vec,
};
use embassy_sync::blocking_mutex::{self, raw::CriticalSectionRawMutex};
use esp_hal::time::Instant;
use esp_hal_bounce_buffers::SwapchainWriter;
use log::{debug, info};
use slint::{
    EventLoopError, PhysicalSize, SharedString, WindowSize,
    platform::{
        EventLoopProxy, Key, WindowEvent,
        software_renderer::{RenderingRotation, RepaintBufferType, Rgb565Pixel, SoftwareRenderer},
    },
};
use xkbcommon::xkb::{self, Keysym};

use crate::proxy::{KEY_MESSAGE_CHANNEL, TryFromKeysym};

use super::window_adapter::SoftwareWindowAdapter;

pub struct SlintBackend {
    pub window_size: [u32; 2],
    pub window: RefCell<Option<Rc<SoftwareWindowAdapter>>>,
    pub swapchain: RefCell<SwapchainWriter>,
    pub quit_event_loop: Arc<AtomicBool>,
    pub events: Arc<
        blocking_mutex::Mutex<CriticalSectionRawMutex, RefCell<VecDeque<Box<dyn FnOnce() + Send>>>>,
    >,
}

impl slint::platform::Platform for SlintBackend {
    fn create_window_adapter(
        &self,
    ) -> Result<Rc<dyn slint::platform::WindowAdapter>, slint::PlatformError> {
        let renderer =
            SoftwareRenderer::new_with_repaint_buffer_type(RepaintBufferType::SwappedBuffers);
        renderer.set_rendering_rotation(RenderingRotation::Rotate270);
        let window = SoftwareWindowAdapter::new(renderer);
        // window.set_scale_factor(4.0);
        window.set_size(WindowSize::Physical(PhysicalSize::new(
            self.window_size[0],
            self.window_size[1],
        )));
        self.window.replace(Some(window.clone()));
        Ok(window)
    }

    fn duration_since_start(&self) -> Duration {
        Duration::from_millis(Instant::now().duration_since_epoch().as_millis())
    }

    fn new_event_loop_proxy(&self) -> Option<Box<dyn EventLoopProxy>> {
        Some(Box::new(AcidEventLoopProxy {
            quit_event_loop: self.quit_event_loop.clone(),
            events: self.events.clone(),
        }))
    }

    fn run_event_loop(&self) -> Result<(), slint::PlatformError> {
        // Instead of `loop`ing here, we execute a single iteration and handle `loop`ing
        // in `crate::run_renderer_task`, where we can make use of `await`.

        /* loop */
        {
            let drained_events = self
                .events
                .lock(|events| events.borrow_mut().drain(..).collect::<Vec<_>>());

            for event in drained_events {
                (event)();
            }

            if let Some(window) = self.window.borrow().clone() {
                // Handle key presses
                while let Ok(mut key_message) = KEY_MESSAGE_CHANNEL.try_receive() {
                    debug!("key_message = {key_message:?}");

                    if let Some(string) = key_message.string.as_ref()
                        && (Keysym::a..=Keysym::z).contains(&key_message.keysym)
                        && let &[code] = string.as_bytes()
                    {
                        const UNICODE_CTRL_A: char = '\u{1}';

                        let letter_index_from_keysym =
                            key_message.keysym.raw().wrapping_sub(Keysym::a.raw());
                        let letter_index_from_string =
                            (code as u32).wrapping_sub(UNICODE_CTRL_A as u32);

                        if letter_index_from_keysym == letter_index_from_string {
                            key_message.keysym =
                                Keysym::new(Keysym::a.raw() + letter_index_from_keysym);
                            // TODO: Avoid allocation
                            key_message.string =
                                Some(((b'a' + letter_index_from_keysym as u8) as char).to_string());

                            info!(
                                "Translating CTRL-{letter} to {letter}",
                                letter = (b'A' + letter_index_from_keysym as u8) as char
                            );
                        }
                    }

                    // let text = key_message.string.map(SharedString::from).or_else(|| {
                    //     Key::try_from_keysym(key_message.keysym).map(SharedString::from)
                    // });
                    let text = Key::try_from_keysym(key_message.keysym)
                        .map(SharedString::from)
                        .or_else(|| key_message.string.map(SharedString::from));

                    debug!("text = {text:?}");

                    if let Some(text) = text {
                        match key_message.direction {
                            xkb::KeyDirection::Down => window
                                .try_dispatch_event(WindowEvent::KeyPressed { text: text.clone() })
                                .unwrap(),
                            xkb::KeyDirection::Up => window
                                .try_dispatch_event(WindowEvent::KeyReleased { text })
                                .unwrap(),
                        }
                    }
                }

                window.draw_if_needed(|renderer| {
                    // TODO: VSYNC support.
                    let mut swapchain = self.swapchain.borrow_mut();
                    let mut write_guard = swapchain.write();
                    let framebuffer = bytemuck::cast_slice_mut::<u8, Rgb565Pixel>(&mut write_guard);
                    renderer.render(framebuffer, self.window_size[1] as usize);
                    info!("UI rendered.");
                });
            }
        }

        Ok(())
    }
}

struct AcidEventLoopProxy {
    pub quit_event_loop: Arc<AtomicBool>,
    pub events: Arc<
        blocking_mutex::Mutex<CriticalSectionRawMutex, RefCell<VecDeque<Box<dyn FnOnce() + Send>>>>,
    >,
}

impl EventLoopProxy for AcidEventLoopProxy {
    fn quit_event_loop(&self) -> Result<(), EventLoopError> {
        self.quit_event_loop.store(true, Ordering::SeqCst);
        Ok(())
    }

    fn invoke_from_event_loop(
        &self,
        event: Box<dyn FnOnce() + Send>,
    ) -> Result<(), EventLoopError> {
        self.events.lock(|events| {
            events.borrow_mut().push_back(event);
        });
        Ok(())
    }
}
