use std::{
    cell::RefCell,
    collections::{HashMap, HashSet},
    rc::Rc,
};

pub mod gui;

use clay_layout::render_commands::RenderCommandConfig;
use gui::{
    GUI,
    document::{
        State,
        line::{Line, LineSegment, Theme},
    },
};
use libopenbirch::node::{add::Add, constant::Constant, divide::Divide, multiply::Multiply};
use raylib::{
    RaylibHandle, RaylibThread,
    color::Color,
    ffi::{LoadCodepoints, MouseButton, TextLength, TextureFilter, Vector2},
    prelude::{Font, RaylibDraw, RaylibDrawHandle},
    text::RaylibFont,
    texture::Texture2D,
};

struct FontStore {
    font: String,
    fonts: HashMap<i32, Rc<Font>>,
    fonts_to_be_loaded: HashSet<i32>,
}

static CHAR_SET: &'static str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWYZ1234567890(){}[]./,:;'\"?><|\\=+-_*&^%$#@!~`·×≤≥±∞≈∝≠∅∈∉∩∪⊂⊃⊆⊇¬∧∨∃∀⇒⇔→↔↑↓ℵ \t\n\r";

impl FontStore {
    // const SIZE_PARAGRAPH: f32 = 20.;
    // const SIZE_HEADER: f32 = 30.;
    // const MOD_SUBTEXT: f32 = 0.5;

    pub fn new(name: impl Into<String>) -> Self {
        Self {
            fonts: HashMap::new(),
            font: name.into(),
            fonts_to_be_loaded: HashSet::new(),
        }
    }

    pub fn get(&mut self, size: i32) -> Option<Rc<Font>> {
        let size = size.max(1);
        if let Some(font) = self.fonts.get(&size) {
            Some(font.clone())
        } else {
            self.fonts_to_be_loaded.insert(size);
            None
        }
    }

    // pub fn get_no_load(&self, size: i32) -> Option<Rc<Font>> {
    //     if let Some(font) = self.fonts.get(&size) {
    //         Some(font.clone())
    //     } else {
    //         None
    //     }
    // }

    pub fn load_fonts(
        &mut self,
        handle: &mut RaylibHandle,
        thread: &RaylibThread,
    ) -> Result<bool, String> {
        let invalidate = self.fonts_to_be_loaded.len() > 0;
        if invalidate {
            for size in self.fonts_to_be_loaded.clone() {
                assert!(size != 0, "HOW IS THIS ZERO?");
                println!("Loading {} in {size}px", self.font);
                let font = Rc::new(handle.load_font_ex(&thread, &self.font, size, None)?);
                self.fonts.insert(size, font.clone());
            }
            self.fonts_to_be_loaded.clear();
        }
        Ok(invalidate)
    }

    pub fn text(
        &mut self,
        d: &mut RaylibDrawHandle,
        text: &str,
        x: f32,
        y: f32,
        size: f32,
        letter_spacing: u16,
        color: Option<Color>,
    ) {
        // Program crashes at size=0.
        let size = if size == 0. { 0.1 } else { size };
        if let Some(font) = self.get(size as i32) {
            d.draw_text_ex(
                font.as_ref(),
                text,
                Vector2 { x, y },
                size,
                letter_spacing.into(),
                color.unwrap_or(Color::BLUE),
            );
        }
    }
}

#[inline]
fn clay2ray(c: clay_layout::color::Color) -> Color {
    Color {
        r: c.r as u8,
        g: c.g as u8,
        b: c.b as u8,
        a: c.a as u8,
    }
}

fn main() -> Result<(), String> {
    println!("Openbirch GUI");

    let (mut rl, thread) = raylib::init()
        .size(640, 480)
        .title("Openbirch GUI")
        // .msaa_4x()
        .resizable()
        .build();
    let mut clay = clay_layout::Clay::new((640., 480.).into());
    let font_store = Rc::new(RefCell::new(FontStore::new("adamina.ttf")));
    let font_store_math = Rc::new(RefCell::new(FontStore::new("xits-italic.ttf")));

    let mut gui = GUI::new();

    gui.document.add_line("This is a test line".to_string());

    let a = Constant::new(69.);
    let b = Constant::new(420.);
    let meth = Divide::new(a.clone().into(), b.into());
    let meth2 = Add::new(a.into(), meth.clone().into());
    let meth3 = Multiply::new(meth2.into(), meth.into());

    gui.document.add_line(Line {
        segments: vec![LineSegment::Math("".into(), Rc::new(meth3.into()), 16)],
    });

    let mut previous_left_button = false;
    let mut previous_right_button = false;

    // stupid fucking 'static closures
    let font_store_clone = font_store.clone();
    let font_store_math_clone = font_store_math.clone();

    clay.set_measure_text_function(move |text, config| {
        let mut text_size = clay_layout::math::Dimensions::new(0., 0.);

        let mut max_text_width: f32 = 0.;
        let mut line_text_width: f32 = 0.;

        let font_store = match config.font_id {
            0 => font_store_clone.clone(),
            1 => font_store_math_clone.clone(),
            _ => panic!("Unknown font {}", config.font_id),
        };

        let font = if let Some(font) = font_store.borrow_mut().get(config.font_size.into()) {
            font
        } else {
            // println!("Font not cached. Returning: {:?}", text_size);
            return text_size;
        };

        let scale_factor = config.font_size as f32 / font.baseSize as f32;

        let text_height = config.font_size as f32;

        for char in text.chars() {
            if char == '\n' {
                max_text_width = max_text_width.max(line_text_width);
                line_text_width = 0.;
                continue;
            }

            let idx = char as u32 - 32;
            line_text_width += if let Some(glyph) = font.chars().get(idx as usize) {
                if glyph.advanceX > 0 {
                    glyph.advanceX as f32
                } else {
                    font.get_glyph_atlas_rec(char).width + glyph.offsetX as f32
                }
            } else {
                0 as f32
            } + config.letter_spacing as f32;
        }

        max_text_width = max_text_width.max(line_text_width);

        text_size.width = max_text_width * scale_factor;
        text_size.height = text_height;

        text_size

        // unsafe {
        //     std::mem::transmute(font.measure_text(text, config.font_size as f32, config.letter_spacing as f32 + 1.))
        // }
    });

    rl.set_target_fps(60);
    rl.set_exit_key(None);

    let mut debug = false;

    let theme = Theme {
        text_color: (236, 239, 244).into(),
        text_font: 0,
        math_color: (129, 161, 193).into(),
        math_font: 1,
    };

    while !rl.window_should_close() {
        if rl.is_window_resized() {
            let size = (rl.get_screen_width() as f32, rl.get_screen_height() as f32);
            clay.layout_dimensions(size.into());
            println!("Resized window to {:?}", size);
        }

        let left_button = rl.is_mouse_button_down(MouseButton::MOUSE_BUTTON_LEFT);
        let right_button = rl.is_mouse_button_down(MouseButton::MOUSE_BUTTON_LEFT);

        {
            if font_store.borrow_mut().load_fonts(&mut rl, &thread)?
                || font_store_math.borrow_mut().load_fonts(&mut rl, &thread)?
            {
                clay.reset_measure_text_cache();
            }
        }

        if rl.is_key_down(raylib::ffi::KeyboardKey::KEY_RIGHT_ALT)
            && rl.is_key_pressed(raylib::ffi::KeyboardKey::KEY_D)
        {
            debug = !clay.debug_mode_enabled();
            clay.reset_measure_text_cache();
            clay.enable_debug_mode(debug);
            println!("Toggled debug mode to {debug}");
        }

        clay.set_debug_highlight((180, 142, 173, 150).into());

        clay.update_scroll_containers(
            false,
            unsafe { std::mem::transmute(rl.get_mouse_wheel_move_v()) },
            rl.get_frame_time(),
        );

        clay.pointer_state(
            unsafe { std::mem::transmute(rl.get_mouse_position()) },
            left_button,
        );

        let state = State {
            mouse_left_held: left_button,
            mouse_left_pressed: left_button && !previous_left_button,
            mouse_right_held: right_button,
            mouse_right_pressed: right_button && !previous_right_button,
            latest_char: rl.get_char_pressed(),
            latest_key: rl.get_key_pressed_number(),
            time: rl.get_time(),
        };

        let render_commands = gui.render(&clay, &state, &theme);

        let mut d = rl.begin_drawing(&thread);

        d.clear_background(clay2ray((46, 52, 64).into()));

        for command in render_commands {
            match command.config {
                RenderCommandConfig::Rectangle(rectangle) => {
                    // println!(
                    //     "RECT!!! {}: {:?}\n{:?}\n\n",
                    //     command.id, command.bounding_box, rectangle
                    // );
                    //
                    d.draw_rectangle_rounded(
                        raylib::ffi::Rectangle {
                            x: command.bounding_box.x,
                            y: command.bounding_box.y,
                            width: command.bounding_box.width,
                            height: command.bounding_box.height,
                        },
                        rectangle.corner_radii.top_left
                            / command.bounding_box.width.min(command.bounding_box.height),
                        8,
                        clay2ray(rectangle.color),
                    );
                }
                RenderCommandConfig::Text(text) => {
                    let font = match text.font_id {
                        0 => font_store.clone(),
                        1 => font_store_math.clone(),
                        _ => panic!("Unknown font {}", text.font_id),
                    };

                    font.borrow_mut().text(
                        &mut d,
                        text.text,
                        command.bounding_box.x,
                        command.bounding_box.y,
                        text.font_size as f32,
                        text.letter_spacing,
                        Some(clay2ray(text.color)),
                    );
                }
                RenderCommandConfig::ScissorStart() => unsafe {
                    raylib::ffi::BeginScissorMode(
                        command.bounding_box.x as i32,
                        command.bounding_box.y as i32,
                        command.bounding_box.width as i32,
                        command.bounding_box.height as i32,
                    );
                },
                RenderCommandConfig::ScissorEnd() => unsafe {
                    raylib::ffi::EndScissorMode();
                },
                RenderCommandConfig::Border(border) => {
                    // Left border
                    if border.width.left > 0 {
                        d.draw_rectangle(
                            command.bounding_box.x as i32,
                            (command.bounding_box.y + border.corner_radii.top_left) as i32,
                            border.width.left as i32,
                            (command.bounding_box.height
                                - border.corner_radii.top_left
                                - border.corner_radii.bottom_left)
                                as i32,
                            clay2ray(border.color),
                        );
                    }
                    // Right border
                    if border.width.right > 0 {
                        d.draw_rectangle(
                            (command.bounding_box.x + command.bounding_box.width
                                - border.width.right as f32) as i32,
                            (command.bounding_box.y + border.corner_radii.top_right) as i32,
                            border.width.right as i32,
                            (command.bounding_box.height
                                - border.corner_radii.top_right
                                - border.corner_radii.bottom_right)
                                as i32,
                            clay2ray(border.color),
                        );
                    }
                    // Top border
                    if border.width.top > 0 {
                        d.draw_rectangle(
                            (command.bounding_box.x + border.corner_radii.top_left) as i32,
                            (command.bounding_box.y) as i32,
                            (command.bounding_box.width
                                - border.corner_radii.top_left
                                - border.corner_radii.top_right) as i32,
                            (border.width.top) as i32,
                            clay2ray(border.color),
                        );
                    }
                    // Top border
                    if border.width.bottom > 0 {
                        d.draw_rectangle(
                            (command.bounding_box.x + border.corner_radii.bottom_left) as i32,
                            (command.bounding_box.y + command.bounding_box.height
                                - border.width.bottom as f32) as i32,
                            (command.bounding_box.width
                                - border.corner_radii.bottom_left
                                - border.corner_radii.bottom_right)
                                as i32,
                            border.width.bottom as i32,
                            clay2ray(border.color),
                        );
                    }
                    // Top left corner
                    if border.corner_radii.top_left > 0. {
                        d.draw_ring(
                            Vector2 {
                                x: command.bounding_box.x + border.corner_radii.top_left,
                                y: command.bounding_box.y + border.corner_radii.top_left,
                            },
                            border.corner_radii.top_left - border.width.top as f32,
                            border.corner_radii.top_left,
                            180.,
                            270.,
                            10,
                            clay2ray(border.color),
                        );
                    }
                    // Top right corner
                    if border.corner_radii.top_right > 0. {
                        d.draw_ring(
                            Vector2 {
                                x: command.bounding_box.x + command.bounding_box.width
                                    - border.corner_radii.top_right,
                                y: command.bounding_box.y + border.corner_radii.top_right,
                            },
                            border.corner_radii.top_right - border.width.top as f32,
                            border.corner_radii.top_right,
                            270.,
                            360.,
                            10,
                            clay2ray(border.color),
                        );
                    }
                    // Bottom left
                    if border.corner_radii.bottom_left > 0. {
                        d.draw_ring(
                            Vector2 {
                                x: command.bounding_box.x + border.corner_radii.bottom_left,
                                y: command.bounding_box.y + command.bounding_box.height
                                    - border.corner_radii.bottom_left,
                            },
                            border.corner_radii.bottom_left - border.width.top as f32,
                            border.corner_radii.bottom_left,
                            90.,
                            180.,
                            10,
                            clay2ray(border.color),
                        );
                    }
                    // Bottom right
                    if border.corner_radii.bottom_right > 0. {
                        d.draw_ring(
                            Vector2 {
                                x: command.bounding_box.x + command.bounding_box.width
                                    - border.corner_radii.bottom_right,
                                y: command.bounding_box.y + command.bounding_box.height
                                    - border.corner_radii.bottom_right,
                            },
                            border.corner_radii.bottom_right - border.width.bottom as f32,
                            border.corner_radii.bottom_right,
                            0.,
                            90.,
                            10,
                            clay2ray(border.color),
                        );
                    }
                }
                RenderCommandConfig::None() => {}
                RenderCommandConfig::Image(image) => {
                    let texture: &Texture2D = unsafe {
                        std::mem::transmute::<*const std::ffi::c_void, *const Texture2D>(image.data)
                            .as_ref()
                            .unwrap()
                    };
                    d.draw_texture_ex(
                        texture,
                        Vector2 {
                            x: command.bounding_box.x,
                            y: command.bounding_box.y,
                        },
                        0.,
                        command.bounding_box.width / image.dimensions.width,
                        clay2ray(image.background_color),
                    );
                }
                _ => panic!("Unimplemented {:#?}", command),
            }
        }

        previous_left_button = left_button;
        previous_right_button = right_button;
    }

    Ok(())
}
