use crate::App;
use core_pb::constants::GUI_LISTENER_PORT;
use core_pb::messages::settings::{
    ConnectionSettings, CvLocationSource, ShouldDoTargetPath, StrategyChoice,
};
use core_pb::messages::{
    GameServerCommand, GuiToServerMessage, NetworkStatus, ServerToRobotMessage,
    ServerToSimulationMessage,
};
use core_pb::names::{RobotName, NUM_ROBOT_NAMES};
use core_pb::threaded_websocket::TextOrT;
use core_pb::util::ColoredStatus;
use eframe::egui;
use eframe::egui::{Align, Color32, Layout, TextEdit, Ui, WidgetText};
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt::Debug;
use std::str::FromStr;
pub struct UiSettings {
    pub selected_robot: RobotName,
    pub any_robot_has_been_selected: bool,
    pub mdrc_server: ConnectionSettings,
    pub mdrc_server_collapsed: bool,
    pub simulation_collapsed: bool,
    pub game_server_collapsed: bool,
    pub robots_collapsed: [bool; NUM_ROBOT_NAMES],
    pub graph_lines: [[bool; 4]; 3],
    pub devices_collapsed: bool,
    pub angle_behavior: VelocityControlAngleBehavior,
    pub record_motor_data: bool,
}
impl Default for UiSettings {
    fn default() -> Self {
        Self {
            selected_robot: RobotName::Pierre,
            any_robot_has_been_selected: false,
            mdrc_server: ConnectionSettings {
                connect: true,
                ipv4: [127, 0, 0, 1],
                port: GUI_LISTENER_PORT,
            },
            mdrc_server_collapsed: true,
            simulation_collapsed: true,
            game_server_collapsed: true,
            robots_collapsed: [true; NUM_ROBOT_NAMES],
            graph_lines: [[true; 4]; 3],
            devices_collapsed: true,
            angle_behavior: VelocityControlAngleBehavior::Free,
            record_motor_data: false,
        }
    }
}
#[derive(Copy, Clone, Debug, PartialOrd, PartialEq, Serialize, Deserialize)]
pub enum VelocityControlAngleBehavior {
    Free,
    Locked(f32),
    FaceForward,
    AssistedDriving,
}
#[allow(clippy::too_many_arguments)]
fn validated<T: PartialEq + Debug>(
    id: String,
    ui: &mut Ui,
    fields: &mut HashMap<String, (String, String)>,
    value: &mut T,
    text: impl Into<WidgetText>,
    validation: fn(&str) -> Option<T>,
    to_str: fn(&T) -> String,
    end_row: bool,
) {
    let text = text.into();
    ui.with_layout(Layout::left_to_right(Align::Center), |ui| {
        ui.label(text);
        if !fields.contains_key(&id) {
            let str = to_str(value);
            fields.insert(id.clone(), (str.clone(), str));
        }
        let (last_typed, last_valid) = fields.get_mut(&id).unwrap();
        let field = ui.add(TextEdit::singleline(last_typed).desired_width(80.0));
        if let Some(t) = validation(last_typed.to_string().as_str()) {
            *last_valid = last_typed.to_string();
            if !field.has_focus() && t != *value {
                let str = to_str(value);
                last_valid.clone_from(&str);
                *last_valid = str.clone();
                *last_typed = str;
            } else {
                *value = t;
            }
        } else if !field.has_focus() {
            last_typed.clone_from(last_valid);
        }
    });
    if end_row {
        ui.end_row();
    }
}
pub fn num<T: FromStr + ToString + PartialEq + Debug>(
    id: String,
    ui: &mut Ui,
    fields: &mut HashMap<String, (String, String)>,
    value: &mut T,
    text: impl Into<WidgetText>,
    end_row: bool,
) {
    validated(
        id,
        ui,
        fields,
        value,
        text,
        |x| x.parse().ok(),
        T::to_string,
        end_row,
    )
}
fn ipv4(
    id: String,
    ui: &mut Ui,
    fields: &mut HashMap<String, (String, String)>,
    value: &mut [u8; 4],
    text: impl Into<WidgetText>,
) {
    validated(
        id,
        ui,
        fields,
        value,
        text,
        |x| {
            let re = Regex::new(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$").unwrap();
            if re.is_match(x) {
                let mut arr = [0; 4];
                for (i, s) in x.split('.').enumerate() {
                    arr[i] = s.parse().unwrap();
                }
                Some(arr)
            } else {
                None
            }
        },
        |x| format!("{}.{}.{}.{}", x[0], x[1], x[2], x[3]),
        true,
    )
}
pub fn dropdown<T: Debug + PartialEq + Clone>(
    ui: &mut Ui,
    id: String,
    text: impl Into<WidgetText>,
    value: &mut T,
    options: &[T],
) {
    let s_text = WidgetText::from(format!("{:?}", value));
    egui::ComboBox::new(id, text)
        .selected_text(s_text)
        .show_ui(ui, |ui| {
            for t in options {
                let str = WidgetText::from(format!("{:?}", t));
                ui.selectable_value(value, t.clone(), str);
            }
        });
}
fn collapsable_section(
    ui: &mut Ui,
    collapsed: &mut bool,
    button_color: Color32,
    header_contents: impl FnOnce(&mut Ui),
    body_contents: impl FnOnce(&mut Ui),
    tooltip: Option<impl Into<WidgetText>>,
) {
    ui.with_layout(Layout::left_to_right(Align::Center), |ui| {
        let mut button = ui.add(
            egui::Button::new(match *collapsed {
                true => egui_phosphor::regular::CARET_RIGHT,
                false => egui_phosphor::regular::CARET_DOWN,
            })
            .fill(button_color),
        );
        if let Some(tt) = tooltip {
            button = button.on_hover_text(tt)
        }
        if button.clicked() {
            *collapsed = !*collapsed;
        }
        header_contents(ui);
    });
    ui.end_row();
    if !*collapsed {
        body_contents(ui);
        ui.label("");
        ui.end_row();
    }
}
#[allow(clippy::too_many_arguments)]
pub fn generic_server(
    ui: &mut Ui,
    name: &str,
    fields: &mut HashMap<String, (String, String)>,
    connection_settings: &mut ConnectionSettings,
    collapsed: &mut bool,
    status: &NetworkStatus,
    right_ui: impl FnOnce(&mut Ui),
    tooltip: Option<impl Into<WidgetText>>,
) {
    let ip_name = name.to_string() + "server_ip";
    let port_name = name.to_string() + "server_port";
    collapsable_section(
        ui,
        collapsed,
        status.status().to_color32(),
        |ui| {
            ui.checkbox(&mut connection_settings.connect, name);
            ui.with_layout(Layout::right_to_left(Align::Center), right_ui);
        },
        |ui| {
            ipv4(ip_name, ui, fields, &mut connection_settings.ipv4, "IP");
            num(
                port_name,
                ui,
                fields,
                &mut connection_settings.port,
                "Port",
                true,
            );
        },
        tooltip,
    );
}
pub fn draw_settings(app: &mut App, ui: &mut Ui) {
    let mut fields = app.settings_fields.take().unwrap();
    egui::Grid::new("settings_grid")
        .num_columns(1)
        .striped(true)
        .show(ui, |ui| draw_settings_inner(app, ui, &mut fields));
    app.settings_fields = Some(fields);
}
fn draw_settings_inner(app: &mut App, ui: &mut Ui, fields: &mut HashMap<String, (String, String)>) {
    ui.checkbox(&mut app.rotated_grid, "Rotated grid");
    ui.end_row();
    ui.with_layout(Layout::left_to_right(Align::Center), |ui| {
        ui.checkbox(&mut app.settings.simulation.simulate, "Run simulation");
        ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
            if ui
                .add_enabled(
                    app.settings.simulation.simulate,
                    egui::Button::new(egui_phosphor::regular::ARROW_CLOCKWISE),
                )
                .clicked()
            {
                app.send(GuiToServerMessage::RestartSimulation);
            }
        });
    });
    ui.end_row();
    ui.checkbox(&mut app.settings.safe_mode, "Safe mode");
    ui.end_row();
    dropdown(
        ui,
        "Target Path Dropdown".to_string(),
        "Target Path",
        &mut app.settings.do_target_path,
        &ShouldDoTargetPath::get_all(),
    );
    ui.end_row();
    num(
        "target_speed".to_string(),
        ui,
        fields,
        &mut app.settings.target_speed,
        "Target speed",
        true,
    );
    ui.add_enabled_ui(
        app.server_status.robots[app.ui_settings.selected_robot as usize].connection
            == NetworkStatus::Connected,
        |ui| {
            if ui.button("Reset angle").clicked() {
                if let Ok(ang) =
                    app.server_status.robots[app.ui_settings.selected_robot as usize].imu_angle
                {
                    app.settings.robots[app.ui_settings.selected_robot as usize]
                        .config
                        .angle_offset += ang;
                }
            }
        },
    );
    ui.end_row();
    if app.ui_settings.selected_robot.is_simulated() {
        app.ui_settings.devices_collapsed = true;
    }
    collapsable_section(
        ui,
        &mut app.ui_settings.devices_collapsed,
        ColoredStatus::NotApplicable(None).to_color32(),
        |ui| {
            ui.label("Physical Devices");
        },
        |ui| {
            let settings = &mut app.settings.robots[app.ui_settings.selected_robot as usize];
            ui.checkbox(&mut settings.config.enable_imu, "IMU");
            ui.end_row();
            ui.checkbox(&mut settings.config.enable_extra_imu_data, "Extra IMU data");
            ui.end_row();
            ui.checkbox(&mut settings.config.enable_dists, "Distance sensors");
            ui.end_row();
            ui.checkbox(
                &mut settings.config.enable_battery_monitor,
                "Battery monitor",
            );
            ui.end_row();
            ui.checkbox(&mut settings.config.enable_display, "Display");
            ui.end_row();
            ui.checkbox(&mut settings.config.enable_gamepad, "Gamepad");
            ui.end_row();
            num(
                "Display update (ms)".to_string(),
                ui,
                fields,
                &mut settings.config.display_loop_interval,
                "Display update (ms)",
                true,
            );
        },
        None::<String>,
    );
    num(
        "lookahead_distance".to_string(),
        ui,
        fields,
        &mut app.settings.robots[app.ui_settings.selected_robot as usize]
            .config
            .lookahead_dist,
        "Lookahead distance",
        true,
    );
    num(
        "robot_speed".to_string(),
        ui,
        fields,
        &mut app.settings.robots[app.ui_settings.selected_robot as usize]
            .config
            .robot_speed,
        "Robot speed",
        true,
    );
    num(
        "snapping_distance".to_string(),
        ui,
        fields,
        &mut app.settings.robots[app.ui_settings.selected_robot as usize]
            .config
            .snapping_dist,
        "Snapping distance",
        true,
    );
    num(
        "cv_error".to_string(),
        ui,
        fields,
        &mut app.settings.robots[app.ui_settings.selected_robot as usize]
            .config
            .cv_error,
        "CV error",
        true,
    );
    ui.end_row();
    dropdown(
        ui,
        "angle behavior".to_string(),
        "Manual Angle Behavior",
        &mut app.ui_settings.angle_behavior,
        &[
            VelocityControlAngleBehavior::Free,
            VelocityControlAngleBehavior::Locked(
                app.server_status.robots[app.ui_settings.selected_robot as usize]
                    .clone()
                    .imu_angle
                    .unwrap_or(0.0),
            ),
            VelocityControlAngleBehavior::FaceForward,
            VelocityControlAngleBehavior::AssistedDriving,
            VelocityControlAngleBehavior::Locked(0.0),
        ],
    );
    ui.end_row();
    ui.separator();
    ui.end_row();
    dropdown(
        ui,
        "selected_robot".to_string(),
        "Selected",
        &mut app.ui_settings.selected_robot,
        &RobotName::get_all(),
    );
    ui.end_row();
    dropdown(
        ui,
        "gs_robot".to_string(),
        "Pacman",
        &mut app.settings.pacman,
        &RobotName::get_all().into_iter().collect::<Vec<_>>(),
    );
    ui.end_row();
    ui.separator();
    ui.end_row();
    generic_server(
        ui,
        "MDRC Server",
        fields,
        &mut app.ui_settings.mdrc_server,
        &mut app.ui_settings.mdrc_server_collapsed,
        &app.network.0.status(),
        |_| {},
        None::<&str>,
    );
    generic_server(
        ui,
        "Simulation",
        fields,
        &mut app.settings.simulation.connection,
        &mut app.ui_settings.simulation_collapsed,
        &app.server_status.simulation_connection,
        |_| {},
        None::<&str>,
    );
    generic_server(
        ui,
        if app.server_status.advanced_game_server {
            "Game server++"
        } else {
            "Game server"
        },
        fields,
        &mut app.settings.game_server.connection,
        &mut app.ui_settings.game_server_collapsed,
        &app.server_status.game_server_connection,
        |ui| {
            let connected = app.server_status.game_server_connection == NetworkStatus::Connected;
            let reset_button = ui.add_enabled(
                connected,
                egui::Button::new(egui_phosphor::regular::ARROW_CLOCKWISE),
            );
            if reset_button.clicked() {
                app.network
                    .0
                    .send(TextOrT::T(GuiToServerMessage::GameServerCommand(
                        GameServerCommand::Reset,
                    )));
            }
            let icon = if app.server_status.game_state.paused {
                egui_phosphor::regular::PLAY
            } else {
                egui_phosphor::regular::PAUSE
            };
            let pause_button = ui.add_enabled(connected, egui::Button::new(icon));
            if pause_button.clicked() {
                app.network
                    .0
                    .send(TextOrT::T(GuiToServerMessage::GameServerCommand(
                        if app.server_status.game_state.paused {
                            GameServerCommand::Unpause
                        } else {
                            GameServerCommand::Pause
                        },
                    )));
            }
        },
        None::<&str>,
    );
    ui.separator();
    ui.end_row();
    let mut any_robot_enabled = None;
    for name in RobotName::get_all() {
        let conn_status = app.server_status.robots[name as usize].connection;
        generic_server(
            ui,
            &format!("{name}"),
            fields,
            &mut app.settings.robots[name as usize].connection,
            &mut app.ui_settings.robots_collapsed[name as usize],
            &app.server_status.robots[name as usize].connection,
            |ui| {
                ui.add_enabled_ui(conn_status == NetworkStatus::Connected, |ui| {
                    if ui.button(egui_phosphor::regular::ARROW_CLOCKWISE).clicked() {
                        app.network
                            .0
                            .send(TextOrT::T(GuiToServerMessage::RobotCommand(
                                name,
                                ServerToRobotMessage::Reboot,
                            )));
                    }
                });
                if name.is_simulated() {
                    ui.add_enabled_ui(
                        app.server_status.simulation_connection == NetworkStatus::Connected,
                        |ui| {
                            if app.server_status.robots[name as usize]
                                .sim_position
                                .is_some()
                            {
                                if ui.button(egui_phosphor::regular::MINUS).clicked() {
                                    app.network.0.send(TextOrT::T(
                                        GuiToServerMessage::SimulationCommand(
                                            ServerToSimulationMessage::Delete(name),
                                        ),
                                    ));
                                }
                            } else if ui.button(egui_phosphor::regular::PLUS).clicked() {
                                app.network.0.send(TextOrT::T(
                                    GuiToServerMessage::SimulationCommand(
                                        ServerToSimulationMessage::Spawn(name),
                                    ),
                                ));
                            }
                        },
                    );
                }
            },
            app.server_status.robots[name as usize]
                .ping
                .map(|x| format!("Ping: {x:?}")),
        );
        if app.settings.robots[name as usize].connection.connect {
            any_robot_enabled = Some(name);
        }
    }
    if let Some(name) = any_robot_enabled {
        if !app.ui_settings.any_robot_has_been_selected {
            app.ui_settings.selected_robot = name;
            app.ui_settings.any_robot_has_been_selected = true;
        }
    }
    ui.separator();
    ui.end_row();
    dropdown(
        ui,
        "strategy".to_string(),
        "Strategy",
        &mut app.settings.driving.strategy,
        &[
            StrategyChoice::Manual,
            StrategyChoice::ReinforcementLearning,
            StrategyChoice::TestUniform,
            StrategyChoice::TestForward,
        ],
    );
    ui.end_row();
    dropdown(
        ui,
        "cv_location".to_string(),
        "CV Location",
        &mut app.settings.cv_location_source,
        &[CvLocationSource::GameState, CvLocationSource::Localization],
    );
    ui.end_row();
}