use gtk::prelude::*;
use gtk::{Application, ApplicationWindow, Button, FileDialog, Picture, Scale, Box as GtkBox, ComboBoxText};
use image::{DynamicImage, ImageBuffer, RgbImage, Rgb, imageops};
use rayon::prelude::*;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::cell::RefCell;

fn main() {
    let app = Application::builder()
        .application_id("ru.risdeveau.imagefilters")
        .build();

    app.connect_activate(|app| {
        build_ui(app);
    });

    app.run();
}

fn build_ui(app: &Application) {
    let window = ApplicationWindow::builder()
        .application(app)
        .title("Image Filters")
        .default_width(800)
        .default_height(600)
        .build();
    window.set_resizable(false);

    let container = GtkBox::new(gtk::Orientation::Vertical, 5);

    let open_button = Button::with_label("Open Image");
    let picture = Picture::new();
    let brightness_slider = Scale::with_range(gtk::Orientation::Horizontal, -100.0, 100.0, 1.0);
    brightness_slider.set_value(0.0);

    let filter_combo = ComboBoxText::new();
    filter_combo.append_text("Original");
    filter_combo.append_text("Gray World");
    filter_combo.append_text("Histogram Stretch");
    filter_combo.append_text("Emboss");
    filter_combo.append_text("Blur");
    filter_combo.set_active(Some(0));

    let image_data = Rc::new(RefCell::new(None::<(PathBuf, RgbImage)>));

    fn parallel_brightness(image: &mut RgbImage, value: i16) {
        image.par_iter_mut().for_each(|p| {
            *p = p.saturating_add_signed(value.try_into().unwrap());
        });
    }

    fn gray_world(image: &mut RgbImage) {
        let (width, height) = image.dimensions();
        let total_pixels = (width * height) as f32;

        let mut sum_r = 0.0;
        let mut sum_g = 0.0;
        let mut sum_b = 0.0;

        for pixel in image.pixels() {
            sum_r += pixel[0] as f32;
            sum_g += pixel[1] as f32;
            sum_b += pixel[2] as f32;
        }

        let avg_r = sum_r / total_pixels;
        let avg_g = sum_g / total_pixels;
        let avg_b = sum_b / total_pixels;

        let avg = (avg_r + avg_g + avg_b) / 3.0;

        let kr = avg / avg_r;
        let kg = avg / avg_g;
        let kb = avg / avg_b;

        for pixel in image.pixels_mut() {
            pixel[0] = (pixel[0] as f32 * kr).min(255.0).max(0.0) as u8;
            pixel[1] = (pixel[1] as f32 * kg).min(255.0).max(0.0) as u8;
            pixel[2] = (pixel[2] as f32 * kb).min(255.0).max(0.0) as u8;
        }
    }

    fn histogram_stretch(image: &mut RgbImage) {
        let (width, height) = image.dimensions();
        let total_pixels = width * height;

        if total_pixels == 0 {
            return;
        }

        let mut min_r = 255;
        let mut max_r = 0;
        let mut min_g = 255;
        let mut max_g = 0;
        let mut min_b = 255;
        let mut max_b = 0;

        for pixel in image.pixels() {
            min_r = min_r.min(pixel[0]);
            max_r = max_r.max(pixel[0]);
            min_g = min_g.min(pixel[1]);
            max_g = max_g.max(pixel[1]);
            min_b = min_b.min(pixel[2]);
            max_b = max_b.max(pixel[2]);
        }

        if min_r == max_r && min_g == max_g && min_b == max_b {
            return;
        }

        for pixel in image.pixels_mut() {
            pixel[0] = ((pixel[0] - min_r) as f32 * 255.0 / (max_r - min_r) as f32).round() as u8;
            pixel[1] = ((pixel[1] - min_g) as f32 * 255.0 / (max_g - min_g) as f32).round() as u8;
            pixel[2] = ((pixel[2] - min_b) as f32 * 255.0 / (max_b - min_b) as f32).round() as u8;
        }
    }

    fn emboss(image: &mut RgbImage) {
        let kernel = [
            [-2.0, -1.0, 0.0],
            [-1.0,  1.0, 1.0],
            [ 0.0,  1.0, 2.0]
        ];

        let (width, height) = image.dimensions();
        let mut new_image = image.clone();

        for y in 1..height-1 {
            for x in 1..width-1 {
                let mut r = 0.0;
                let mut g = 0.0;
                let mut b = 0.0;

                for ky in 0..3 {
                    for kx in 0..3 {
                        let px = image.get_pixel(x + kx - 1, y + ky - 1);
                        let weight = kernel[ky as usize][kx as usize];
                        r += px[0] as f32 * weight;
                        g += px[1] as f32 * weight;
                        b += px[2] as f32 * weight;
                    }
                }

                r = (r + 128.0).clamp(0.0, 255.0);
                g = (g + 128.0).clamp(0.0, 255.0);
                b = (b + 128.0).clamp(0.0, 255.0);

                new_image.put_pixel(x, y, Rgb([r as u8, g as u8, b as u8]));
            }
        }

        *image = new_image;
    }

    fn apply_filter(image: &mut RgbImage, filter_name: &str) {
        match filter_name {
            "Gray World" => gray_world(image),
            "Histogram Stretch" => histogram_stretch(image),
            "Emboss" => emboss(image),
            "Blur" => *image = imageops::blur(image, 3.0),
            _ => {}
        }
    }

    {
        let window = window.clone();
        let image_data = image_data.clone();
        let picture = picture.clone();
        open_button.connect_clicked(move |_| {
            let dialog = FileDialog::builder()
                .title("Select Image")
                .build();

            let filter = gtk::FileFilter::new();
            filter.add_mime_type("image/*");
            dialog.set_default_filter(Some(&filter));

            let window = window.clone();
            let image_data = image_data.clone();
            let picture = picture.clone();
            dialog.open(
                Some(&window),
                None::<&gtk::gio::Cancellable>,
                move |result| {
                    if let Ok(file) = result {
                        if let Some(path) = file.path() {
                            match image::open(&path) {
                                Ok(img) => {
                                    let mut rgb_img = img.to_rgb8();
                                    *image_data.borrow_mut() = Some((path.to_path_buf(), rgb_img.clone()));

                                    let temp_path = Path::new("/tmp/current_image.png");
                                    if rgb_img.save(&temp_path).is_ok() {
                                        picture.set_filename(Some(temp_path));
                                    }
                                }
                                Err(e) => eprintln!("Image load error: {}", e),
                            }
                        }
                    }
                }
            );
        });
    }

    {
        let image_data = image_data.clone();
        let picture = picture.clone();
        let filter_combo = filter_combo.clone();
        brightness_slider.connect_value_changed(move |slider| {
            if let Some((path, ref mut original_img)) = image_data.borrow_mut().as_mut() {
                let mut edited_img = original_img.clone();
                let brightness_value = (slider.value() * 2.55) as i16;

                parallel_brightness(&mut edited_img, brightness_value);

                if let Some(active_filter) = filter_combo.active_text() {
                    apply_filter(&mut edited_img, &active_filter);
                }

                let temp_path = Path::new("/tmp/current_image.png");
                if edited_img.save(&temp_path).is_ok() {
                    picture.set_filename(Some(temp_path));
                }
            }
        });
    }

    {
        let image_data = image_data.clone();
        let picture = picture.clone();
        let brightness_slider = brightness_slider.clone();
        filter_combo.connect_changed(move |combo| {
            if let Some((path, ref mut original_img)) = image_data.borrow_mut().as_mut() {
                let mut edited_img = original_img.clone();
                let brightness_value = (brightness_slider.value() * 2.55) as i16;

                parallel_brightness(&mut edited_img, brightness_value);

                if let Some(active_filter) = combo.active_text() {
                    apply_filter(&mut edited_img, &active_filter);
                }

                let temp_path = Path::new("/tmp/current_image.png");
                if edited_img.save(&temp_path).is_ok() {
                    picture.set_filename(Some(temp_path));
                }
            }
        });
    }

    container.append(&open_button);
    container.append(&picture);
    container.append(&brightness_slider);
    container.append(&filter_combo);
    window.set_child(Some(&container));
    window.show();
}
