use std::fmt::{self, Display};

use iced::{
    Color, Element, Event, Font, Length, Padding, Pixels, Point, Rectangle, Size, Task, Vector,
    advanced::{
        Clipboard, Layout, Shell, Text, Widget,
        layout::{Limits, Node},
        overlay,
        renderer::{Quad, Style},
        text::Renderer,
        widget::{
            Operation, Tree,
            tree::{State, Tag},
        },
    },
    alignment::Vertical,
    keyboard::{Key, key::Named},
    mouse::{Button, Cursor, Interaction, ScrollDelta},
};
use nes_emu::Memory;

use nes_emu::{Mapped, PPU, PPUMMRegisters};

#[derive(Debug, Clone, Copy)]
struct Cpu<'a>(&'a Mapped);

impl Memory for Cpu<'_> {
    fn peek(&self, val: usize) -> Option<u8> {
        self.0.peek_cpu(val as u16)
    }

    fn len(&self) -> usize {
        0x10000
    }

    fn edit_ver(&self) -> usize {
        self.0.cpu_edit_ver()
    }
}

#[derive(Debug, Clone, Copy)]
struct Ppu<'a>(&'a Mapped, &'a PPU);

impl Memory for Ppu<'_> {
    fn peek(&self, val: usize) -> Option<u8> {
        match self.0.peek_ppu(val as u16) {
            Ok(v) => Some(v),
            Err(Some((PPUMMRegisters::Palette, off))) => Some(self.1.palette.ram(off as u8)),
            Err(None) => None,
        }
    }

    fn len(&self) -> usize {
        0x4000
    }

    fn edit_ver(&self) -> usize {
        self.0.ppu_edit_ver()
    }
}

#[derive(Debug, Clone, Copy)]
pub struct Oam<'a>(pub &'a PPU);

impl Memory for Oam<'_> {
    fn peek(&self, val: usize) -> Option<u8> {
        Some(self.0.peek_oam(val as u8))
    }

    fn len(&self) -> usize {
        0x100
    }

    fn edit_ver(&self) -> usize {
        self.0.oam_edit_ver()
    }
}

#[derive(Debug, Clone)]
pub enum HexEvent {}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HexView {}

// struct Val(Option<u8>);
// impl fmt::Display for Val {
//     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
//         if let Some(val) = self.0 {
//             write!(f, "{:02X}", val)
//         } else {
//             write!(f, "XX")
//         }
//     }
// }

impl HexView {
    // pub fn new() -> Self {
    //     Self {}
    // }

    // pub fn render_any<'a, M: Memory + Copy + 'a>(&self, mem: M) -> Element<'a, HexEvent> {
    //     struct Row<M: Memory>(usize, M);
    //     impl<'a, M: Memory + 'a> fmt::Display for Row<M> {
    //         fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    //             write!(f, "{}", Val(self.1.peek(self.0)))?;
    //             for i in 1..16 {
    //                 write!(f, " {}", Val(self.1.peek(self.0 + i)))?;
    //             }
    //             Ok(())
    //         }
    //     }
    //     column![
    //         text!("Hex view"),
    //         iced::widget::scrollable(lazy(mem.edit_ver(), move |_| column(
    //             [
    //                 text!("      | 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F")
    //                     .font(Font::MONOSPACE)
    //                     .into()
    //             ]
    //             .into_iter()
    //             .chain((0..mem.len()).step_by(16).map(|off| {
    //                 text!(" {off:04X} | {}", Row(off, mem))
    //                     .font(Font::MONOSPACE)
    //                     .into()
    //             }))
    //         )))
    //         .width(Fill),
    //     ]
    //     .width(Fill)
    //     .into()
    // }
    pub fn render_cpu<'a>(&self, mem: &'a Mapped) -> Element<'a, HexEvent> {
        // self.render_any(Cpu(mem))
        Element::new(
            hex_editor::<Cpu<'a>, HexEvent, iced::Renderer>(Cpu(mem)).font(Font::MONOSPACE),
        )
    }
    pub fn render_ppu<'a>(&self, mem: &'a Mapped, ppu: &'a PPU) -> Element<'a, HexEvent> {
        // self.render_any(Ppu(mem, ppu))
        Element::new(
            hex_editor::<Ppu<'a>, HexEvent, iced::Renderer>(Ppu(mem, ppu)).font(Font::MONOSPACE),
        )
    }
    pub fn render_oam<'a>(&self, ppu: &'a PPU) -> Element<'a, HexEvent> {
        Element::new(
            hex_editor::<Oam<'a>, HexEvent, iced::Renderer>(Oam(ppu)).font(Font::MONOSPACE),
        )
    }

    pub fn update(&mut self, _ev: HexEvent) -> Task<HexEvent> {
        todo!()
    }
}

// pub struct BufferSlice<'a>(pub &'a [u8]);

// impl Deref for BufferSlice<'_> {
//     type Target = [u8];

//     fn deref(&self) -> &Self::Target {
//         self.0
//     }
// }

pub trait Buffer {
    fn peek(&self, val: usize) -> Option<u8>;
    fn len(&self) -> usize;
}

// impl Buffer for BufferSlice<'_> {
//     fn peek(&self, val: usize) -> Option<u8> {
//         self.get(val).copied()
//     }

//     fn len(&self) -> usize {
//         self.iter().len()
//     }
// }
impl<M: Memory> Buffer for M {
    fn peek(&self, val: usize) -> Option<u8> {
        self.peek(val)
    }

    fn len(&self) -> usize {
        self.len()
    }
}

pub fn hex_editor<B: Buffer, M, R: Renderer>(raw: B) -> HexEditor<B, M, R> {
    HexEditor {
        val: raw,
        on_edit: None,
        font: None,
        font_size: None,
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct HexEdit {
    position: usize,
    old_value: u8,
    new_value: u8,
}

#[allow(dead_code)]
pub struct HexEditor<B, M, R: Renderer> {
    val: B,
    font_size: Option<Pixels>,
    font: Option<R::Font>,
    on_edit: Option<Box<dyn Fn(HexEdit) -> M>>,
}

impl<B, M, R> HexEditor<B, M, R>
where
    R: Renderer,
{
    pub fn font(mut self, font: R::Font) -> Self {
        self.font = Some(font);
        self
    }
}

impl<B: Buffer, M, R> HexEditor<B, M, R>
where
    R: Renderer,
{
    fn value_bounds(&self, renderer: &R, layout: Layout<'_>) -> Rectangle {
        let size = self.font_size.unwrap_or(renderer.default_size());
        layout
            .bounds()
            .shrink(Padding::new(0.).top(size.0 * 1.5).right(10.))
    }

    fn scrollbar_bounds(&self, renderer: &R, layout: Layout<'_>) -> Rectangle {
        let size = self.font_size.unwrap_or(renderer.default_size());
        layout.bounds().shrink(
            Padding::new(0.)
                .top(size.0 * 1.5)
                .left(layout.bounds().width - 10.),
        )
    }

    fn row_height(&self, renderer: &R) -> f32 {
        self.font_size.unwrap_or(renderer.default_size()).0 * LINE_HEIGHT
    }

    fn scroll_max(&self, renderer: &R, layout: Layout<'_>) -> f32 {
        let size = self.font_size.unwrap_or(renderer.default_size());
        let bounds = layout.bounds().shrink(Padding::new(0.).top(size.0 * 1.5));
        let rows = self.val.len().div_ceil(0x10);
        (self.row_height(renderer) * rows as f32 - bounds.height).max(0.)
    }

    fn scroll(
        &self,
        state: &mut HexEditorState,
        renderer: &R,
        layout: Layout<'_>,
        delta: &ScrollDelta,
    ) {
        // let size = self.font_size.unwrap_or(renderer.default_size());
        // let bounds = layout.bounds().shrink(Padding::new(0.).top(size.0 * 1.5));
        // let rows = self.val.len().div_ceil(0x10);
        let max = self.scroll_max(renderer, layout);
        // println!("max: {max}, rows: {rows}");
        match delta {
            ScrollDelta::Lines { y, .. } => {
                state.offset_y += y * self.row_height(renderer);
            }
            ScrollDelta::Pixels { y, .. } => {
                state.offset_y += y;
            }
        }
        if state.offset_y > 0. {
            state.offset_y = 0.;
        } else if state.offset_y < -max {
            state.offset_y = -max;
        }
    }
}

#[derive(Default)]
#[allow(dead_code)]
struct HexEditorState {
    offset_y: f32,
    selected: usize,
    dragging: bool,
}

const LINE_HEIGHT: f32 = 1.3;

impl<B: Buffer, M, T, R> Widget<M, T, R> for HexEditor<B, M, R>
where
    R: Renderer,
{
    fn tag(&self) -> Tag {
        Tag::of::<HexEditorState>()
    }

    fn state(&self) -> State {
        State::new(HexEditorState::default())
    }

    fn size(&self) -> Size<Length> {
        Size::new(Length::Fill, Length::Fill)
    }

    fn layout(&mut self, _tree: &mut Tree, _renderer: &R, limits: &Limits) -> Node {
        Node::new(limits.max())
    }

    fn draw(
        &self,
        tree: &Tree,
        renderer: &mut R,
        _theme: &T,
        _style: &Style,
        layout: Layout<'_>,
        _cursor: Cursor,
        viewport: &Rectangle,
    ) {
        let state: &HexEditorState = tree.state.downcast_ref();
        let size = self.font_size.unwrap_or(renderer.default_size());
        let font = self.font.unwrap_or(renderer.default_font());

        // let fonts = font_system();
        // let mut font_sys = fonts.write().unwrap();
        // let id = font_sys.raw().db().query(&Query {
        //     families: &[Family::Monospace],
        //     ..Query::default()
        // });
        // let f = font_sys.raw().get_font(id.unwrap(), iced::advanced::graphics::text::cosmic_text::Weight(1)).unwrap();
        // let width = f.metrics();
        // println!("Width: {width:?}");
        // iced::advanced::graphics::text::cosmic_text::Buffer::new(
        //     font_sys.raw(),
        //     Metrics::new(size.0, size.0),
        // ).layout_runs();
        // TODO: this needs to be computed from the font
        let col_width = 0.6 * size.0;

        let rows = self.val.len().div_ceil(0x10);
        let row_label_length = rows.ilog2().div_ceil(4) as usize;
        let mut draw = |v: &str, pos: Point| {
            renderer.fill_text(
                Text {
                    content: format!("{}", v),
                    bounds: viewport.size(),
                    size,
                    line_height: size.into(),
                    font,
                    align_x: iced::advanced::text::Alignment::Left,
                    align_y: Vertical::Top,
                    shaping: iced::advanced::text::Shaping::Basic,
                    wrapping: iced::advanced::text::Wrapping::None,
                    hint_factor: None,
                },
                pos,
                Color::WHITE,
                layout.bounds(),
            );
        };
        draw("0", Point::new(0., 0.));
        for i in 0..0x10 {
            draw(
                "0",
                layout.position()
                    + Vector::new((3 + row_label_length + 3 * i) as f32 * col_width, 0.),
            );
            draw(
                "0",
                layout.position()
                    + Vector::new((4 + row_label_length + 3 * i) as f32 * col_width, 0.),
            );
        }
        // renderer.fill_text(
        //     Text {
        //         content: format!(
        //             "{:width$}   00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F    {}",
        //             "",
        //             state.offset_y,
        //             width = row_label_length
        //         ),
        //         bounds: viewport.size(),
        //         size,
        //         line_height: size.into(),
        //         font,
        //         align_x: iced_core::text::Alignment::Left,
        //         align_y: iced_core::alignment::Vertical::Top,
        //         shaping: iced_core::text::Shaping::Basic,
        //         wrapping: iced_core::text::Wrapping::None,
        //         hint_factor: None,
        //     },
        //     layout.position(),
        //     Color::WHITE,
        //     layout.bounds(),
        // );
        struct HexV(Option<u8>);
        impl Display for HexV {
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                match self.0 {
                    Some(v) => write!(f, "{:02X}", v),
                    None => write!(f, "XX"),
                }
            }
        }
        // if rows > 0 {
        //     rows.ilog2()
        // }
        let bounds = self.value_bounds(renderer, layout);
        let mut pos = bounds.position() + Vector::new(0., state.offset_y);
        for row in 0..rows {
            if bounds.contains(pos) || bounds.contains(pos + Vector::new(0., size.0 * LINE_HEIGHT))
            {
                renderer.fill_text(
                    Text {
                        content: format!(
                            "{:0width$X}0: {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {}",
                            row,
                            HexV(self.val.peek(row * 0x10 + 0x0)),
                            HexV(self.val.peek(row * 0x10 + 0x1)),
                            HexV(self.val.peek(row * 0x10 + 0x2)),
                            HexV(self.val.peek(row * 0x10 + 0x3)),
                            HexV(self.val.peek(row * 0x10 + 0x4)),
                            HexV(self.val.peek(row * 0x10 + 0x5)),
                            HexV(self.val.peek(row * 0x10 + 0x6)),
                            HexV(self.val.peek(row * 0x10 + 0x7)),
                            HexV(self.val.peek(row * 0x10 + 0x8)),
                            HexV(self.val.peek(row * 0x10 + 0x9)),
                            HexV(self.val.peek(row * 0x10 + 0xA)),
                            HexV(self.val.peek(row * 0x10 + 0xB)),
                            HexV(self.val.peek(row * 0x10 + 0xC)),
                            HexV(self.val.peek(row * 0x10 + 0xD)),
                            HexV(self.val.peek(row * 0x10 + 0xE)),
                            HexV(self.val.peek(row * 0x10 + 0xF)),
                            width = row_label_length,
                        ),
                        bounds: viewport.size(),
                        size,
                        line_height: size.into(),
                        font,
                        align_x: iced::advanced::text::Alignment::Left,
                        align_y: Vertical::Top,
                        shaping: iced::advanced::text::Shaping::Basic,
                        wrapping: iced::advanced::text::Wrapping::None,
                        hint_factor: None,
                    },
                    pos,
                    Color::WHITE,
                    bounds,
                );
            }
            pos += Vector::new(0., size.0 * LINE_HEIGHT);
        }

        let scrollbar = self.scrollbar_bounds(renderer, layout);
        renderer.fill_quad(
            Quad {
                bounds: scrollbar,
                ..Quad::default()
            },
            Color::BLACK,
        );
        let pos = state.offset_y / self.scroll_max(renderer, layout);
        renderer.fill_quad(
            Quad {
                bounds: Rectangle::new(
                    Point::new(scrollbar.x, scrollbar.y - pos * (scrollbar.height - 20.)),
                    Size::new(10., 20.),
                ),
                ..Quad::default()
            },
            Color::WHITE,
        );
    }

    fn operate(
        &mut self,
        _tree: &mut Tree,
        _layout: Layout<'_>,
        _renderer: &R,
        _operation: &mut dyn Operation,
    ) {
    }

    fn update(
        &mut self,
        tree: &mut Tree,
        event: &Event,
        layout: Layout<'_>,
        cursor: Cursor,
        renderer: &R,
        _clipboard: &mut dyn Clipboard,
        shell: &mut Shell<'_, M>,
        _viewport: &Rectangle,
    ) {
        // if !matches!(event, Event::Window(_)) {
        //     println!("Event: {:#?}", event);
        // }
        match event {
            Event::Keyboard(iced::keyboard::Event::KeyPressed {
                key: Key::Named(Named::PageUp),
                ..
            }) => {
                let state: &mut HexEditorState = tree.state.downcast_mut();
                self.scroll(
                    state,
                    renderer,
                    layout,
                    &ScrollDelta::Pixels {
                        x: 0.,
                        y: self.value_bounds(renderer, layout).height,
                    },
                );
                shell.request_redraw();
                shell.capture_event();
            }
            Event::Keyboard(iced::keyboard::Event::KeyPressed {
                key: Key::Named(Named::PageDown),
                ..
            }) => {
                let state: &mut HexEditorState = tree.state.downcast_mut();
                self.scroll(
                    state,
                    renderer,
                    layout,
                    &ScrollDelta::Pixels {
                        x: 0.,
                        y: -self.value_bounds(renderer, layout).height,
                    },
                );
                shell.request_redraw();
                shell.capture_event();
            }
            Event::Mouse(iced::mouse::Event::WheelScrolled { delta }) => {
                let state: &mut HexEditorState = tree.state.downcast_mut();
                let bounds = self.value_bounds(renderer, layout);
                if cursor.is_over(bounds) {
                    self.scroll(state, renderer, layout, delta);
                    shell.request_redraw();
                    shell.capture_event();
                }
            }
            Event::Mouse(iced::mouse::Event::ButtonPressed(Button::Left)) => {
                let state: &mut HexEditorState = tree.state.downcast_mut();
                let bounds = self.scrollbar_bounds(renderer, layout);
                if let Some(pos) = cursor.position_in(bounds) {
                    state.offset_y = -(pos.y / bounds.height * self.scroll_max(renderer, layout));
                    state.dragging = true;
                    shell.request_redraw();
                    shell.capture_event();
                }
            }
            Event::Mouse(iced::mouse::Event::ButtonReleased(Button::Left)) => {
                let state: &mut HexEditorState = tree.state.downcast_mut();
                state.dragging = false;
            }
            // Event::Mouse(iced::mouse::Event::CursorLeft) => {
            //     let state: &mut HexEditorState = tree.state.downcast_mut();
            //     state.dragging = false;
            // }
            Event::Mouse(iced::mouse::Event::CursorMoved { .. }) => {
                let state: &mut HexEditorState = tree.state.downcast_mut();
                if state.dragging {
                    let bounds = self.scrollbar_bounds(renderer, layout);
                    if let Some(pos) = cursor.position_in(bounds) {
                        state.offset_y =
                            -(pos.y / bounds.height * self.scroll_max(renderer, layout));
                        shell.request_redraw();
                        shell.capture_event();
                    }
                }
            }
            _ => (),
        }
    }

    fn mouse_interaction(
        &self,
        _tree: &Tree,
        _layout: Layout<'_>,
        _cursor: Cursor,
        _viewport: &Rectangle,
        _renderer: &R,
    ) -> Interaction {
        Interaction::None
    }

    fn overlay<'a>(
        &'a mut self,
        _tree: &'a mut Tree,
        _layout: Layout<'a>,
        _renderer: &R,
        _viewport: &Rectangle,
        _translation: Vector,
    ) -> Option<overlay::Element<'a, M, T, R>> {
        None
    }
}
