use std::any::Any;
use egui::{Ui, Vec2, WidgetText};
use egui_tiles::TileId;
use emulator_core::{memory::PhysAddr, EmulatorHandle};
use crate::ui::Action;
use crate::ui::panels::TilePane;

pub struct FrameBuffer {
    width: usize,
    height: usize,
    addr: PhysAddr,
    emulator: EmulatorHandle,
    pub buffer: Vec<u8>,

    texture: Option<egui::TextureHandle>,
}

impl FrameBuffer {
    pub fn new(width: u32, height: u32, addr: PhysAddr, emulator: EmulatorHandle) -> Self {
        let width = width as usize;
        let height = height as usize;
        Self {
            width,
            height,
            addr,
            emulator,
            buffer: vec![0; width * height * 4],
            texture: None,
        }
    }

    pub fn width(&self) -> usize {
        self.width
    }
    pub fn height(&self) -> usize {
        self.height
    }

    fn internal_read(&mut self) -> Vec<u8> {
        let byte_size = self.width * self.height * 4;
        self.emulator.mem_read(self.addr, byte_size as u32)
    }

    pub fn changed(&mut self) -> bool {
        let temp = self.internal_read();
        if temp != self.buffer {
            self.buffer = temp;
            return true;
        }
        false
    }

    pub fn read(&self) -> &[u8] {
        &self.buffer
    }

    /// Convert buffer to RGBA bytes for use with egui/wgpu textures
    pub fn as_rgba_bytes(&self) -> Vec<u8> {
        self.buffer
            .iter()
            .flat_map(|&pixel| pixel.to_le_bytes())
            .collect()
    }
}

impl TilePane for FrameBuffer {
    fn title(&self) -> WidgetText {
        "Framebuffer".into()
    }
    fn as_any_mut(&mut self) -> &mut dyn Any {
        self
    }


    fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option<Action> {
        let changed = self.changed();

        // get or create the texture
        let texture = self.texture.get_or_insert_with(|| {
            ui.load_texture(
                "framebuffer",
                egui::ColorImage {
                    size: [self.width, self.height],
                    source_size: Vec2::from([self.width as f32, self.height as f32]),
                    pixels: vec![egui::Color32::BLACK; self.width * self.height],
                },
                egui::TextureOptions::NEAREST,
            )
        });

        if changed {
            let pixels: Vec<egui::Color32> = self
                .buffer
                .as_chunks::<4>().0
                .iter()
                .map(|&[red, green, blue, alpha]| {
                    egui::Color32::from_rgba_premultiplied(red, green, blue, alpha)
                })
                .collect();

            texture.set(
                egui::ColorImage {
                    size: [self.width, self.height],
                    source_size: Vec2::from([self.width as f32, self.height as f32]),
                    pixels,
                },
                egui::TextureOptions::NEAREST,
            );
        }

        // scale to fit available space while preserving aspect ratio
        let available = ui.available_size();
        let aspect = self.width as f32 / self.height as f32;
        let size = if available.x / aspect <= available.y {
            egui::vec2(available.x, available.x / aspect)
        } else {
            egui::vec2(available.y * aspect, available.y)
        };

        ui.image(egui::load::SizedTexture::new(texture.id(), size));
        None
    }
}
