use crate::{
    menu::main::MainMenuState,
    settings::get_fps,
    ui,
    window::{Event, EventLoop},
    Direction, GlobalState, PlayState, PlayStateResult,
};
use common_base::{no_guard_span, span, GuardlessSpan};
use std::{mem, time::Duration};
use tracing::debug;

pub fn run(mut global_state: GlobalState, event_loop: EventLoop) {
    // Set up the initial play state.
    let mut states: Vec<Box<dyn PlayState>> = vec![Box::new(MainMenuState::new(&mut global_state))];
    states.last_mut().map(|current_state| {
        current_state.enter(&mut global_state, Direction::Forwards);
        let current_state = current_state.name();
        debug!(?current_state, "Started game with state");
    });

    // Used to ignore every other `MainEventsCleared`
    // This is a workaround for a bug on macos in which mouse motion events are only
    // reported every other cycle of the event loop
    // See: https://github.com/rust-windowing/winit/issues/1418
    let mut polled_twice = false;

    let mut poll_span = None;
    let mut event_span = None;

    event_loop.run(move |event, _, control_flow| {
        // Continuously run loop since we handle sleeping
        *control_flow = winit::event_loop::ControlFlow::Poll;

        // Get events for the ui.
        if let Some(event) = ui::Event::try_from(&event, global_state.window.window()) {
            global_state.window.send_event(Event::Ui(event));
        }
        // iced ui events
        // TODO: no clone
        if let winit::event::Event::WindowEvent { event, .. } = &event {
            let window = &mut global_state.window;
            if let Some(event) =
                ui::ice::window_event(event, window.scale_factor(), window.modifiers())
            {
                window.send_event(Event::IcedUi(event));
            }
        }

        match event {
            winit::event::Event::NewEvents(_) => {
                event_span = Some(no_guard_span!("Process Events"));
            },
            winit::event::Event::MainEventsCleared => {
                event_span.take();
                poll_span.take();
                if polled_twice {
                    handle_main_events_cleared(&mut states, control_flow, &mut global_state);
                }
                poll_span = Some(no_guard_span!("Poll Winit"));
                polled_twice = !polled_twice;
            },
            winit::event::Event::WindowEvent { event, .. } => {
                span!(_guard, "Handle WindowEvent");
                global_state
                    .window
                    .handle_window_event(event, &mut global_state.settings)
            },
            winit::event::Event::DeviceEvent { event, .. } => {
                span!(_guard, "Handle DeviceEvent");
                global_state.window.handle_device_event(event)
            },
            winit::event::Event::LoopDestroyed => {
                // Save any unsaved changes to settings and profile
                global_state.settings.save_to_file_warn();
                global_state.profile.save_to_file_warn();
            },
            _ => {},
        }
    });
}

fn handle_main_events_cleared(
    states: &mut Vec<Box<dyn PlayState>>,
    control_flow: &mut winit::event_loop::ControlFlow,
    global_state: &mut GlobalState,
) {
    span!(guard, "Handle MainEventsCleared");
    // Screenshot / Fullscreen toggle
    global_state
        .window
        .resolve_deduplicated_events(&mut global_state.settings);
    // Run tick here

    // What's going on here?
    // ---------------------
    // The state system used by Voxygen allows for the easy development of
    // stack-based menus. For example, you may want a "title" state
    // that can push a "main menu" state on top of it, which can in
    // turn push a "settings" state or a "game session" state on top of it.
    // The code below manages the state transfer logic automatically so that we
    // don't have to re-engineer it for each menu we decide to add
    // to the game.
    let mut exit = true;
    while let Some(state_result) = states.last_mut().map(|last| {
        let events = global_state.window.fetch_events();
        last.tick(global_state, events)
    }) {
        // Implement state transfer logic.
        match state_result {
            PlayStateResult::Continue => {
                exit = false;
                break;
            },
            PlayStateResult::Shutdown => {
                debug!("Shutting down all states...");
                while states.last().is_some() {
                    states.pop().map(|old_state| {
                        debug!("Popped state '{}'.", old_state.name());
                        global_state.on_play_state_changed();
                    });
                }
            },
            PlayStateResult::Pop => {
                states.pop().map(|old_state| {
                    debug!("Popped state '{}'.", old_state.name());
                    global_state.on_play_state_changed();
                });
                states.last_mut().map(|new_state| {
                    new_state.enter(global_state, Direction::Backwards);
                });
            },
            PlayStateResult::Push(mut new_state) => {
                new_state.enter(global_state, Direction::Forwards);
                debug!("Pushed state '{}'.", new_state.name());
                states.push(new_state);
                global_state.on_play_state_changed();
            },
            PlayStateResult::Switch(mut new_state) => {
                new_state.enter(global_state, Direction::Forwards);
                states.last_mut().map(|old_state| {
                    debug!(
                        "Switching to state '{}' from state '{}'.",
                        new_state.name(),
                        old_state.name()
                    );
                    mem::swap(old_state, &mut new_state);
                    global_state.on_play_state_changed();
                });
            },
        }
    }

    if exit {
        *control_flow = winit::event_loop::ControlFlow::Exit;
    }

    drop(guard);
    if let Some(last) = states.last_mut() {
        span!(guard, "Render");
        let renderer = global_state.window.renderer_mut();
        // Clear the shadow maps.
        renderer.clear_shadows();
        // Clear the screen
        renderer.clear();
        // Render the screen using the global renderer
        last.render(renderer, &global_state.settings);
        // Finish the frame.
        global_state.window.renderer_mut().flush();
        // Display the frame on the window.
        global_state
            .window
            .swap_buffers()
            .expect("Failed to swap window buffers!");
        drop(guard);
    }

    if !exit {
        // Wait for the next tick.
        span!(guard, "Main thread sleep");
        global_state.clock.set_target_dt(Duration::from_secs_f64(
            1.0 / get_fps(global_state.settings.graphics.max_fps) as f64,
        ));
        global_state.clock.tick();
        drop(guard);
        #[cfg(feature = "tracy")]
        common_base::tracy_client::finish_continuous_frame!();

        // Maintain global state.
        global_state.maintain(global_state.clock.dt());
    }
}
