use std::{
    collections::VecDeque,
    sync::{
        Arc,
        atomic::Ordering,
        mpsc::{Receiver, Sender},
    },
};
use std::any::Any;
use egui::{Ui, WidgetText};
use egui_tiles::TileId;
use emulator_core::{
    memory::{
        PhysAddr,
        ram::MemoryBank
    },
    processor::{
        interrupts::Interrupt,
        state::SharedState
    }
};
use crate::ui::components::registers::Registers;
use crate::ui::debugger::Action;
use crate::ui::debugger::panels::TilePane;
use super::Component;

/// ## SERIAL LAYOUT: (from the perspective of code inside the ui)
/// - [base+0x00]: ser_out valid flag - set by kernel after writing a byte to serial
/// - [base+0x01]: ser_out byte       - data payload for ui to read
/// - [base+0x02]: ser_in valid flag  - set by ui after writing a byte to serial
/// - [base+0x03]: ser_in byte        - data payload for kernel to read
pub struct Serial {
    pub read_rx: Receiver<u8>,
    pub write_tx: Sender<u8>,
    pub read_buff: Vec<u8>,
    pub write_buff: String,

    pub visible: bool,
}

impl TilePane for Serial {
    fn title(&self) -> WidgetText {
        "Serial".into()
    }

    fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option<Action> {
        Component::ui(self, ui);
        None
    }

    fn as_any_mut(&mut self) -> &mut dyn Any {
        self
    }
}

impl Serial {
    fn uart_io_thread(
        mem: MemoryBank,
        state: Arc<SharedState>,
        uart_base: PhysAddr,
        tx: Sender<u8>,
        rx: Receiver<u8>,
    ) {
        let mut write_buffer: VecDeque<u8> = VecDeque::new();

        loop {
            while let Ok(byte) = rx.try_recv() {
                write_buffer.push_back(byte);
            }

            let ser_out_valid = mem.read_byte(uart_base + 0);
            // Kernel => ui: valid flag in byte 0, data in byte 1
            if ser_out_valid == 0x01 {
                let ser_out = mem.read_byte(uart_base + 1);
                let _ = tx.send(ser_out);
                mem.write_byte(uart_base, 0x00); // clear the whole word
            }

            if !write_buffer.is_empty() {
                // Emulator => kernel: valid flag in byte 2, data in byte 3
                let ser_in_valid = mem.read_byte(uart_base + 2);
                if ser_in_valid == 0x00 {
                    if let Some(byte) = write_buffer.pop_front() {
                        mem.write_byte(uart_base + 3, byte);
                        mem.write_byte(uart_base + 2, 0x01);
                    }
                }

                std::thread::sleep(std::time::Duration::from_millis(2));

                state.interrupt_queue.send(Interrupt::SerialIn).unwrap();
            }

            std::thread::sleep(std::time::Duration::from_millis(1));
        }
    }

    pub fn new(uart_base: PhysAddr, state: Arc<SharedState>, mem_handle: MemoryBank) -> Self {
        let (read_tx, read_rx) = std::sync::mpsc::channel();
        let (write_tx, write_rx) = std::sync::mpsc::channel();
        let _ = std::thread::spawn(move || {
            Self::uart_io_thread(mem_handle, state, uart_base, read_tx, write_rx)
        });

        Self {
            visible: false,
            write_tx,
            read_rx,
            read_buff: Vec::new(),
            write_buff: String::new(),
        }
    }

    pub fn update(&mut self) {
        while let Ok(byte) = self.read_rx.try_recv() {
            self.read_buff.push(byte);
        }
    }
}

impl Component for Serial {
    fn title(&self) -> &str {
        "Serial"
    }

    fn visible(&mut self) -> &mut bool {
        &mut self.visible
    }

    fn ui(&mut self, ui: &mut egui::Ui) {
        self.update();

        // Text field for input and a send button
        ui.text_edit_multiline(&mut self.write_buff);
        if ui.button("Send").clicked() {
            self.write_buff.push(0 as char);
            for byte in self.write_buff.bytes() {
                self.write_tx.send(byte).unwrap();
            }
            self.write_buff.clear();
        }

        // Display received data
        ui.label(String::from_utf8(self.read_buff.clone()).unwrap());
        if ui.button("Clear").clicked() {
            self.read_buff.clear();
        }
    }
}
