use std::sync::mpsc;

use crate::{
    app::UserEvent,
    emulator::{EmulatorClient, EmulatorCommand, EmulatorState, SimId, SimState},
    persistence::Persistence,
};
use egui::{
    ecolor::HexColor, menu, Align2, Button, CentralPanel, Color32, Context, Direction, Frame,
    Layout, Response, Sense, TopBottomPanel, Ui, Vec2, ViewportBuilder, ViewportCommand,
    ViewportId, WidgetText, Window,
};
use egui_toast::{Toast, Toasts};
use serde::{Deserialize, Serialize};
use winit::event_loop::EventLoopProxy;

use super::{
    game_screen::{DisplayMode, GameScreen},
    AppWindow,
};

const COLOR_PRESETS: [[Color32; 2]; 3] = [
    [
        Color32::from_rgb(0xff, 0x00, 0x00),
        Color32::from_rgb(0x00, 0xc6, 0xf0),
    ],
    [
        Color32::from_rgb(0x00, 0xb4, 0x00),
        Color32::from_rgb(0xc8, 0x00, 0xff),
    ],
    [
        Color32::from_rgb(0xb4, 0x9b, 0x00),
        Color32::from_rgb(0x00, 0x00, 0xff),
    ],
];

pub struct GameWindow {
    client: EmulatorClient,
    proxy: EventLoopProxy<UserEvent>,
    persistence: Persistence,
    sim_id: SimId,
    config: GameConfig,
    screen: Option<GameScreen>,
    messages: Option<mpsc::Receiver<Toast>>,
    color_picker: Option<ColorPickerState>,
}

impl GameWindow {
    pub fn new(
        client: EmulatorClient,
        proxy: EventLoopProxy<UserEvent>,
        persistence: Persistence,
        sim_id: SimId,
    ) -> Self {
        let config = load_config(&persistence, sim_id);
        Self {
            client,
            proxy,
            persistence,
            sim_id,
            config,
            screen: None,
            messages: None,
            color_picker: None,
        }
    }

    fn show_menu(&mut self, ctx: &Context, ui: &mut Ui) {
        ui.menu_button("ROM", |ui| {
            if ui.button("Open ROM").clicked() {
                let rom = rfd::FileDialog::new()
                    .add_filter("Virtual Boy ROMs", &["vb", "vbrom"])
                    .pick_file();
                if let Some(path) = rom {
                    self.client
                        .send_command(EmulatorCommand::LoadGame(self.sim_id, path));
                }
                ui.close_menu();
            }
            if ui.button("Quit").clicked() {
                let _ = self.proxy.send_event(UserEvent::Quit(self.sim_id));
            }
        });
        ui.menu_button("Emulation", |ui| {
            let state = self.client.emulator_state();
            let is_ready = self.client.sim_state(self.sim_id) == SimState::Ready;
            let can_pause = is_ready && state == EmulatorState::Running;
            let can_resume = is_ready && state == EmulatorState::Paused;
            if state == EmulatorState::Running {
                if ui.add_enabled(can_pause, Button::new("Pause")).clicked() {
                    self.client.send_command(EmulatorCommand::Pause);
                    ui.close_menu();
                }
            } else if ui.add_enabled(can_resume, Button::new("Resume")).clicked() {
                self.client.send_command(EmulatorCommand::Resume);
                ui.close_menu();
            }
            if ui.add_enabled(is_ready, Button::new("Reset")).clicked() {
                self.client
                    .send_command(EmulatorCommand::Reset(self.sim_id));
                ui.close_menu();
            }
        });
        ui.menu_button("Options", |ui| self.show_options_menu(ctx, ui));
        ui.menu_button("Multiplayer", |ui| {
            let has_player_2 = self.client.sim_state(SimId::Player2) != SimState::Uninitialized;
            if self.sim_id == SimId::Player1
                && !has_player_2
                && ui.button("Open Player 2").clicked()
            {
                self.client
                    .send_command(EmulatorCommand::StartSecondSim(None));
                self.proxy.send_event(UserEvent::OpenPlayer2).unwrap();
                ui.close_menu();
            }
            if has_player_2 {
                let linked = self.client.are_sims_linked();
                if linked && ui.button("Unlink").clicked() {
                    self.client.send_command(EmulatorCommand::Unlink);
                    ui.close_menu();
                }
                if !linked && ui.button("Link").clicked() {
                    self.client.send_command(EmulatorCommand::Link);
                    ui.close_menu();
                }
            }
        });
        ui.menu_button("Tools", |ui| {
            if ui.button("GDB Server").clicked() {
                self.proxy
                    .send_event(UserEvent::OpenDebugger(self.sim_id))
                    .unwrap();
                ui.close_menu();
            }
            if ui.button("Character Data").clicked() {
                self.proxy
                    .send_event(UserEvent::OpenCharacterData(self.sim_id))
                    .unwrap();
                ui.close_menu();
            }
        });
        ui.menu_button("About", |ui| {
            self.proxy.send_event(UserEvent::OpenAbout).unwrap();
            ui.close_menu();
        });
    }

    fn show_options_menu(&mut self, ctx: &Context, ui: &mut Ui) {
        ui.menu_button("Video", |ui| {
            ui.menu_button("Screen Size", |ui| {
                let current_dims = ctx.input(|i| i.viewport().inner_rect.unwrap());
                let current_dims = current_dims.max - current_dims.min;

                for scale in 1..=4 {
                    let label = format!("x{scale}");
                    let scale = scale as f32;
                    let dims = {
                        let Vec2 { x, y } = self.config.display_mode.proportions();
                        Vec2::new(x * scale, y * scale + 22.0)
                    };
                    if ui
                        .selectable_button((current_dims - dims).length() < 1.0, label)
                        .clicked()
                    {
                        ctx.send_viewport_cmd(ViewportCommand::InnerSize(dims));
                        ui.close_menu();
                    }
                }
            });

            ui.menu_button("Display Mode", |ui| {
                let old_proportions = self.config.display_mode.proportions();
                let mut changed = false;
                let mut display_mode = self.config.display_mode;
                changed |= ui
                    .selectable_option(&mut display_mode, DisplayMode::Anaglyph, "Anaglyph")
                    .clicked();
                changed |= ui
                    .selectable_option(&mut display_mode, DisplayMode::LeftEye, "Left Eye")
                    .clicked();
                changed |= ui
                    .selectable_option(&mut display_mode, DisplayMode::RightEye, "Right Eye")
                    .clicked();
                changed |= ui
                    .selectable_option(&mut display_mode, DisplayMode::SideBySide, "Side by Side")
                    .clicked();

                if !changed {
                    return;
                }

                let current_dims = {
                    let viewport = ctx.input(|i| i.viewport().inner_rect.unwrap());
                    viewport.max - viewport.min
                };
                let new_proportions = display_mode.proportions();
                let scale = new_proportions / old_proportions;
                if scale != Vec2::new(1.0, 1.0) {
                    ctx.send_viewport_cmd(ViewportCommand::InnerSize(current_dims * scale));
                }

                self.update_config(|c| {
                    c.display_mode = display_mode;
                    c.dimensions = current_dims * scale;
                });
                ui.close_menu();
            });
            ui.menu_button("Colors", |ui| {
                for preset in COLOR_PRESETS {
                    if ui.color_pair_button(preset[0], preset[1]).clicked() {
                        self.update_config(|c| c.colors = preset);
                        ui.close_menu();
                    }
                }
                ui.with_layout(ui.layout().with_cross_align(egui::Align::Center), |ui| {
                    if ui.button("Custom").clicked() {
                        let color_str = |color: Color32| {
                            format!("{:02x}{:02x}{:02x}", color.r(), color.g(), color.b())
                        };
                        let is_running = self.client.emulator_state() == EmulatorState::Running;
                        if is_running {
                            self.client.send_command(EmulatorCommand::Pause);
                        }
                        let color_codes = [
                            color_str(self.config.colors[0]),
                            color_str(self.config.colors[1]),
                        ];
                        self.color_picker = Some(ColorPickerState {
                            color_codes,
                            just_opened: true,
                            unpause_on_close: is_running,
                        });
                        ui.close_menu();
                    }
                });
            });
        });
        ui.menu_button("Audio", |ui| {
            let p1_enabled = self.client.is_audio_enabled(SimId::Player1);
            let p2_enabled = self.client.is_audio_enabled(SimId::Player2);
            if ui.selectable_button(p1_enabled, "Player 1").clicked() {
                self.client
                    .send_command(EmulatorCommand::SetAudioEnabled(!p1_enabled, p2_enabled));
                ui.close_menu();
            }
            if ui.selectable_button(p2_enabled, "Player 2").clicked() {
                self.client
                    .send_command(EmulatorCommand::SetAudioEnabled(p1_enabled, !p2_enabled));
                ui.close_menu();
            }
        });
        ui.menu_button("Input", |ui| {
            if ui.button("Bind Inputs").clicked() {
                self.proxy.send_event(UserEvent::OpenInput).unwrap();
                ui.close_menu();
            }
        });
    }

    fn show_color_picker(&mut self, ui: &mut Ui) {
        let mut colors = self.config.colors;
        let Some(state) = self.color_picker.as_mut() else {
            return;
        };
        let (open, updated) = ui
            .horizontal(|ui| {
                let left_color = ui.color_picker(&mut colors[0], &mut state.color_codes[0]);
                if state.just_opened {
                    left_color.request_focus();
                    state.just_opened = false;
                }
                let right_color = ui.color_picker(&mut colors[1], &mut state.color_codes[1]);
                let open = left_color.has_focus() || right_color.has_focus();
                let updated = left_color.changed() || right_color.changed();
                (open, updated)
            })
            .inner;
        if !open {
            if state.unpause_on_close {
                self.client.send_command(EmulatorCommand::Resume);
            }
            self.color_picker = None;
        }
        if updated {
            self.update_config(|c| c.colors = colors);
        }
    }

    fn update_config(&mut self, update: impl FnOnce(&mut GameConfig)) {
        let mut new_config = self.config.clone();
        update(&mut new_config);
        if self.config != new_config {
            let _ = self
                .persistence
                .save_config(config_filename(self.sim_id), &new_config);
        }
        self.config = new_config;
    }
}

fn config_filename(sim_id: SimId) -> &'static str {
    match sim_id {
        SimId::Player1 => "config_p1",
        SimId::Player2 => "config_p2",
    }
}

fn load_config(persistence: &Persistence, sim_id: SimId) -> GameConfig {
    if let Ok(config) = persistence.load_config(config_filename(sim_id)) {
        return config;
    }
    GameConfig {
        display_mode: DisplayMode::Anaglyph,
        colors: COLOR_PRESETS[0],
        dimensions: DisplayMode::Anaglyph.proportions() + Vec2::new(0.0, 22.0),
    }
}

impl AppWindow for GameWindow {
    fn viewport_id(&self) -> ViewportId {
        match self.sim_id {
            SimId::Player1 => ViewportId::ROOT,
            SimId::Player2 => ViewportId::from_hash_of("Player2"),
        }
    }

    fn sim_id(&self) -> SimId {
        self.sim_id
    }

    fn initial_viewport(&self) -> ViewportBuilder {
        ViewportBuilder::default()
            .with_title("Lemur")
            .with_inner_size(self.config.dimensions)
    }

    fn show(&mut self, ctx: &Context) {
        let dimensions = {
            let bounds = ctx.input(|i| i.viewport().inner_rect.unwrap());
            bounds.max - bounds.min
        };
        self.update_config(|c| c.dimensions = dimensions);

        let mut toasts = Toasts::new()
            .anchor(Align2::LEFT_BOTTOM, (10.0, 10.0))
            .direction(Direction::BottomUp);
        if let Some(messages) = self.messages.as_mut() {
            while let Ok(toast) = messages.try_recv() {
                toasts.add(toast);
            }
        }
        TopBottomPanel::top("menubar")
            .exact_height(22.0)
            .show(ctx, |ui| {
                menu::bar(ui, |ui| {
                    self.show_menu(ctx, ui);
                });
            });
        if self.color_picker.is_some() {
            Window::new("Color Picker")
                .title_bar(false)
                .resizable(false)
                .anchor(Align2::CENTER_CENTER, Vec2::ZERO)
                .show(ctx, |ui| {
                    self.show_color_picker(ui);
                });
        }
        let frame = Frame::central_panel(&ctx.style()).fill(Color32::BLACK);
        CentralPanel::default().frame(frame).show(ctx, |ui| {
            if let Some(screen) = self.screen.as_mut() {
                screen.update(self.config.display_mode, self.config.colors);
                ui.add(screen);
            }
        });
        toasts.show(ctx);
    }

    fn on_init(&mut self, render_state: &egui_wgpu::RenderState) {
        let (screen, sink) = GameScreen::init(render_state);
        let (message_sink, message_source) = mpsc::channel();
        self.client.send_command(EmulatorCommand::ConnectToSim(
            self.sim_id,
            sink,
            message_sink,
        ));
        self.screen = Some(screen);
        self.messages = Some(message_source);
    }

    fn on_destroy(&mut self) {
        if self.sim_id == SimId::Player2 {
            self.client.send_command(EmulatorCommand::StopSecondSim);
        }
        let _ = self.proxy.send_event(UserEvent::Quit(self.sim_id));
    }
}

trait UiExt {
    fn selectable_button(&mut self, selected: bool, text: impl Into<WidgetText>) -> Response;
    fn selectable_option<T: Eq>(
        &mut self,
        current_value: &mut T,
        selected_value: T,
        text: impl Into<WidgetText>,
    ) -> Response {
        let response = self.selectable_button(*current_value == selected_value, text);
        if response.clicked() {
            *current_value = selected_value;
        }
        response
    }

    fn color_pair_button(&mut self, left: Color32, right: Color32) -> Response;

    fn color_picker(&mut self, color: &mut Color32, hex: &mut String) -> Response;
}

impl UiExt for Ui {
    fn selectable_button(&mut self, selected: bool, text: impl Into<WidgetText>) -> Response {
        self.style_mut().visuals.widgets.inactive.bg_fill = Color32::TRANSPARENT;
        self.style_mut().visuals.widgets.hovered.bg_fill = Color32::TRANSPARENT;
        self.style_mut().visuals.widgets.active.bg_fill = Color32::TRANSPARENT;
        let mut selected = selected;
        self.checkbox(&mut selected, text)
    }

    fn color_pair_button(&mut self, left: Color32, right: Color32) -> Response {
        let button_size = Vec2::new(60.0, 20.0);
        let (rect, response) = self.allocate_at_least(button_size, Sense::click());
        let center_x = rect.center().x;
        let left_rect = rect.with_max_x(center_x);
        self.painter().rect_filled(left_rect, 0.0, left);
        let right_rect = rect.with_min_x(center_x);
        self.painter().rect_filled(right_rect, 0.0, right);

        let style = self.style().interact(&response);
        self.painter().rect_stroke(rect, 0.0, style.fg_stroke);
        response
    }

    fn color_picker(&mut self, color: &mut Color32, hex: &mut String) -> Response {
        self.allocate_ui_with_layout(
            Vec2::new(100.0, 130.0),
            Layout::top_down_justified(egui::Align::Center),
            |ui| {
                let (rect, _) = ui.allocate_at_least(Vec2::new(100.0, 100.0), Sense::hover());
                ui.painter().rect_filled(rect, 0.0, *color);
                let resp = ui.text_edit_singleline(hex);
                if resp.changed() {
                    if let Ok(new_color) = HexColor::from_str_without_hash(hex) {
                        *color = new_color.color();
                    }
                }
                resp
            },
        )
        .inner
    }
}

struct ColorPickerState {
    color_codes: [String; 2],
    just_opened: bool,
    unpause_on_close: bool,
}

#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
struct GameConfig {
    display_mode: DisplayMode,
    colors: [Color32; 2],
    dimensions: Vec2,
}