use serde::{Deserialize, Serialize};

use super::asset_uuid::AssetUuid;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SceneAudioSource {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub audio_uuid: Option<AssetUuid>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub audio_name: Option<String>,
    #[serde(default = "default_volume")]
    pub volume: f32,
    #[serde(default)]
    pub looping: bool,
    #[serde(default)]
    pub playing: bool,
    #[serde(default)]
    pub spatial: bool,
    #[serde(default)]
    pub reverb: bool,
}

fn default_volume() -> f32 {
    1.0
}

impl SceneAudioSource {
    pub fn from_name(name: impl Into<String>) -> Self {
        Self {
            audio_uuid: None,
            audio_name: Some(name.into()),
            volume: 1.0,
            looping: false,
            playing: false,
            spatial: false,
            reverb: false,
        }
    }

    pub fn from_uuid(uuid: AssetUuid) -> Self {
        Self {
            audio_uuid: Some(uuid),
            audio_name: None,
            volume: 1.0,
            looping: false,
            playing: false,
            spatial: false,
            reverb: false,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScenePrefabInstance {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub prefab_uuid: Option<AssetUuid>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub prefab_path: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub prefab_name: Option<String>,
}

impl ScenePrefabInstance {
    pub fn from_uuid(uuid: AssetUuid) -> Self {
        Self {
            prefab_uuid: Some(uuid),
            prefab_path: None,
            prefab_name: None,
        }
    }

    pub fn from_path(path: impl Into<String>) -> Self {
        Self {
            prefab_uuid: None,
            prefab_path: Some(path.into()),
            prefab_name: None,
        }
    }

    pub fn from_name(name: impl Into<String>) -> Self {
        Self {
            prefab_uuid: None,
            prefab_path: None,
            prefab_name: Some(name.into()),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum EmbeddedTexture {
    Rgba {
        #[serde(with = "base64_bytes")]
        rgba_data: Vec<u8>,
        width: u32,
        height: u32,
    },
    Png {
        #[serde(with = "base64_bytes")]
        png_data: Vec<u8>,
    },
}

mod base64_bytes {
    use base64::{Engine, engine::general_purpose::STANDARD};
    use serde::{Deserialize, Deserializer, Serialize, Serializer};

    pub fn serialize<S: Serializer>(data: &[u8], serializer: S) -> Result<S::Ok, S::Error> {
        STANDARD.encode(data).serialize(serializer)
    }

    pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<u8>, D::Error> {
        let string = String::deserialize(deserializer)?;
        STANDARD.decode(&string).map_err(serde::de::Error::custom)
    }
}

impl EmbeddedTexture {
    pub fn from_rgba(rgba_data: Vec<u8>, width: u32, height: u32) -> Self {
        Self::Rgba {
            rgba_data,
            width,
            height,
        }
    }

    pub fn from_png(png_data: Vec<u8>) -> Self {
        Self::Png { png_data }
    }

    pub fn to_rgba(&self) -> Option<(Vec<u8>, u32, u32)> {
        match self {
            Self::Rgba {
                rgba_data,
                width,
                height,
            } => Some((rgba_data.clone(), *width, *height)),
            Self::Png { png_data } => {
                if let Ok(img) = image::load_from_memory(png_data) {
                    let rgba = img.to_rgba8();
                    let (width, height) = rgba.dimensions();
                    Some((rgba.into_raw(), width, height))
                } else {
                    None
                }
            }
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmbeddedAudio {
    #[serde(with = "base64_bytes")]
    pub data: Vec<u8>,
}

impl EmbeddedAudio {
    pub fn from_bytes(data: Vec<u8>) -> Self {
        Self { data }
    }

    pub fn data(&self) -> &[u8] {
        &self.data
    }
}
