use crate::{
    screen::{Camera, RenderError, RenderInput, RenderOptions, Screen},
    text::{TextSystem, TextSystemData},
};
use winit::{
    event::{ElementState, KeyEvent, WindowEvent},
    keyboard::{Key, NamedKey},
    window::Window,
};

pub struct Document {
    cell_width: u32,
    cell_height: u32,
}

pub struct Project {
    screen: Screen,
    egui: egui::Context,
    egui_winit: egui_winit::State,
    internal_state: ProjectState,

    text: TextSystem,

    // internal controls
    show_debug: bool,
}

pub struct ProjectState {
    pub camera: Camera,
    pub render_options: RenderOptions,
    pub document: Document,
}

impl Project {
    pub async fn new(window: Window) -> color_eyre::Result<Self> {
        let camera = Camera::default();
        let render_options = RenderOptions::default();
        // The *only* time we need to manually do this, all others should use extract_view!
        let egui = egui::Context::default();
        let egui_winit =
            egui_winit::State::new(egui.clone(), egui::ViewportId::ROOT, &window, None, None);
        let screen = Screen::new(window).await?;
        let document = Document {
            cell_width: 100,
            cell_height: 100,
        };

        Ok(Self {
            egui,
            egui_winit,
            screen,

            // systems
            text: TextSystem::new()?,

            show_debug: false,
            internal_state: ProjectState {
                camera,
                render_options,
                document,
            },
        })
    }

    pub async fn create_surface(&mut self) -> color_eyre::Result<()> {
        self.screen.create_surface().await
    }

    pub fn screen(&self) -> &Screen {
        &self.screen
    }

    pub fn screen_mut(&mut self) -> &mut Screen {
        &mut self.screen
    }

    /// Runs egui code
    pub fn gui_egui(&mut self) -> (egui::FullOutput, Vec<egui::ClippedPrimitive>) {
        egui_winit::update_viewport_info(
            self.egui_winit
                .egui_input_mut()
                .viewports
                .get_mut(&egui::ViewportId::ROOT)
                .unwrap(),
            &self.egui,
            self.screen.window(),
        );
        let input = self.egui_winit.take_egui_input(self.screen.window());

        let full_output = self.egui.run(input, |ctx| {
            egui::Window::new("Debug Tools")
                .open(&mut self.show_debug)
                .show(ctx, |ui| {
                    ui.horizontal(|ui| {
                        let mut vsync = self.screen.is_vsync();
                        let text = if vsync { "yes" } else { "no" };
                        ui.label("VSync");
                        ui.toggle_value(&mut vsync, text);
                        self.screen.vsync(vsync);
                    });
                    ui.add(
                        egui::Slider::new(&mut self.internal_state.render_options.gamma, 0.0..=2.0)
                            .text("Gamma"),
                    );
                });
        });

        self.egui_winit
            .handle_platform_output(self.screen.window(), full_output.platform_output.clone());

        let prims = self
            .egui
            .tessellate(full_output.shapes.clone(), full_output.pixels_per_point);

        (full_output, prims)
    }

    /// Returns true if the event was handled
    pub fn input(&mut self, event: &WindowEvent) -> bool {
        #[cfg(debug_assertions)]
        if matches!(
            event,
            WindowEvent::KeyboardInput {
                event: KeyEvent {
                    logical_key: Key::Named(NamedKey::F1),
                    state: ElementState::Pressed,
                    repeat: false,
                    ..
                },
                ..
            }
        ) {
            self.show_debug = !self.show_debug;
            return true;
        }

        false
    }

    pub fn update(&mut self, _dt: std::time::Duration) {
        puffin::profile_function!();

        // Run any in-progress tasks from the async executor
        // self.executor.run_until_stall(&mut self.internal_state);
    }

    pub fn render(&mut self) -> Result<(), RenderError> {
        puffin::profile_function!();

        // Any resources only used for the rendering process go here
        let (egui_output, paint_jobs) = self.gui_egui();
        let text_data = TextSystemData::extract();

        self.text.prepare(&text_data);

        let camera_text_data_info: Vec<_> =
            self.text.produce_camera(&text_data).into_iter().collect();
        let camera_text_data = camera_text_data_info
            .iter()
            .map(|(buf, mapper)| mapper.produce(buf))
            .collect();

        let world_text_data_info: Vec<_> =
            self.text.produce_world(&text_data).into_iter().collect();
        let world_text_data = world_text_data_info
            .iter()
            .map(|(buf, mapper)| mapper.produce(buf))
            .collect();

        // Build the renderer input
        let input = RenderInput {
            paint_jobs: &paint_jobs,
            textures_delta: &egui_output.textures_delta,
            font_system: &mut self.text.fonts,
            swash_cache: &mut self.text.swash,
            camera: &mut self.internal_state.camera,
            render_options: &self.internal_state.render_options,
            camera_text_data,
            world_text_data,
            document: &self.internal_state.document,
        };

        self.screen.render(input)
    }

    // methods here might query the world to produce
    // rendering data ready for Screen to use
}