Enhance the codes UI interface massively
Fix the issue where settings just don't apply ( like i move the slider and it snaps back / any checkbox goes back to its default )
use eframe::egui;
use std::fs::File;
use std::io::{self, Read, Write};
use std::path::PathBuf;

struct StringDumperApp {
    // Data related to the application
    strings: Vec<String>,
    file_path: Option<PathBuf>,
    search_text: String,
    filtered_strings: Vec<String>,

    // Settings
    min_length: usize,
    include_ascii: bool,
    include_utf16le: bool,
    include_utf16be: bool,
    settings_open: bool,
    about_open: bool,
}

impl Default for StringDumperApp {
    fn default() -> Self {
        Self {
            strings: Vec::new(),
            file_path: None,
            search_text: String::new(),
            filtered_strings: Vec::new(),
            min_length: 4,
            include_ascii: true,
            include_utf16le: true,
            include_utf16be: true,
            settings_open: false,
            about_open: false,
        }
    }
}

impl eframe::App for StringDumperApp {

    fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
        self.ui_menu(ctx, frame);

        if self.settings_open {
            self.open_settings(ctx);
        }

        if self.about_open {
            self.show_about(ctx);
        }

        egui::CentralPanel::default().show(ctx, |ui| {
            self.ui_central_panel(ui);
        });
    }
}

impl StringDumperApp {
    fn ui_menu(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        use egui::menu;

        egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
            ui.horizontal_wrapped(|ui| {
                ui.visuals_mut().button_frame = false;

                menu::bar(ui, |ui| {
                    ui.menu_button("File", |ui| {
                        if ui.button("Open File").clicked() {
                            if let Some(path) = rfd::FileDialog::new().pick_file() {
                                self.file_path = Some(path);
                                self.load_strings();
                            }
                            ui.close_menu();
                        }
                        if ui.button("Save Strings").clicked() {
                            if let Some(path) = rfd::FileDialog::new().save_file() {
                                self.save_strings(&path);
                            }
                            ui.close_menu();
                        }
                        if ui.button("Exit").clicked() {
                            std::process::exit(0);
                        }
                    });

                    ui.menu_button("Edit", |ui| {
                        if ui.button("Settings").clicked() {
                            self.settings_open = true;
                            ui.close_menu();
                        }
                    });

                    ui.menu_button("Help", |ui| {
                        if ui.button("About").clicked() {
                            self.about_open = true;
                            ui.close_menu();
                        }
                    });
                });
            });
        });
    }

    fn ui_central_panel(&mut self, ui: &mut egui::Ui) {
        ui.vertical_centered(|ui| {
            ui.heading("Advanced String Dumper");
        });

        ui.separator();

        if let Some(path) = &self.file_path {
            ui.horizontal_wrapped(|ui| {
                ui.label("Loaded File:");
                ui.monospace(format!("{}", path.display()));
            });
        } else {
            ui.label("No file loaded.");
        }

        ui.horizontal(|ui| {
            ui.label("Search:");
            if ui
                .text_edit_singleline(&mut self.search_text)
                .changed()
            {
                self.filter_strings();
            }
            if ui.button("Clear").clicked() {
                self.search_text.clear();
                self.filter_strings();
            }
        });

        ui.separator();

        egui::ScrollArea::vertical().show(ui, |ui| {
            for s in &self.filtered_strings {
                ui.monospace(s);
            }
        });
    }

    fn load_strings(&mut self) {
        if let Some(path) = &self.file_path {
            match extract_strings_from_file(
                path,
                self.min_length,
                self.include_ascii,
                self.include_utf16le,
                self.include_utf16be,
            ) {
                Ok(strings) => {
                    self.strings = strings;
                    self.filter_strings();
                }
                Err(e) => {
                    eprintln!("Error extracting strings: {}", e);
                }
            }
        }
    }

    fn filter_strings(&mut self) {
        if self.search_text.is_empty() {
            self.filtered_strings = self.strings.clone();
        } else {
            let search_lower = self.search_text.to_lowercase();
            self.filtered_strings = self
                .strings
                .iter()
                .filter(|s| s.to_lowercase().contains(&search_lower))
                .cloned()
                .collect();
        }
    }

    fn save_strings(&self, path: &std::path::Path) {
        if let Ok(mut file) = File::create(path) {
            for s in &self.filtered_strings {
                if let Err(e) = writeln!(file, "{}", s) {
                    eprintln!("Error writing to file: {}", e);
                    break;
                }
            }
        } else {
            eprintln!("Error creating file");
        }
    }

    fn open_settings(&mut self, ctx: &egui::Context) {
        // Create local mutable copies of the settings
        let mut min_length = self.min_length;
        let mut include_ascii = self.include_ascii;
        let mut include_utf16le = self.include_utf16le;
        let mut include_utf16be = self.include_utf16be;

        // Variable to track if "Apply" was clicked
        let mut apply_changes = false;

        // Open the settings window
        egui::Window::new("Settings")
            .open(&mut self.settings_open)
            .show(ctx, |ui| {
                ui.label("Minimum String Length:");
                ui.add(egui::Slider::new(&mut min_length, 1..=20));

                ui.separator();
                ui.label("String Encodings to Include:");
                ui.checkbox(&mut include_ascii, "ASCII");
                ui.checkbox(&mut include_utf16le, "UTF-16 Little Endian");
                ui.checkbox(&mut include_utf16be, "UTF-16 Big Endian");

                ui.separator();

                if ui.button("Apply").clicked() {
                    apply_changes = true;
                }
            });

        // Apply changes after the closure to avoid borrowing `self` within it
        if apply_changes {
            self.min_length = min_length;
            self.include_ascii = include_ascii;
            self.include_utf16le = include_utf16le;
            self.include_utf16be = include_utf16be;
            self.load_strings();
        }
    }

    fn show_about(&mut self, ctx: &egui::Context) {
        egui::Window::new("About")
            .open(&mut self.about_open)
            .show(ctx, |ui| {
                ui.label("Advanced String Dumper");
                ui.label("Version 1.0");
                ui.add_space(10.0);
                ui.label("Developed using Rust and egui.");
                ui.label("This application extracts strings from binary files using advanced methods.");
            });
    }
}

fn extract_strings_from_file(
    path: &std::path::Path,
    min_length: usize,
    include_ascii: bool,
    include_utf16le: bool,
    include_utf16be: bool,
) -> io::Result<Vec<String>> {
    let mut file = File::open(path)?;
    let mut buffer = Vec::new();
    file.read_to_end(&mut buffer)?;

    let strings = extract_strings(
        &buffer,
        min_length,
        include_ascii,
        include_utf16le,
        include_utf16be,
    );

    Ok(strings)
}

fn extract_strings(
    data: &[u8],
    min_length: usize,
    include_ascii: bool,
    include_utf16le: bool,
    include_utf16be: bool,
) -> Vec<String> {
    let mut strings = Vec::new();

    if include_ascii {
        strings.extend(extract_ascii_strings(data, min_length));
    }

    if include_utf16le {
        strings.extend(extract_unicode_strings(data, min_length, true));
    }

    if include_utf16be {
        strings.extend(extract_unicode_strings(data, min_length, false));
    }

    strings
}

fn extract_ascii_strings(data: &[u8], min_length: usize) -> Vec<String> {
    let mut strings = Vec::new();
    let mut current_string = Vec::new();

    for &byte in data {
        if (byte.is_ascii_graphic() || byte == b' ') {
            current_string.push(byte);
        } else {
            if current_string.len() >= min_length {
                if let Ok(s) = String::from_utf8(current_string.clone()) {
                    strings.push(s);
                }
            }
            current_string.clear();
        }
    }

    // Check if there's a string at the end
    if current_string.len() >= min_length {
        if let Ok(s) = String::from_utf8(current_string.clone()) {
            strings.push(s);
        }
    }

    strings
}

fn extract_unicode_strings(data: &[u8], min_length: usize, little_endian: bool) -> Vec<String> {
    let mut strings = Vec::new();
    let mut current_string = Vec::new();
    let mut i = 0;

    while i + 1 < data.len() {
        let b1 = data[i];
        let b2 = data[i + 1];

        let code_unit = if little_endian {
            u16::from_le_bytes([b1, b2])
        } else {
            u16::from_be_bytes([b1, b2])
        };

        if code_unit == 0 {
            if current_string.len() >= min_length {
                let s = String::from_utf16_lossy(&current_string);
                strings.push(s);
            }
            current_string.clear();
        } else {
            current_string.push(code_unit);
        }

        i += 2;
    }

    // Check if there's a string at the end
    if current_string.len() >= min_length {
        let s = String::from_utf16_lossy(&current_string);
        strings.push(s);
    }

    strings
}

fn main() {
    let app = StringDumperApp::default();
    let native_options = eframe::NativeOptions::default();
    eframe::run_native("String Dumper", native_options, Box::new(|_cc| Ok(Box::new(app)))).unwrap();
}