#[cfg(feature = "gdk")]
use gdk::prelude::*;
use serde::Serialize;
use zbus::zvariant::{SerializeDict, Type};

use crate::ResultID;

/// Detailed information of a [`ResultID`].
#[derive(SerializeDict, Type, Debug, Default)]
#[zvariant(signature = "dict")]
pub struct ResultMeta {
    id: ResultID,
    name: String,
    description: Option<String>,
    #[zvariant(rename = "clipboardText")]
    clipboard_text: Option<String>,
    icon: Option<zbus::zvariant::OwnedValue>,
    gicon: Option<String>,
    #[zvariant(rename = "icon-data")]
    icon_data: Option<IconData>,
}

impl ResultMeta {
    pub fn builder(id: ResultID, name: &str) -> ResultMetaBuilder {
        ResultMetaBuilder::new(id, name)
    }
}

/// A struct wrapping the required information to re-construct an icon with
/// [`gdk-pixbuf`](https://gtk-rs.org/gtk-rs-core/stable/0.14/docs/gdk_pixbuf/index.html).
///
/// You can make use of the `pixbuf` feature that implements `From<gdk_pixbuf::Pixbuf> for IconData`.
#[derive(Debug, Type, Serialize)]
pub struct IconData {
    pub width: i32,
    pub height: i32,
    pub rowstride: i32,
    pub has_alpha: bool,
    pub bits_per_sample: i32,
    pub n_channels: i32,
    pub data: Vec<u8>,
}

#[cfg(feature = "gdk-pixbuf")]
impl From<&gdk_pixbuf::Pixbuf> for IconData {
    fn from(pixbuf: &gdk_pixbuf::Pixbuf) -> Self {
        let data = pixbuf.read_pixel_bytes();
        Self {
            width: pixbuf.width(),
            height: pixbuf.height(),
            rowstride: pixbuf.rowstride(),
            has_alpha: pixbuf.has_alpha(),
            bits_per_sample: pixbuf.bits_per_sample(),
            n_channels: pixbuf.n_channels(),
            data: data.to_vec(),
        }
    }
}

#[cfg(feature = "gdk")]
impl From<&gdk::Texture> for IconData {
    fn from(texture: &gdk::Texture) -> Self {
        const BITS_PER_SAMPLE: i32 = 8; // This is the 8 in `MemoryFormat::R8g8b8a8`.
        const N_CHANNELS: i32 = 4; // vec!['r', 'g', 'b', 'a'].len().
        const HAS_ALPHA: bool = true; // Did I mention `a8`?
        let width = texture.width();
        let height = texture.height();

        let mut downloader = gdk::TextureDownloader::new(texture);
        downloader.set_format(gdk::MemoryFormat::R8g8b8a8);

        let (data, rowstride) = downloader.download_bytes();

        Self {
            width,
            height,
            rowstride: rowstride as i32,
            has_alpha: HAS_ALPHA,
            bits_per_sample: BITS_PER_SAMPLE,
            n_channels: N_CHANNELS,
            data: data.to_vec(),
        }
    }
}

/// Create an instance of [`ResultMeta`].
pub struct ResultMetaBuilder {
    id: String,
    name: String,
    description: Option<String>,
    clipboard_text: Option<String>,
    gicon: Option<String>,
    icon: Option<zbus::zvariant::OwnedValue>,
    icon_data: Option<IconData>,
}

impl ResultMetaBuilder {
    pub fn new(id: ResultID, name: &str) -> Self {
        Self {
            id,
            name: name.to_owned(),
            gicon: None,
            description: None,
            icon: None,
            icon_data: None,
            clipboard_text: None,
        }
    }

    /// Set a short description of the search result.
    pub fn description(mut self, description: &str) -> Self {
        self.description = Some(description.to_owned());
        self
    }

    /// Set a text to be copied to the clipboard when the result is activated.
    pub fn clipboard_text(mut self, clipboard_text: &str) -> Self {
        self.clipboard_text = Some(clipboard_text.to_owned());
        self
    }

    /// Set an icon-name or a URI/path.
    pub fn gicon(mut self, gicon: &str) -> Self {
        self.gicon = Some(gicon.to_owned());
        self
    }

    /// Set an icon serialized with [`gio::Icon::Serialize`](https://gtk-rs.org/gtk-rs-core/stable/0.14/docs/gio/prelude/trait.IconExt.html#tymethod.serialize).
    pub fn icon(mut self, icon: zbus::zvariant::OwnedValue) -> Self {
        self.icon = Some(icon);
        self
    }

    /// Set the icon as data. GNOME Shell will re-construct it using GdkPixbuf's API.
    pub fn icon_data(mut self, icon_data: IconData) -> Self {
        self.icon_data = Some(icon_data);
        self
    }

    /// Build an instance of [`ResultMeta`].
    pub fn build(self) -> ResultMeta {
        ResultMeta {
            id: self.id,
            name: self.name,
            gicon: self.gicon,
            description: self.description,
            icon: self.icon,
            icon_data: self.icon_data,
            clipboard_text: self.clipboard_text,
        }
    }
}

#[cfg(test)]
mod tests {
    use zbus::zvariant::Type;

    #[test]
    fn icon_data_signature() {
        assert_eq!(super::IconData::SIGNATURE, "(iiibiiay)");
    }
}
