//! GLTF material asset and simulator material-override encoding.

#![allow(
    clippy::assigning_clones,
    clippy::cast_possible_truncation,
    clippy::collapsible_if,
    clippy::float_cmp,
    clippy::missing_errors_doc,
    clippy::missing_panics_doc,
    clippy::must_use_candidate,
    clippy::needless_pass_by_value,
    clippy::should_implement_trait
)]

use crate::{Error, assets::GltfAlphaMode};
use libremetaverse_structured_data::{OSD, OSDMap};
use libremetaverse_types::{AssetType, Color4, UUID, Vector2, Vector3};
use serde_json::{Value, json};
use std::collections::HashMap;

const FLOAT_EPSILON: f32 = 1.192_092_9e-7;

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct GltfTextureTransform {
    pub offset: Vector2,
    pub rotation: f32,
    pub scale: Vector2,
}

impl GltfTextureTransform {
    pub fn default() -> Self {
        Self {
            offset: Vector2::zero(),
            rotation: 0.0,
            scale: Vector2 { x: 1.0, y: 1.0 },
        }
    }
    pub fn equals_with_gltf_texture_transform(&self, other: Self) -> bool {
        *self == other
    }
    pub fn equals_with_object(&self, _obj: Option<libremetaverse_types::compat::Object>) -> bool {
        false
    }
    pub fn get_hash_code(&self) -> i32 {
        (self.offset.x.to_bits()
            ^ self.offset.y.to_bits()
            ^ self.rotation.to_bits()
            ^ self.scale.x.to_bits()
            ^ self.scale.y.to_bits())
        .cast_signed()
    }
    pub fn is_default(&self) -> bool {
        self.offset.x.abs() <= FLOAT_EPSILON
            && self.offset.y.abs() <= FLOAT_EPSILON
            && self.rotation.abs() <= FLOAT_EPSILON
            && (self.scale.x - 1.0).abs() <= FLOAT_EPSILON
            && (self.scale.y - 1.0).abs() <= FLOAT_EPSILON
    }
}

#[derive(Clone, Debug)]
pub struct AssetMaterial {
    asset_id: UUID,
    asset_data: Vec<u8>,
    name: String,
    texture_ids: Vec<UUID>,
    texture_transforms: Vec<GltfTextureTransform>,
    base_color_factor: Color4,
    emissive_factor: Vector3,
    metallic_factor: f32,
    roughness_factor: f32,
    alpha_cutoff: f32,
    alpha_mode: GltfAlphaMode,
    double_sided: bool,
    override_alpha_mode: bool,
    override_double_sided: bool,
}

impl AssetMaterial {
    pub const TEXTURE_BASE_COLOR: i32 = 0;
    pub const TEXTURE_NORMAL: i32 = 1;
    pub const TEXTURE_METALLIC_ROUGHNESS: i32 = 2;
    pub const TEXTURE_EMISSIVE: i32 = 3;
    pub const TEXTURE_COUNT: i32 = 4;

    pub fn default() -> Self {
        Self {
            asset_id: UUID::zero(),
            asset_data: Vec::new(),
            name: String::new(),
            texture_ids: vec![UUID::zero(); Self::TEXTURE_COUNT as usize],
            texture_transforms: vec![GltfTextureTransform::default(); Self::TEXTURE_COUNT as usize],
            base_color_factor: Color4 {
                r: 1.0,
                g: 1.0,
                b: 1.0,
                a: 1.0,
            },
            emissive_factor: Vector3::zero(),
            metallic_factor: 1.0,
            roughness_factor: 1.0,
            alpha_cutoff: 0.5,
            alpha_mode: GltfAlphaMode::Opaque,
            double_sided: false,
            override_alpha_mode: false,
            override_double_sided: false,
        }
    }

    pub fn gltf_override_null_uuid() -> UUID {
        UUID::new_with_string("ffffffff-ffff-ffff-ffff-ffffffffffff".into())
            .expect("constant UUID is valid")
    }

    pub fn new_with_constructor() -> Result<Self, Error> {
        Ok(Self::default())
    }

    pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec<u8>) -> Result<Self, Error> {
        if asset_data.len() > crate::asset_models::MAX_ASSET_BYTES {
            return Err(Error::Argument);
        }
        let mut material = Self {
            asset_id,
            asset_data,
            ..Self::default()
        };
        if !material.decode()? {
            return Err(Error::Argument);
        }
        Ok(material)
    }

    fn slot(slot: i32) -> Result<usize, Error> {
        usize::try_from(slot)
            .ok()
            .filter(|slot| *slot < Self::TEXTURE_COUNT as usize)
            .ok_or(Error::Argument)
    }

    pub fn set_texture_id(
        &mut self,
        slot: i32,
        id: UUID,
        for_override: Option<bool>,
    ) -> Result<(), Error> {
        let slot = Self::slot(slot)?;
        self.texture_ids[slot] = if for_override.unwrap_or(false) && id == UUID::zero() {
            Self::gltf_override_null_uuid()
        } else {
            id
        };
        Ok(())
    }
    pub fn set_texture_offset(&mut self, slot: i32, offset: Vector2) -> Result<(), Error> {
        self.texture_transforms[Self::slot(slot)?].offset = offset;
        Ok(())
    }
    pub fn set_texture_scale(&mut self, slot: i32, scale: Vector2) -> Result<(), Error> {
        self.texture_transforms[Self::slot(slot)?].scale = scale;
        Ok(())
    }
    pub fn set_texture_rotation(&mut self, slot: i32, rotation: f32) -> Result<(), Error> {
        self.texture_transforms[Self::slot(slot)?].rotation = rotation;
        Ok(())
    }
    pub fn set_base_color_factor_with_color4_boolean(
        &mut self,
        mut color: Color4,
        for_override: Option<bool>,
    ) -> Result<(), Error> {
        if for_override.unwrap_or(false) && color == Self::default().base_color_factor {
            color.a -= FLOAT_EPSILON;
        }
        self.base_color_factor = color;
        Ok(())
    }
    pub fn set_emissive_factor_with_vector3_boolean(
        &mut self,
        mut color: Vector3,
        for_override: Option<bool>,
    ) -> Result<(), Error> {
        if for_override.unwrap_or(false) && color == Vector3::zero() {
            color.x += FLOAT_EPSILON;
        }
        self.emissive_factor = color;
        Ok(())
    }
    pub fn set_metallic_factor_with_single_boolean(
        &mut self,
        metallic: f32,
        for_override: Option<bool>,
    ) -> Result<(), Error> {
        if !metallic.is_finite() {
            return Err(Error::Argument);
        }
        self.metallic_factor = if for_override.unwrap_or(false) {
            metallic.min(1.0 - FLOAT_EPSILON)
        } else {
            metallic
        };
        Ok(())
    }
    pub fn set_roughness_factor_with_single_boolean(
        &mut self,
        roughness: f32,
        for_override: Option<bool>,
    ) -> Result<(), Error> {
        if !roughness.is_finite() {
            return Err(Error::Argument);
        }
        self.roughness_factor = if for_override.unwrap_or(false) {
            roughness.min(1.0 - FLOAT_EPSILON)
        } else {
            roughness
        };
        Ok(())
    }
    pub fn set_alpha_cutoff_with_single_boolean(
        &mut self,
        cutoff: f32,
        for_override: Option<bool>,
    ) -> Result<(), Error> {
        if !cutoff.is_finite() {
            return Err(Error::Argument);
        }
        self.alpha_cutoff = if for_override.unwrap_or(false) && cutoff == 0.5 {
            cutoff - FLOAT_EPSILON
        } else {
            cutoff
        };
        Ok(())
    }
    pub fn set_alpha_mode_with_gltf_alpha_mode_boolean(
        &mut self,
        mode: GltfAlphaMode,
        for_override: Option<bool>,
    ) -> Result<(), Error> {
        self.alpha_mode = mode;
        self.override_alpha_mode = for_override.unwrap_or(false);
        Ok(())
    }
    pub fn set_double_sided_with_boolean_boolean(
        &mut self,
        double_sided: bool,
        for_override: Option<bool>,
    ) -> Result<(), Error> {
        self.double_sided = double_sided;
        self.override_double_sided = for_override.unwrap_or(false);
        Ok(())
    }

    pub fn set_base_material(&mut self) -> Result<(), Error> {
        let transforms = self.texture_transforms.clone();
        let asset_id = self.asset_id;
        *self = Self::default();
        self.asset_id = asset_id;
        self.texture_transforms = transforms;
        Ok(())
    }

    pub fn apply_override(&mut self, value: Self) -> Result<(), Error> {
        let sentinel = Self::gltf_override_null_uuid();
        for slot in 0..Self::TEXTURE_COUNT as usize {
            if value.texture_ids[slot] == sentinel {
                self.texture_ids[slot] = UUID::zero();
            } else if value.texture_ids[slot] != UUID::zero() {
                self.texture_ids[slot] = value.texture_ids[slot];
            }
            if !value.texture_transforms[slot].is_default() {
                self.texture_transforms[slot] = value.texture_transforms[slot];
            }
        }
        let defaults = Self::default();
        if value.base_color_factor != defaults.base_color_factor {
            self.base_color_factor = value.base_color_factor;
        }
        if value.emissive_factor != defaults.emissive_factor {
            self.emissive_factor = value.emissive_factor;
        }
        if value.metallic_factor != defaults.metallic_factor {
            self.metallic_factor = value.metallic_factor;
        }
        if value.roughness_factor != defaults.roughness_factor {
            self.roughness_factor = value.roughness_factor;
        }
        if value.alpha_cutoff != defaults.alpha_cutoff {
            self.alpha_cutoff = value.alpha_cutoff;
        }
        if value.override_alpha_mode {
            self.alpha_mode = value.alpha_mode;
        }
        if value.override_double_sided {
            self.double_sided = value.double_sided;
        }
        Ok(())
    }

    fn alpha_name(mode: GltfAlphaMode) -> &'static str {
        match mode {
            GltfAlphaMode::Opaque => "OPAQUE",
            GltfAlphaMode::Blend => "BLEND",
            GltfAlphaMode::Mask => "MASK",
        }
    }
    fn parse_alpha(value: Option<&str>) -> Result<GltfAlphaMode, Error> {
        match value.unwrap_or("OPAQUE") {
            "OPAQUE" => Ok(GltfAlphaMode::Opaque),
            "BLEND" => Ok(GltfAlphaMode::Blend),
            "MASK" => Ok(GltfAlphaMode::Mask),
            _ => Err(Error::Argument),
        }
    }

    fn texture_json(
        &self,
        slot: usize,
        images: &mut Vec<Value>,
        textures: &mut Vec<Value>,
    ) -> Option<Value> {
        let id = self.texture_ids[slot];
        if id == UUID::zero() || id == Self::gltf_override_null_uuid() {
            return None;
        }
        let index = images.len();
        images.push(json!({"uri": id.to_string()}));
        textures.push(json!({"source": index}));
        let mut info = json!({"index": index});
        let transform = self.texture_transforms[slot];
        if !transform.is_default() {
            info["extensions"] = json!({"KHR_texture_transform": {"offset": [transform.offset.x, transform.offset.y], "scale": [transform.scale.x, transform.scale.y], "rotation": transform.rotation}});
        }
        Some(info)
    }

    pub fn to_json(&self) -> Result<String, Error> {
        let mut images = Vec::new();
        let mut textures = Vec::new();
        let base = self.texture_json(
            Self::TEXTURE_BASE_COLOR as usize,
            &mut images,
            &mut textures,
        );
        let normal = self.texture_json(Self::TEXTURE_NORMAL as usize, &mut images, &mut textures);
        let metallic = self.texture_json(
            Self::TEXTURE_METALLIC_ROUGHNESS as usize,
            &mut images,
            &mut textures,
        );
        let emissive =
            self.texture_json(Self::TEXTURE_EMISSIVE as usize, &mut images, &mut textures);
        let mut pbr = json!({"baseColorFactor": [self.base_color_factor.r,self.base_color_factor.g,self.base_color_factor.b,self.base_color_factor.a], "metallicFactor": self.metallic_factor, "roughnessFactor": self.roughness_factor});
        if let Some(value) = base {
            pbr["baseColorTexture"] = value;
        }
        if let Some(value) = metallic {
            pbr["metallicRoughnessTexture"] = value;
        }
        let mut material = json!({"name": self.name, "pbrMetallicRoughness": pbr, "emissiveFactor": [self.emissive_factor.x,self.emissive_factor.y,self.emissive_factor.z], "alphaMode": Self::alpha_name(self.alpha_mode), "alphaCutoff": self.alpha_cutoff, "doubleSided": self.double_sided});
        if let Some(value) = normal {
            material["normalTexture"] = value;
        }
        if let Some(value) = emissive {
            material["emissiveTexture"] = value;
        }
        let mut root = json!({"asset":{"version":"2.0"},"materials":[material]});
        if !images.is_empty() {
            root["images"] = Value::Array(images);
            root["textures"] = Value::Array(textures);
            root["extensionsUsed"] = json!(["KHR_texture_transform"]);
        }
        serde_json::to_string(&root).map_err(|_| Error::InvalidOperation)
    }

    pub fn encode(&mut self) -> Result<(), Error> {
        self.asset_data = self.to_json()?.into_bytes();
        Ok(())
    }
    pub fn decode(&mut self) -> Result<bool, Error> {
        let root: Value = serde_json::from_slice(&self.asset_data).map_err(|_| Error::Argument)?;
        let mat = root
            .get("materials")
            .and_then(Value::as_array)
            .and_then(|v| v.first())
            .and_then(Value::as_object)
            .ok_or(Error::Argument)?;
        self.name = mat
            .get("name")
            .and_then(Value::as_str)
            .unwrap_or_default()
            .to_owned();
        let pbr = mat.get("pbrMetallicRoughness").and_then(Value::as_object);
        if let Some(values) = pbr
            .and_then(|v| v.get("baseColorFactor"))
            .and_then(Value::as_array)
        {
            if values.len() == 4 {
                self.base_color_factor = Color4 {
                    r: number(&values[0])?,
                    g: number(&values[1])?,
                    b: number(&values[2])?,
                    a: number(&values[3])?,
                };
            }
        }
        self.metallic_factor = pbr
            .and_then(|v| v.get("metallicFactor"))
            .map(number)
            .transpose()?
            .unwrap_or(1.0);
        self.roughness_factor = pbr
            .and_then(|v| v.get("roughnessFactor"))
            .map(number)
            .transpose()?
            .unwrap_or(1.0);
        if let Some(values) = mat.get("emissiveFactor").and_then(Value::as_array) {
            if values.len() == 3 {
                self.emissive_factor = Vector3 {
                    x: number(&values[0])?,
                    y: number(&values[1])?,
                    z: number(&values[2])?,
                };
            }
        }
        self.alpha_mode = Self::parse_alpha(mat.get("alphaMode").and_then(Value::as_str))?;
        self.alpha_cutoff = mat
            .get("alphaCutoff")
            .map(number)
            .transpose()?
            .unwrap_or(0.5);
        self.double_sided = mat
            .get("doubleSided")
            .and_then(Value::as_bool)
            .unwrap_or(false);
        let textures = root.get("textures").and_then(Value::as_array);
        let images = root.get("images").and_then(Value::as_array);
        for (slot, info) in [
            (0, pbr.and_then(|v| v.get("baseColorTexture"))),
            (1, mat.get("normalTexture")),
            (2, pbr.and_then(|v| v.get("metallicRoughnessTexture"))),
            (3, mat.get("emissiveTexture")),
        ] {
            if let Some(info) = info {
                self.decode_texture(slot, info, textures, images)?;
            }
        }
        Ok(true)
    }

    fn decode_texture(
        &mut self,
        slot: usize,
        info: &Value,
        textures: Option<&Vec<Value>>,
        images: Option<&Vec<Value>>,
    ) -> Result<(), Error> {
        let index = info
            .get("index")
            .and_then(Value::as_u64)
            .and_then(|v| usize::try_from(v).ok())
            .ok_or(Error::Argument)?;
        let source = textures
            .and_then(|v| v.get(index))
            .and_then(|v| v.get("source"))
            .and_then(Value::as_u64)
            .and_then(|v| usize::try_from(v).ok())
            .ok_or(Error::Argument)?;
        let uri = images
            .and_then(|v| v.get(source))
            .and_then(|v| v.get("uri"))
            .and_then(Value::as_str)
            .ok_or(Error::Argument)?;
        self.texture_ids[slot] = UUID::new_with_string(uri.to_owned())?;
        if let Some(transform) = info.pointer("/extensions/KHR_texture_transform") {
            if let Some(v) = transform.get("offset").and_then(Value::as_array) {
                if v.len() == 2 {
                    self.texture_transforms[slot].offset = Vector2 {
                        x: number(&v[0])?,
                        y: number(&v[1])?,
                    };
                }
            }
            if let Some(v) = transform.get("scale").and_then(Value::as_array) {
                if v.len() == 2 {
                    self.texture_transforms[slot].scale = Vector2 {
                        x: number(&v[0])?,
                        y: number(&v[1])?,
                    };
                }
            }
            if let Some(v) = transform.get("rotation") {
                self.texture_transforms[slot].rotation = number(v)?;
            }
        }
        Ok(())
    }

    pub fn to_override_osd(&self) -> Result<OSDMap, Error> {
        let defaults = Self::default();
        let mut map = HashMap::new();
        let mut tex = Vec::new();
        let mut last = None;
        for (index, id) in self.texture_ids.iter().enumerate() {
            if *id != UUID::zero() {
                last = Some(index);
            }
        }
        if let Some(last) = last {
            for id in &self.texture_ids[..=last] {
                tex.push(OSD::UUID(*id));
            }
            map.insert("tex".into(), OSD::Array(tex));
        }
        if self.base_color_factor != defaults.base_color_factor {
            map.insert("bc".into(), color_osd(self.base_color_factor));
        }
        if self.emissive_factor != defaults.emissive_factor {
            map.insert("ec".into(), vector3_osd(self.emissive_factor));
        }
        if self.metallic_factor != defaults.metallic_factor {
            map.insert("mf".into(), OSD::Real(f64::from(self.metallic_factor)));
        }
        if self.roughness_factor != defaults.roughness_factor {
            map.insert("rf".into(), OSD::Real(f64::from(self.roughness_factor)));
        }
        if self.override_alpha_mode {
            map.insert("am".into(), OSD::Integer(self.alpha_mode as i32));
        }
        if self.alpha_cutoff != defaults.alpha_cutoff {
            map.insert("ac".into(), OSD::Real(f64::from(self.alpha_cutoff)));
        }
        if self.override_double_sided {
            map.insert("ds".into(), OSD::Boolean(self.double_sided));
        }
        let transforms: Vec<_> = self
            .texture_transforms
            .iter()
            .map(|t| {
                OSD::Map(HashMap::from([
                    ("o".into(), vector2_osd(t.offset)),
                    ("s".into(), vector2_osd(t.scale)),
                    ("r".into(), OSD::Real(f64::from(t.rotation))),
                ]))
            })
            .collect();
        if self.texture_transforms.iter().any(|v| !v.is_default()) {
            map.insert("ti".into(), OSD::Array(transforms));
        }
        OSDMap::new_with_dictionary(map)
    }

    pub fn from_override_osd(data: OSDMap) -> Result<Self, Error> {
        let map = data.snapshot();
        let mut value = Self::default();
        if let Some(OSD::Array(ids)) = map.get("tex") {
            for (slot, id) in ids.iter().take(Self::TEXTURE_COUNT as usize).enumerate() {
                value.texture_ids[slot] = id.as_uuid()?;
            }
        }
        if let Some(v) = map.get("bc") {
            value.base_color_factor = v.as_color4()?;
            if value.base_color_factor == Self::default().base_color_factor {
                value.base_color_factor.a -= FLOAT_EPSILON;
            }
        }
        if let Some(v) = map.get("ec") {
            value.emissive_factor = v.as_vector3()?;
            if value.emissive_factor == Vector3::zero() {
                value.emissive_factor.x += FLOAT_EPSILON;
            }
        }
        if let Some(v) = map.get("mf") {
            value.metallic_factor = (v.as_real()? as f32).min(1.0 - FLOAT_EPSILON);
        }
        if let Some(v) = map.get("rf") {
            value.roughness_factor = (v.as_real()? as f32).min(1.0 - FLOAT_EPSILON);
        }
        if let Some(v) = map.get("am") {
            value.alpha_mode = match v.as_integer()? {
                0 => GltfAlphaMode::Opaque,
                1 => GltfAlphaMode::Blend,
                2 => GltfAlphaMode::Mask,
                _ => return Err(Error::Argument),
            };
            value.override_alpha_mode = true;
        }
        if let Some(v) = map.get("ac") {
            value.alpha_cutoff = v.as_real()? as f32;
            if value.alpha_cutoff == 0.5 {
                value.alpha_cutoff -= FLOAT_EPSILON;
            }
        }
        if let Some(v) = map.get("ds") {
            value.double_sided = v.as_boolean()?;
            value.override_double_sided = true;
        }
        if let Some(OSD::Array(values)) = map.get("ti") {
            for (slot, transform) in values.iter().take(Self::TEXTURE_COUNT as usize).enumerate() {
                if let OSD::Map(t) = transform {
                    if let Some(v) = t.get("o") {
                        value.texture_transforms[slot].offset = v.as_vector2()?;
                    }
                    if let Some(v) = t.get("s") {
                        value.texture_transforms[slot].scale = v.as_vector2()?;
                    }
                    if let Some(v) = t.get("r") {
                        value.texture_transforms[slot].rotation = v.as_real()? as f32;
                    }
                }
            }
        }
        Ok(value)
    }

    pub const fn asset_type(&self) -> AssetType {
        AssetType::Material
    }
    pub fn alpha_cutoff(&self) -> f32 {
        self.alpha_cutoff
    }
    pub fn set_alpha_cutoff(&mut self, value: f32) {
        self.alpha_cutoff = value;
    }
    pub fn alpha_mode(&self) -> GltfAlphaMode {
        self.alpha_mode
    }
    pub fn set_alpha_mode(&mut self, value: GltfAlphaMode) {
        self.alpha_mode = value;
    }
    pub fn base_color_factor(&self) -> Color4 {
        self.base_color_factor
    }
    pub fn set_base_color_factor(&mut self, value: Color4) {
        self.base_color_factor = value;
    }
    pub fn double_sided(&self) -> bool {
        self.double_sided
    }
    pub fn set_double_sided(&mut self, value: bool) {
        self.double_sided = value;
    }
    pub fn emissive_factor(&self) -> Vector3 {
        self.emissive_factor
    }
    pub fn set_emissive_factor(&mut self, value: Vector3) {
        self.emissive_factor = value;
    }
    pub fn metallic_factor(&self) -> f32 {
        self.metallic_factor
    }
    pub fn set_metallic_factor(&mut self, value: f32) {
        self.metallic_factor = value;
    }
    pub fn roughness_factor(&self) -> f32 {
        self.roughness_factor
    }
    pub fn set_roughness_factor(&mut self, value: f32) {
        self.roughness_factor = value;
    }
    pub fn name(&self) -> String {
        self.name.clone()
    }
    pub fn set_name(&mut self, value: String) {
        self.name = value;
    }
    pub fn override_alpha_mode(&self) -> bool {
        self.override_alpha_mode
    }
    pub fn set_override_alpha_mode(&mut self, value: bool) {
        self.override_alpha_mode = value;
    }
    pub fn override_double_sided(&self) -> bool {
        self.override_double_sided
    }
    pub fn set_override_double_sided(&mut self, value: bool) {
        self.override_double_sided = value;
    }
    pub fn texture_ids(&self) -> Vec<UUID> {
        self.texture_ids.clone()
    }
    pub fn set_texture_ids(&mut self, value: Vec<UUID>) {
        self.texture_ids = value;
    }
    pub fn texture_transforms(&self) -> Vec<GltfTextureTransform> {
        self.texture_transforms.clone()
    }
    pub fn set_texture_transforms(&mut self, value: Vec<GltfTextureTransform>) {
        self.texture_transforms = value;
    }
}

fn number(value: &Value) -> Result<f32, Error> {
    value
        .as_f64()
        .map(|v| v as f32)
        .filter(|v| v.is_finite())
        .ok_or(Error::Argument)
}
fn vector2_osd(value: Vector2) -> OSD {
    OSD::Array(vec![
        OSD::Real(f64::from(value.x)),
        OSD::Real(f64::from(value.y)),
    ])
}
fn vector3_osd(value: Vector3) -> OSD {
    OSD::Array(vec![
        OSD::Real(f64::from(value.x)),
        OSD::Real(f64::from(value.y)),
        OSD::Real(f64::from(value.z)),
    ])
}
fn color_osd(value: Color4) -> OSD {
    OSD::Array(vec![
        OSD::Real(f64::from(value.r)),
        OSD::Real(f64::from(value.g)),
        OSD::Real(f64::from(value.b)),
        OSD::Real(f64::from(value.a)),
    ])
}
