// SPDX-License-Identifier: GPL-3.0-or-later
use gettextrs::gettext;

use gtk::subclass::prelude::*;
use gtk::{CompositeTemplate, prelude::*};
use gtk::{gdk, glib};

use crate::image;

#[derive(Debug, Clone, Copy, PartialEq, glib::Enum)]
#[enum_type(name = "EmblemSymbolicColor")]
pub enum SymbolicColor {
    Auto,
    Light,
    Dark,
}

impl Default for SymbolicColor {
    fn default() -> Self {
        Self::Auto
    }
}

impl From<&str> for SymbolicColor {
    fn from(color: &str) -> Self {
        match color {
            "light" => SymbolicColor::Light,
            "dark" => SymbolicColor::Dark,
            _ => SymbolicColor::Auto,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, glib::Enum)]
#[enum_type(name = "EmblemShape")]
pub enum Shape {
    Circle,
    Square,
}

impl Default for Shape {
    fn default() -> Self {
        Self::Square
    }
}

impl From<&str> for Shape {
    fn from(shape: &str) -> Self {
        match shape {
            "circle" => Shape::Circle,
            _ => Shape::Square,
        }
    }
}

mod imp {
    use super::*;

    use std::cell::{Cell, RefCell};

    use glib::Properties;

    #[derive(Debug, CompositeTemplate, Properties)]
    #[template(resource = "/org/gnome/design/Emblem/ui/export.ui")]
    #[properties(wrapper_type = super::Export)]
    pub struct Export {
        #[property(get, set = Self::set_symbolic_color, explicit_notify, default)]
        pub symbolic_color: Cell<SymbolicColor>,
        #[property(get, set = Self::set_gradient, explicit_notify)]
        pub gradient: Cell<gdk::RGBA>,
        #[property(get, set = Self::set_color, explicit_notify)]
        pub color: Cell<gdk::RGBA>,
        #[property(name = "symbolic", get, set = Self::set_symbolic, explicit_notify)]
        pub symbolic_bytes: RefCell<Option<glib::Bytes>>,
        #[property(get, set = Self::set_shape, explicit_notify, default)]
        pub shape: Cell<Shape>,

        #[template_child]
        pub child: TemplateChild<gtk::Widget>,
        #[template_child]
        pub emblem: TemplateChild<gtk::Image>,
    }

    impl Export {
        fn set_shape(&self, shape: Shape) {
            if self.shape.replace(shape) != shape {
                let widget = self.obj();
                self.shape.set(shape);
                widget.notify_shape();

                widget.draw();
            }
        }

        fn set_symbolic_color(&self, symbolic_color: SymbolicColor) {
            if symbolic_color != self.symbolic_color.replace(symbolic_color) {
                self.obj().notify_symbolic_color();
                self.obj().draw();
            }
        }

        fn set_symbolic(&self, bytes: Option<&glib::Bytes>) {
            if bytes != self.symbolic_bytes.replace(bytes.cloned()).as_ref() {
                self.obj().notify_symbolic();
                self.obj().draw();
            }
        }

        fn set_gradient(&self, color: gdk::RGBA) {
            if color != self.gradient.replace(color) {
                self.obj().notify_gradient();
                self.obj().draw();
            }
        }

        fn set_color(&self, color: gdk::RGBA) {
            if color != self.color.replace(color) {
                self.obj().notify_color();
                self.obj().draw();
            }
        }
    }

    impl Default for Export {
        fn default() -> Self {
            Self {
                color: Cell::new(gdk::RGBA::BLACK),
                gradient: Cell::new(gdk::RGBA::BLACK),
                symbolic_bytes: RefCell::default(),
                symbolic_color: Cell::default(),
                shape: Cell::default(),
                child: TemplateChild::default(),
                emblem: TemplateChild::default(),
            }
        }
    }

    #[glib::object_subclass]
    impl ObjectSubclass for Export {
        const NAME: &'static str = "Export";
        type Type = super::Export;
        type ParentType = gtk::Widget;

        fn class_init(klass: &mut Self::Class) {
            klass.set_layout_manager_type::<gtk::BinLayout>();
            klass.bind_template();
        }

        fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
            obj.init_template();
        }
    }

    #[glib::derived_properties]
    impl ObjectImpl for Export {
        fn dispose(&self) {
            self.dispose_template();
        }
    }
    impl WidgetImpl for Export {}
}

glib::wrapper! {
    pub struct Export(ObjectSubclass<imp::Export>)
        @extends gtk::Widget,
        @implements gtk::ConstraintTarget, gtk::Buildable, gtk::Accessible;
}

impl Default for Export {
    fn default() -> Self {
        glib::Object::new()
    }
}

impl Export {
    fn draw(&self) {
        let svg_owned = image::generate_svg(&self.config());
        match crate::SvgPaintable::new(&svg_owned) {
            Ok(paintable) => {
                self.imp().emblem.set_paintable(Some(&paintable));
                self.bind_property("scale-factor", &paintable, "scale-factor")
                    .sync_create()
                    .build();
            }
            Err(err) => {
                log::error!("Could not draw SVG: {err}");
            }
        };
    }

    pub async fn save_png_action(&self) -> anyhow::Result<()> {
        let window = self.root().and_downcast::<crate::Window>().unwrap();

        let dialog = gtk::FileDialog::new();
        dialog.set_title(&gettext("Save PNG"));
        // TRANSLATORS Only translate the word logo, not the file format.
        dialog.set_initial_name(Some(&gettext("logo.png")));
        let dest = match dialog.save_future(Some(&window)).await {
            Err(err) if err.matches(gtk::DialogError::Dismissed) => return Ok(()),
            res => res?,
        };

        let config = self.config();
        image::save_png(&config, &dest).await
    }

    pub async fn save_svg_action(&self) -> anyhow::Result<()> {
        let window = self.root().and_downcast::<crate::Window>().unwrap();

        let dialog = gtk::FileDialog::new();
        dialog.set_title(&gettext("Save SVG"));
        // TRANSLATORS Only translate the world logo, not the file format.
        dialog.set_initial_name(Some(&gettext("logo.svg")));
        let dest = match dialog.save_future(Some(&window)).await {
            Err(err) if err.matches(gtk::DialogError::Dismissed) => return Ok(()),
            res => res?,
        };

        let config = self.config();
        image::save_svg(&config, &dest).await
    }

    fn config(&self) -> image::Config {
        let border_radius = if matches!(self.shape(), Shape::Circle) {
            128
        } else {
            0
        };
        image::Config {
            size: 256,
            border_radius,
            symbolic: self.symbolic(),
            symbolic_color: self.symbolic_color(),
            gradient_start: self.color(),
            gradient_end: self.gradient(),
        }
    }
}
