Add section indicators, string indicators/detection, add more advanced-level features. Make this be more advanced.

use std::fs;
use std::path::PathBuf;

use capstone::prelude::*;
use eframe::egui::{self, Color32, FontData, FontDefinitions, FontFamily, RichText, TextStyle};
use object::{Object, ObjectSection};

struct BinaryAnalyzerApp {
    file_path: Option<PathBuf>,
    disassembly: Vec<InstructionEntry>,
    theme: Theme,
    setup_done: bool,
    error_message: Option<String>,
}

#[derive(PartialEq)]
enum Theme {
    Dark,
    Light,
}

// Struct to hold disassembly entries with separate fields
struct InstructionEntry {
    address: u64,
    mnemonic: String,
    operands: String,
}

impl Default for BinaryAnalyzerApp {
    fn default() -> Self {
        Self {
            file_path: None,
            disassembly: Vec::new(),
            theme: Theme::Dark,
            setup_done: false,
            error_message: None,
        }
    }
}

impl eframe::App for BinaryAnalyzerApp {
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        if !self.setup_done {
            self.set_theme(ctx);
            self.set_custom_fonts(ctx);
            self.setup_done = true;
        }

        self.top_panel(ctx);
        self.central_panel(ctx);
    }
}

impl BinaryAnalyzerApp {
    fn top_panel(&mut self, ctx: &egui::Context) {
        egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
            ui.add_space(5.0);
            ui.horizontal(|ui| {
                ui.heading("🔍 x86 Binary Analyzer");

                ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
                    if ui
                        .button(match self.theme {
                            Theme::Dark => "🌞 Light Theme",
                            Theme::Light => "🌙 Dark Theme",
                        })
                        .clicked()
                    {
                        self.toggle_theme(ctx);
                    }
                });
            });
            ui.add_space(5.0);
        });
    }

    fn central_panel(&mut self, ctx: &egui::Context) {
        egui::CentralPanel::default().show(ctx, |ui| {
            ui.add_space(10.0);

            ui.horizontal(|ui| {
                if ui.button("📂 Select Binary File").clicked() {
                    if let Some(path) = rfd::FileDialog::new()
                        .add_filter("Executable Files", &["exe", "bin", "elf"])
                        .pick_file()
                    {
                        self.file_path = Some(path);
                        self.analyze();
                    }
                }

                if let Some(path) = &self.file_path {
                    ui.label(
                        RichText::new(format!("Selected File: {}", path.display())).italics(),
                    );
                }

                if ui.button("🔄 Refresh").clicked() {
                    if self.file_path.is_some() {
                        self.analyze();
                    }
                }
            });

            ui.separator();
            ui.add_space(5.0);

            egui::ScrollArea::both().show(ui, |ui| {
                if let Some(error) = &self.error_message {
                    ui.colored_label(Color32::RED, error);
                } else if self.disassembly.is_empty() {
                    ui.label(
                        "No disassembly to display. Please select a binary file to analyze.",
                    );
                } else {
                    for instr in &self.disassembly {
                        ui.horizontal_wrapped(|ui| {
                            ui.label(
                                RichText::new(format!("0x{:016X}: ", instr.address))
                                    .monospace()
                                    .color(Color32::LIGHT_BLUE),
                            );
                            ui.label(
                                RichText::new(format!("{:8}", instr.mnemonic))
                                    .monospace()
                                    .color(Color32::YELLOW),
                            );
                            ui.label(
                                RichText::new(&instr.operands)
                                    .monospace()
                                    .color(Color32::WHITE),
                            );
                        });
                    }
                }
            });
        });
    }

    fn analyze(&mut self) {
        // Clear previous results
        self.disassembly.clear();
        self.error_message = None;

        if let Some(path) = &self.file_path {
            // Read and analyze the binary file
            match fs::read(path) {
                Ok(binary_data) => match object::File::parse(&*binary_data) {
                    Ok(obj_file) => {
                        // Detect architecture (32-bit or 64-bit)
                        let is_64 = obj_file.is_64();

                        if let Some(section) = obj_file.section_by_name(".text") {
                            match section.data() {
                                Ok(code) => {
                                    let address = section.address();

                                    // Initialize Capstone
                                    let cs = Capstone::new()
                                        .x86()
                                        .mode(if is_64 {
                                            arch::x86::ArchMode::Mode64
                                        } else {
                                            arch::x86::ArchMode::Mode32
                                        })
                                        .syntax(arch::x86::ArchSyntax::Intel)
                                        .detail(true)
                                        .build()
                                        .expect("Failed to create Capstone object");

                                    // Disassemble instructions
                                    match cs.disasm_all(code, address) {
                                        Ok(instructions) => {
                                            for i in instructions.iter() {
                                                let mnemonic = i.mnemonic().unwrap_or("");
                                                let op_str = i.op_str().unwrap_or("");

                                                self.disassembly.push(InstructionEntry {
                                                    address: i.address(),
                                                    mnemonic: mnemonic.to_string(),
                                                    operands: op_str.to_string(),
                                                });
                                            }
                                        }
                                        Err(err) => {
                                            self.error_message =
                                                Some(format!("Disassembly failed: {}", err));
                                        }
                                    };
                                }
                                Err(err) => {
                                    self.error_message =
                                        Some(format!("Could not get section data: {}", err));
                                }
                            }
                        } else {
                            self.error_message =
                                Some("No .text section found in the binary.".to_string());
                        }
                    }
                    Err(err) => {
                        self.error_message =
                            Some(format!("Failed to parse binary: {}", err));
                    }
                },
                Err(err) => {
                    self.error_message =
                        Some(format!("Failed to read binary file: {}", err));
                }
            }
        } else {
            self.error_message = Some("No file selected.".to_string());
        }
    }

    fn toggle_theme(&mut self, ctx: &egui::Context) {
        self.theme = if self.theme == Theme::Dark {
            Theme::Light
        } else {
            Theme::Dark
        };
        self.set_theme(ctx);
    }

    fn set_theme(&self, ctx: &egui::Context) {
        match self.theme {
            Theme::Dark => {
                ctx.set_visuals(egui::Visuals::dark());
            }
            Theme::Light => {
                ctx.set_visuals(egui::Visuals::light());
            }
        }
    }

    fn set_custom_fonts(&self, ctx: &egui::Context) {
        let mut fonts = FontDefinitions::default();

        // Add a custom font (e.g., "Fira Code")
        fonts.font_data.insert(
            "FiraCode".to_string(),
            FontData::from_static(include_bytes!("../assets/FiraCode-Regular.ttf")),
        );

        // Use the font for monospaced texts
        fonts
            .families
            .entry(FontFamily::Monospace)
            .or_default()
            .insert(0, "FiraCode".to_string());

        // Set the modified font definitions
        ctx.set_fonts(fonts);

        // Adjust text sizes
        let mut style = (*ctx.style()).clone();
        style.text_styles = [
            (TextStyle::Heading, egui::FontId::new(24.0, FontFamily::Proportional)),
            (TextStyle::Body, egui::FontId::new(18.0, FontFamily::Proportional)),
            (
                TextStyle::Monospace,
                egui::FontId::new(16.0, FontFamily::Monospace),
            ),
            (
                TextStyle::Button,
                egui::FontId::new(18.0, FontFamily::Proportional),
            ),
            (
                TextStyle::Small,
                egui::FontId::new(14.0, FontFamily::Proportional),
            ),
        ]
        .into();
        ctx.set_style(style);
    }
}

fn main() -> Result<(), eframe::Error> {
    let app = BinaryAnalyzerApp::default();
    let native_options = eframe::NativeOptions {
        viewport: egui::ViewportBuilder::default().with_inner_size([900.0, 700.0]),
                ..Default::default()
    };
    eframe::run_native(
        "Binary Analyzer",
        native_options,
        Box::new(|_cc| Ok(Box::new(app))),
    )
}