use std::rc::Rc;

use iced::{
    Element,
    Length::{self, Fill},
    Point, Renderer, Size,
    advanced::{
        Widget,
        layout::Node,
        widget::{
            Tree,
            tree::{State, Tag},
        },
    },
    mouse,
    widget::{
        self, Canvas, Text, button,
        canvas::{Frame, Program},
        checkbox, column,
        container::bordered_box,
        hex_input, image, number_input, row,
        rule::horizontal,
        scrollable, text,
    },
};
use nes_emu::{Break, CycleResult, Mapped, NES, PPU};

#[derive(Debug, Clone)]
pub struct DebuggerState {
    ppu_cycles: usize,
    cpu_cycles: usize,
    instructions: usize,
    scan_lines: usize,
    to_scan_line: usize,
    frames: usize,
    breakpoint: usize,
}

#[derive(Debug, Clone)]
pub enum DebuggerMessage {
    // Run,
    // Pause,
    SetPPUCycles(usize),
    RunPPUCycles,
    SetCPUCycles(usize),
    RunCPUCycles,
    SetInstructions(usize),
    RunInstructions,
    SetScanLines(usize),
    RunScanLines,
    SetToScanLine(usize),
    RunToScanLine,
    SetFrames(usize),
    RunFrames,
    SetBreakpoint(usize),
    RunBreakpoint,
}

pub fn hex16<'a, Theme, Renderer>(val: u16) -> Text<'a, Theme, Renderer>
where
    Theme: text::Catalog + 'a,
    Renderer: iced::advanced::text::Renderer,
{
    text(format!("{val:04X}"))
}
pub fn hex8<'a, Theme, Renderer>(val: u8) -> Text<'a, Theme, Renderer>
where
    Theme: text::Catalog + 'a,
    Renderer: iced::advanced::text::Renderer,
{
    text(format!("{val:02X}"))
}
// pub fn bin8<'a, Theme, Renderer>(val: u8) -> Text<'a, Theme, Renderer>
// where
//     Theme: text::Catalog + 'a,
//     Renderer: iced::advanced::text::Renderer,
// {
//     text(format!("{val:08b}"))
// }
pub fn bin32<'a, Theme, Renderer>(val: u32) -> Text<'a, Theme, Renderer>
where
    Theme: text::Catalog + 'a,
    Renderer: iced::advanced::text::Renderer,
{
    text(format!("{val:032b}"))
}

type Type = NES;

impl DebuggerState {
    pub fn new() -> Self {
        Self {
            ppu_cycles: 1,
            cpu_cycles: 1,
            instructions: 1,
            scan_lines: 1,
            to_scan_line: 1,
            frames: 1,
            breakpoint: 0xEA5A,
            // cpu_cycles: 1,
        }
    }

    pub fn view<'s>(&'s self, nes: &'s NES) -> Element<'s, DebuggerMessage> {
        column![
            iced::widget::rule::horizontal(2.),
            row![column![
                text("Status"),
                row![
                    labelled("A:", text(format!("{:02X}", nes.cpu().a))),
                    labelled("X:", text(format!("{:02X}", nes.cpu().x))),
                    labelled("Y:", text(format!("{:02X}", nes.cpu().y))),
                    labelled("PC:", text(format!("{:04X}", nes.cpu().pc))),
                    labelled("Cycle:", text(format!("{}", nes.cpu_cycle()))),
                    labelled("SP:", text(format!("{:02X}", nes.cpu().sp))),
                ]
                .spacing(5.),
                row![
                    labelled("P:", text(format!("{:02X}", nes.cpu().status.0))),
                    labelled_box("Carry", nes.cpu().status.carry()),
                    labelled_box("Zero", nes.cpu().status.zero()),
                    labelled_box("Interrupt", nes.cpu().status.interrupt_disable()),
                    labelled_box("--", false),
                    labelled_box("--", false),
                    labelled_box("Overflow", nes.cpu().status.overflow()),
                    labelled_box("Negative", nes.cpu().status.negative()),
                ]
                .spacing(5.),
                row![
                    text("IRQs:"),
                    labelled_box("NMI", nes.peek_nmi()),
                    labelled_box("Cart", false),
                    labelled_box("Frame Counter", nes.apu().peek_irq()),
                    labelled_box("DMC", false),
                ]
                .spacing(5.),
                row![
                    column![
                        labelled("Cycle", text(nes.ppu().pixel)),
                        labelled("Scanline", text(if nes.ppu().scanline == 261 { -1 } else { nes.ppu().scanline as isize })),
                        labelled("PPU Cycle", text(nes.ppu().cycle)),
                        labelled("Frame", text(nes.ppu().frame_count)),
                        labelled("V:", hex16(nes.ppu().background.v)),
                        labelled("T:", hex16(nes.ppu().background.t)),
                        labelled("X:", hex8(nes.ppu().background.x)),
                        text(""),
                        labelled("NT:", hex8(nes.ppu().background.cur_nametable)),
                        labelled2(
                            "AT:",
                            hex8(nes.ppu().background.next_attr),
                            hex16(
                                0x23C0
                                    | (nes.ppu().background.v & 0x0C00)
                                    | ((nes.ppu().background.v >> 4) & 0x38)
                                    | ((nes.ppu().background.v >> 2) & 0x07)
                            )
                        ),
                        labelled("AT:", hex8(nes.ppu().background.next_attr)),
                        labelled("HI:", bin32(nes.ppu().background.cur_shift_high)),
                        labelled("LO:", bin32(nes.ppu().background.cur_shift_low)),
                    ],
                    column![
                        labelled_box("Sprite 0 Hit", nes.ppu().sprite_zero_hit),
                        labelled_box("Sprite 0 Overflow", nes.ppu().oam.overflow),
                        labelled_box("Vertical Blank", nes.ppu().vblank),
                        labelled_box("Write Toggle", nes.ppu().background.w),
                        text(""),
                        labelled_box("Large Sprites", false),
                        labelled_box("Vertical Write", nes.ppu().background.vram_column),
                        labelled_box("NMI on VBlank", nes.ppu().nmi_on_vblank()),
                        labelled_box("BG at $1000", nes.ppu().background.second_pattern),
                        labelled_box("Sprites at $1000", false),
                    ],
                    column![
                        labelled_box("Even frame", nes.ppu().even),
                        labelled_box("BG Enabled", nes.ppu().mask.enable_background),
                        labelled_box("Sprites Enabled", nes.ppu().mask.enable_sprites),
                        labelled_box("BG Mask", nes.ppu().mask.background_on_left_edge),
                        labelled_box("Sprites Mask", nes.ppu().mask.sprites_on_left_edge),
                        labelled_box("Grayscale", nes.ppu().mask.grayscale),
                        labelled_box("Intensify Red", nes.ppu().mask.em_red),
                        labelled_box("Intensify Green", nes.ppu().mask.em_green),
                        labelled_box("Intensify Blue", nes.ppu().mask.em_blue),
                    ],
                    column![
                        run_type(
                            "PPU Cycles:",
                            self.ppu_cycles,
                            DebuggerMessage::SetPPUCycles,
                            DebuggerMessage::RunPPUCycles
                        ),
                        run_type(
                            "CPU Cycles:",
                            self.cpu_cycles,
                            DebuggerMessage::SetCPUCycles,
                            DebuggerMessage::RunCPUCycles
                        ),
                        run_type(
                            "Instructions:",
                            self.instructions,
                            DebuggerMessage::SetInstructions,
                            DebuggerMessage::RunInstructions
                        ),
                        run_type(
                            "Scanlines:",
                            self.scan_lines,
                            DebuggerMessage::SetScanLines,
                            DebuggerMessage::RunScanLines
                        ),
                        run_type(
                            "To Scanline:",
                            self.to_scan_line,
                            DebuggerMessage::SetToScanLine,
                            DebuggerMessage::RunToScanLine
                        ),
                        run_type(
                            "Frames:",
                            self.frames,
                            DebuggerMessage::SetFrames,
                            DebuggerMessage::RunFrames
                        ),
                        run_type_hex(
                            "To Address:",
                            self.breakpoint,
                            DebuggerMessage::SetBreakpoint,
                            DebuggerMessage::RunBreakpoint
                        ),
                    ],
                ]
                .spacing(5.),
                horizontal(2),
                scrollable(
                    column(
                        nes.debug_log()
                            .history()
                            .into_iter()
                            .rev()
                            .map(|s| text(s).line_height(0.9).into())
                    )
                    .spacing(0)
                )
                .width(Fill),
            ],],
        ]
        .width(Fill)
        .height(Fill)
        .into()
    }

    fn run_n_clock_cycles(nes: &mut Type, n: usize) {
        for _ in 0..n {
            if nes.run_one_clock_cycle(&Break::default()).dbg_int || nes.halted() {
                break;
            }
        }
    }
    fn run_until(
        nes: &mut NES,
        br: &Break,
        mut f: impl FnMut(CycleResult, &NES) -> bool,
        // mut count: usize,
    ) {
        // Always run at least 1 cycle
        let mut res = nes.run_one_clock_cycle(&Break::default());
        while !nes.halted() && !res.dbg_int && !f(res, nes) {
            // if res.dbg_int || f(res, nes) {
            //     count -= 1;
            //     if count <= 0 {
            //         break;
            //     }
            // }
            // if nes.halted() {
            //     break;
            // }
            res = nes.run_one_clock_cycle(br);
        }
    }
    fn run_until_n(
        nes: &mut NES,
        br: &Break,
        mut f: impl FnMut(CycleResult, &NES) -> bool,
        mut count: usize,
    ) {
        while count > 0 {
            Self::run_until(nes, br, &mut f);
            count -= 1;
        }
    }

    pub fn update(&mut self, message: DebuggerMessage, nes: &mut NES) {
        match message {
            DebuggerMessage::SetPPUCycles(n) => self.ppu_cycles = n,
            DebuggerMessage::SetCPUCycles(n) => self.cpu_cycles = n,
            DebuggerMessage::SetInstructions(n) => self.instructions = n,
            DebuggerMessage::SetScanLines(n) => self.scan_lines = n,
            DebuggerMessage::SetToScanLine(n) => self.to_scan_line = n.min(261), // Max scanline is 261
            DebuggerMessage::SetFrames(n) => self.frames = n,
            DebuggerMessage::SetBreakpoint(n) => self.breakpoint = n,
            DebuggerMessage::RunPPUCycles => Self::run_n_clock_cycles(nes, self.ppu_cycles),
            DebuggerMessage::RunCPUCycles => Self::run_n_clock_cycles(nes, self.cpu_cycles * 3),
            // DebuggerMessage::RunInstructions => Self::run_until_n(
            //     nes,
            //     &Break {
            //         cpu_exec: true,
            //         ..Break::default()
            //     },
            //     |_, _| false,
            //     self.instructions,
            // ),
            DebuggerMessage::RunInstructions => Self::run_until_n(
                nes,
                &Break {
                    ..Break::default()
                },
                |res, _| res.cpu_exec,
                self.instructions,
            ),
            DebuggerMessage::RunScanLines => Self::run_n_clock_cycles(nes, self.scan_lines * 341),
            DebuggerMessage::RunToScanLine => Self::run_until(
                nes,
                &Break {
                    ppu_scanline: true,
                    ..Break::default()
                },
                |_, n| n.ppu().scanline == self.to_scan_line,
            ),
            DebuggerMessage::RunFrames => Self::run_n_clock_cycles(nes, self.frames * 341 * 262),
            DebuggerMessage::RunBreakpoint => Self::run_until(
                nes,
                &Break {
                    break_points: vec![self.breakpoint as u16],
                    ..Break::default()
                },
                |_, nes| nes.cpu().pc as usize == self.breakpoint,
            ),
            // DebuggerMessage::Run => {
            //     Self::run_until(nes, &Break { ..Break::default() }, |_, nes| nes.halted())
            // }
            // DebuggerMessage::Pause => todo!(),
        }
    }
}

fn run_type<'a, Message: Clone + 'a>(
    label: &'a str,
    val: usize,
    update: impl Fn(usize) -> Message + 'a,
    run: Message,
) -> Element<'a, Message> {
    row![
        widget::container(text(label)).padding(2.),
        widget::container(number_input(val).on_input(update).on_submit(run.clone())).padding(2.),
        widget::container(button(image("./images/ic_fluent_play_24_filled.png")).on_press(run))
            .padding(2.),
    ]
    .spacing(1.)
    .into()
}
fn run_type_hex<'a, Message: Clone + 'a>(
    label: &'a str,
    val: usize,
    update: impl Fn(usize) -> Message + 'a,
    run: Message,
) -> Element<'a, Message> {
    row![
        widget::container(text(label)).padding(2.),
        widget::container(hex_input(val).on_input(update).on_submit(run.clone())).padding(2.),
        widget::container(button(image("./images/ic_fluent_play_24_filled.png")).on_press(run))
            .padding(2.),
    ]
    .spacing(1.)
    .into()
}

pub fn labelled<'a, Message: 'a>(
    label: &'a str,
    content: impl Into<Element<'a, Message>>,
) -> Element<'a, Message> {
    row![
        widget::container(text(label)).padding(2.),
        widget::container(content).style(bordered_box).padding(2.),
    ]
    .spacing(1.)
    .into()
}

pub fn labelled2<'a, Message: 'a>(
    label: &'a str,
    content: impl Into<Element<'a, Message>>,
    content2: impl Into<Element<'a, Message>>,
) -> Element<'a, Message> {
    row![
        widget::container(text(label)).padding(2.),
        widget::container(content).style(bordered_box).padding(2.),
        widget::container(content2).style(bordered_box).padding(2.),
    ]
    .spacing(1.)
    .into()
}

pub fn labelled_box<'a, Message: 'a>(label: &'a str, value: bool) -> Element<'a, Message> {
    row![checkbox(value), widget::container(text(label)),]
        .spacing(1.)
        .into()
}

#[derive(Clone, Copy)]
pub enum DbgImage<'a> {
    NameTable(&'a Mapped, &'a PPU),
    PatternTable(&'a Mapped, &'a PPU),
    Palette(&'a PPU),
}

impl<Message, T> Program<Message, T> for DbgImage<'_> {
    type State = ();

    fn draw(
        &self,
        _state: &Self::State,
        renderer: &Renderer,
        _theme: &T,
        _bounds: iced::Rectangle,
        _cursor: mouse::Cursor,
    ) -> Vec<iced::widget::canvas::Geometry<Renderer>> {
        // const SIZE: f32 = 2.;
        let mut name_table_frame =
            Frame::new(renderer, Size::new(256. * 4. + 260. * 2., 256. * 4.));
        name_table_frame.scale(2.);
        // println!("Position: {:?}", cursor.position());
        match self {
            DbgImage::NameTable(mem, ppu) => ppu.render_name_table(mem, &mut name_table_frame),
            DbgImage::PatternTable(mem, ppu) => {
                ppu.render_pattern_tables(mem, &mut name_table_frame)
            }
            DbgImage::Palette(ppu) => ppu.render_palette(&mut name_table_frame),
        };
        vec![name_table_frame.into_geometry()]
    }
}

impl DbgImage<'_> {
    fn width(&self) -> Length {
        match self {
            DbgImage::NameTable(_, _) => Length::Fixed(512. * 2.),
            DbgImage::PatternTable(_, _) => Length::Fixed(16. * 8. * 2.),
            DbgImage::Palette(_) => Length::Fixed(40. * 2.),
        }
    }
    fn height(&self) -> Length {
        match self {
            DbgImage::NameTable(_, _) => Length::Fixed(512. * 2.),
            DbgImage::PatternTable(_, _) => Length::Fixed(16. * 8. * 2. * 2.),
            DbgImage::Palette(_) => Length::Fixed(80. * 2.),
        }
    }
    fn help(&self, cursor: Point) -> Option<String> {
        match self {
            DbgImage::NameTable(mem, ppu) => ppu.name_cursor_info(mem, cursor),
            DbgImage::PatternTable(_, ppu) => ppu.pattern_cursor_info(cursor),
            DbgImage::Palette(ppu) => ppu.palette_cursor_info(cursor),
        }
    }
}

struct DbgImageSetup<'a, M, T: text::Catalog> {
    dbg: DbgImage<'a>,
    image: Canvas<DbgImage<'a>, M, T>,
    text: Text<'a, T>,
    padding: f32,
    drawn: bool,
    // image: DbgImage<'a>,
    // text: te,
}

// pub trait Container<Message, Theme, Renderer> {
//     // Not ideal
//     fn children(&self) -> &[&dyn Widget<Message, Theme, Renderer>];
// }

impl<'s, Message, Theme> Widget<Message, Theme, Renderer> for DbgImageSetup<'s, Message, Theme>
where
    Theme: text::Catalog + 's,
{
    fn size(&self) -> Size<iced::Length> {
        // self.
        Size::new(Fill, Fill)
    }

    fn layout(
        &mut self,
        tree: &mut Tree,
        renderer: &Renderer,
        limits: &iced::advanced::layout::Limits,
    ) -> Node {
        let img_node = self.image.layout(&mut tree.children[0], renderer, limits);
        let txt_node = Widget::<Message, Theme, _>::layout(
            &mut self.text,
            &mut tree.children[1],
            renderer,
            &limits.shrink(Size::new(
                img_node.size().width + self.padding * 2.,
                self.padding * 2.,
            )),
        )
        .move_to(Point::new(
            img_node.size().width + self.padding,
            self.padding,
        ));
        Node::with_children(limits.max(), vec![img_node, txt_node])
    }

    fn draw(
        &self,
        tree: &Tree,
        renderer: &mut Renderer,
        theme: &Theme,
        style: &iced::advanced::renderer::Style,
        layout: iced::advanced::Layout<'_>,
        cursor: iced::advanced::mouse::Cursor,
        viewport: &iced::Rectangle,
    ) {
        self.image.draw(
            &tree.children[0],
            renderer,
            theme,
            style,
            layout.child(0),
            cursor,
            viewport,
        );
        Widget::<Message, Theme, _>::draw(
            &self.text,
            &tree.children[1],
            renderer,
            theme,
            style,
            layout.child(1),
            cursor,
            viewport,
        )
    }

    fn tag(&self) -> Tag {
        Tag::of::<Rc<String>>()
    }

    fn state(&self) -> State {
        State::new(Rc::new(String::new()))
    }

    fn children(&self) -> Vec<Tree> {
        vec![
            Tree::new(&self.image as &dyn Widget<Message, Theme, Renderer>),
            Tree::new(&self.text as &dyn Widget<Message, Theme, Renderer>),
        ]
    }

    fn diff(&self, tree: &mut Tree) {
        tree.diff_children(&[
            &self.image as &dyn Widget<Message, Theme, Renderer>,
            &self.text as &dyn Widget<Message, Theme, Renderer>,
        ]);
    }

    fn operate(
        &mut self,
        tree: &mut Tree,
        layout: iced::advanced::Layout<'_>,
        renderer: &Renderer,
        operation: &mut dyn iced::advanced::widget::Operation,
    ) {
        operation.container(None, layout.bounds());
        operation.traverse(&mut |op| {
            self.image
                .operate(&mut tree.children[0], layout.child(0), renderer, op);
            Widget::<Message, Theme, _>::operate(
                &mut self.text,
                &mut tree.children[1],
                layout.child(1),
                renderer,
                op,
            );
        });
    }

    fn update(
        &mut self,
        tree: &mut Tree,
        event: &iced::Event,
        layout: iced::advanced::Layout<'_>,
        cursor: iced::advanced::mouse::Cursor,
        renderer: &Renderer,
        clipboard: &mut dyn iced::advanced::Clipboard,
        shell: &mut iced::advanced::Shell<'_, Message>,
        viewport: &iced::Rectangle,
    ) {
        self.image.update(
            &mut tree.children[0],
            event,
            layout.child(0),
            cursor,
            renderer,
            clipboard,
            shell,
            viewport,
        );
        if matches!(event, iced::Event::Mouse(mouse::Event::CursorMoved { .. })) || !self.drawn {
            if let Some(help) = cursor
                .position_in(layout.child(0).bounds())
                .and_then(|pos| self.dbg.help(Point::new(pos.x / 2., pos.y / 2.)))
            {
                self.text = text(help);
                shell.invalidate_layout();
                shell.request_redraw();
            }
            self.drawn = true;
        }
        Widget::<Message, Theme, _>::update(
            &mut self.text,
            &mut tree.children[1],
            event,
            layout.child(1),
            cursor,
            renderer,
            clipboard,
            shell,
            viewport,
        );
    }

    fn mouse_interaction(
        &self,
        _tree: &iced::advanced::widget::Tree,
        _layout: iced::advanced::Layout<'_>,
        _cursor: iced::advanced::mouse::Cursor,
        _viewport: &iced::Rectangle,
        _renderer: &Renderer,
    ) -> iced::advanced::mouse::Interaction {
        iced::advanced::mouse::Interaction::default()
    }

    fn overlay<'a>(
        &'a mut self,
        _tree: &'a mut iced::advanced::widget::Tree,
        _layout: iced::advanced::Layout<'a>,
        _renderer: &Renderer,
        _viewport: &iced::Rectangle,
        _translation: iced::Vector,
    ) -> Option<iced::advanced::overlay::Element<'a, Message, Theme, Renderer>> {
        None
    }
}

pub fn dbg_image<'a, Message: 'a>(img: DbgImage<'a>) -> Element<'a, Message> {
    Element::new(DbgImageSetup {
        dbg: img,
        image: Canvas::new(img).width(img.width()).height(img.height()),
        text: text(""),
        padding: 10.,
        drawn: false,
    })
}
