use failure::Error;
use gtk::prelude::*;
use std::ops::DerefMut;

macro_rules! get_widget {
    ($builder:expr, $wtype:ty, $name:ident) => {
        let $name: $wtype = $builder.get_object(stringify!($name)).expect(&format!("Could not find widget \"{}\"", stringify!($name)));
    };
}

macro_rules! send {
    ($sender:expr, $action:expr) => {
        if let Err(err) = $sender.send($action) {
            error!("Failed to send \"{}\" action due to {}", stringify!($action), err);
        }
    };
}

pub fn action<T, F>(thing: &T, name: &str, value: Option<&glib::VariantTy>, action: F) -> gio::SimpleAction
where
    T: gio::ActionMapExt,
    for<'r, 's> F: Fn(&'r gio::SimpleAction, Option<&glib::Variant>) + 'static,
{
    // Create a stateless, parameterless action
    let act = gio::SimpleAction::new(name, value);
    // Connect the handler
    act.connect_activate(action);
    // Add it to the map
    thing.add_action(&act);
    act
}

pub fn setup_image_filters(dialog: &gtk::FileChooserNative) {
    let all_filters = gtk::FileFilter::new();
    all_filters.set_name(Some("All Images"));
    all_filters.add_mime_type("image/png");
    all_filters.add_mime_type("image/jpeg");
    all_filters.add_mime_type("image/jpg");
    all_filters.add_mime_type("image/bmp");

    let png_filter = gtk::FileFilter::new();
    png_filter.set_name(Some("image/png"));
    png_filter.add_mime_type("image/png");
    let jpg_filter = gtk::FileFilter::new();
    jpg_filter.set_name(Some("image/jpg"));
    jpg_filter.add_mime_type("image/jpeg");
    jpg_filter.add_mime_type("image/jpg");
    let bpm_filter = gtk::FileFilter::new();
    bpm_filter.set_name(Some("image/bmp"));
    bpm_filter.add_mime_type("image/bmp");

    dialog.add_filter(&all_filters);
    dialog.add_filter(&png_filter);
    dialog.add_filter(&jpg_filter);
    dialog.add_filter(&bpm_filter);
}

pub fn blur_surface(surface: &mut cairo::ImageSurface) -> Result<cairo::ImageSurface, Error> {
    // Source https://github.com/elementary/granite/blob/master/lib/Drawing/BufferSurface.vala#L230

    // Those value gives the best blur/speed ratio (at least during my tests)
    let mut iterations = 10;
    let radius: i32 = 3;
    let channels = 4;

    let w = surface.get_width();
    let h = surface.get_height();

    if radius > w - 1 || radius > h - 1 {
        bail!("the radius is too big compared to surface size");
    }
    let stride = surface.get_stride();
    let mut pixels: Vec<u8> = surface.get_data()?.deref_mut().to_vec();

    let mut buffer: Vec<u8> = vec![0; (w * h * channels) as usize];
    let mut vmin = vec![0; w.max(h) as usize];
    let mut vmax = vec![0; w.max(h) as usize];
    let div: i32 = 2 * radius + 1;
    let mut dv: Vec<u8> = vec![0; (256 * div) as usize];
    for (i, item) in dv.iter_mut().enumerate() {
        *item = (i as i32 / div) as u8;
    }

    while iterations > 0 {
        iterations -= 1;

        for x in 0..w {
            vmin[x as usize] = (x as i32 + radius + 1).min(w - 1);
            vmax[x as usize] = (x as i32 - radius).max(0);
        }
        for y in 0..h {
            let mut asum = 0;
            let mut rsum = 0;
            let mut gsum = 0;
            let mut bsum = 0;
            let mut cur_pixel = y * w * channels;
            asum += radius * pixels[cur_pixel as usize] as i32;
            rsum += radius * pixels[(cur_pixel + 1) as usize] as i32;
            gsum += radius * pixels[(cur_pixel + 2) as usize] as i32;
            bsum += radius * pixels[(cur_pixel + 3) as usize] as i32;
            for _ in 0..=radius {
                asum += pixels[cur_pixel as usize] as i32;
                rsum += pixels[(cur_pixel + 1) as usize] as i32;
                gsum += pixels[(cur_pixel + 2) as usize] as i32;
                bsum += pixels[(cur_pixel + 3) as usize] as i32;
                cur_pixel += channels;
            }
            cur_pixel = y * w * channels;
            for x in 0..w {
                let p1 = (y * w + vmin[x as usize]) * channels;
                let p2 = (y * w + vmax[x as usize]) * channels;
                buffer[cur_pixel as usize] = dv[asum as usize];
                buffer[(cur_pixel + 1) as usize] = dv[rsum as usize];
                buffer[(cur_pixel + 2) as usize] = dv[gsum as usize];
                buffer[(cur_pixel + 3) as usize] = dv[bsum as usize];
                asum += pixels[p1 as usize] as i32 - pixels[p2 as usize] as i32;
                rsum += pixels[(p1 + 1) as usize] as i32 - pixels[(p2 + 1) as usize] as i32;
                gsum += pixels[(p1 + 2) as usize] as i32 - pixels[(p2 + 2) as usize] as i32;
                bsum += pixels[(p1 + 3) as usize] as i32 - pixels[(p2 + 3) as usize] as i32;
                cur_pixel += channels;
            }
        }
        for y in 0..h {
            vmin[y as usize] = (y + radius + 1).min(h - 1) * w;
            vmax[y as usize] = (y - radius).max(0) * w;
        }
        for x in 0..w {
            let mut asum = 0;
            let mut rsum = 0;
            let mut gsum = 0;
            let mut bsum = 0;
            let mut cur_pixel = x * channels;
            asum += radius * buffer[cur_pixel as usize] as i32;
            rsum += radius * buffer[(cur_pixel + 1) as usize] as i32;
            gsum += radius * buffer[(cur_pixel + 2) as usize] as i32;
            bsum += radius * buffer[(cur_pixel + 3) as usize] as i32;
            for _ in 0..=radius {
                asum += buffer[cur_pixel as usize] as i32;
                rsum += buffer[(cur_pixel + 1) as usize] as i32;
                gsum += buffer[(cur_pixel + 2) as usize] as i32;
                bsum += buffer[(cur_pixel + 3) as usize] as i32;
                cur_pixel += w * channels;
            }
            cur_pixel = x * channels;
            for y in 0..h {
                let p1 = (x + vmin[y as usize]) * channels;
                let p2 = (x + vmax[y as usize]) * channels;
                pixels[cur_pixel as usize] = dv[asum as usize] as u8;
                pixels[(cur_pixel + 1) as usize] = dv[rsum as usize] as u8;
                pixels[(cur_pixel + 2) as usize] = dv[gsum as usize] as u8;
                pixels[(cur_pixel + 3) as usize] = dv[bsum as usize] as u8;
                asum += buffer[p1 as usize] as i32 - buffer[p2 as usize] as i32;
                rsum += buffer[(p1 + 1) as usize] as i32 - buffer[(p2 + 1) as usize] as i32;
                gsum += buffer[(p1 + 2) as usize] as i32 - buffer[(p2 + 2) as usize] as i32;
                bsum += buffer[(p1 + 3) as usize] as i32 - buffer[(p2 + 3) as usize] as i32;
                cur_pixel += w * channels;
            }
        }
    }
    let surface = cairo::ImageSurface::create_for_data(pixels, cairo::Format::ARgb32, w as i32, h as i32, stride).unwrap();
    Ok(surface)
}
