// SPDX-FileCopyrightText: 2023 Gleb Smirnov <glebsmirnov0708@gmail.com>
//
// SPDX-License-Identifier: GPL-3.0-or-later

//! This module contains types representing action parameters.

use enum_iterator::{all, Sequence};
use std::fmt::Display;

/// Parameter metadata.
///
/// Contains the label and the type of the parameter.
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "glib", derive(glib::Variant))]
pub struct Parameter {
    /// Parameter type.
    pub type_: ParameterType,
    /// Parameter label that is displayed to the user.
    pub label: String,
}

/// Parameter type.
#[derive(
    Debug, Clone, Copy, PartialEq, Eq, Default, Sequence, serde::Serialize, serde::Deserialize,
)]
#[cfg_attr(feature = "glib", derive(glib::Variant))]
pub enum ParameterType {
    /// String.
    ///
    /// Allows to pass any text value.
    ///
    /// This is the default parameter type.
    #[default]
    String,
    /// Boolean.
    ///
    /// Allows to pass `true` or `false`
    Bool,
    /// Integer.
    ///
    /// Allows to pass any integer.
    Int,
    /// Float.
    ///
    /// Allows to pass any real number.
    Float,
}

impl ParameterType {
    /// Converts number to the parameter type.
    pub fn from_number(n: u8) -> Option<Self> {
        all::<Self>().find(|&variant| n == variant as u8)
    }
}

/// Paramever value.
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "glib", derive(glib::Variant))]
pub enum ParameterValue {
    /// String value.
    String(String),
    /// Boolean value.
    Bool(bool),
    /// Integer value.
    Int(i64),
    /// Float value.
    Float(f64),
}

impl ParameterValue {
    /// Returns value type.
    pub const fn type_(&self) -> ParameterType {
        match self {
            Self::String(_) => ParameterType::String,
            Self::Bool(_) => ParameterType::Bool,
            Self::Int(_) => ParameterType::Int,
            Self::Float(_) => ParameterType::Float,
        }
    }

    /// Unwraps string value.
    ///
    /// # Panics
    ///
    /// The function panics if the value is not string.
    pub fn string(&self) -> String {
        String::from_parameter_value(self).expect("Unexpected value type")
    }

    /// Unwraps boolean value.
    ///
    /// # Panics
    ///
    /// The function panics if the value is not boolean.
    pub fn bool(&self) -> bool {
        bool::from_parameter_value(self).expect("Unexpected value type")
    }

    /// Unwraps integer value.
    ///
    /// # Panics
    ///
    /// The function panics if the value is not integer.
    pub fn int(&self) -> i64 {
        i64::from_parameter_value(self).expect("Unexpected value type")
    }

    /// Unwraps float value.
    ///
    /// # Panics
    ///
    /// The function panics if the value is not float.
    pub fn float(&self) -> f64 {
        f64::from_parameter_value(self).expect("Unexpected value type")
    }
}

impl Display for ParameterValue {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::String(s) => s.fmt(f),
            Self::Bool(b) => f.write_str(match b {
                // This is used to make possible casting
                // boolean arguments to bool in some languages
                true => "1",
                false => "",
            }),
            Self::Int(n) => n.fmt(f),
            Self::Float(x) => x.fmt(f),
        }
    }
}

/// Trait for types that can be converted from and to parameter value.
pub trait ParameterValueType: Sized {
    /// Parameter type corresponding to the type.
    const TYPE: ParameterType;

    /// Converts parameter value to value of the type.
    fn from_parameter_value(value: &ParameterValue) -> Option<Self>;

    /// Converts value of the type to parameter value.
    fn into_parameter_value(self) -> ParameterValue;
}

impl ParameterValueType for String {
    const TYPE: ParameterType = ParameterType::String;

    fn from_parameter_value(value: &ParameterValue) -> Option<Self> {
        if let ParameterValue::String(value) = value {
            Some(value.to_owned())
        } else {
            None
        }
    }

    fn into_parameter_value(self) -> ParameterValue {
        ParameterValue::String(self)
    }
}

impl From<String> for ParameterValue {
    fn from(value: String) -> Self {
        Self::String(value)
    }
}

impl From<&str> for ParameterValue {
    fn from(value: &str) -> Self {
        Self::String(value.to_owned())
    }
}

impl ParameterValueType for bool {
    const TYPE: ParameterType = ParameterType::Bool;

    fn from_parameter_value(value: &ParameterValue) -> Option<Self> {
        if let &ParameterValue::Bool(value) = value {
            Some(value)
        } else {
            None
        }
    }

    fn into_parameter_value(self) -> ParameterValue {
        ParameterValue::Bool(self)
    }
}

impl From<bool> for ParameterValue {
    fn from(value: bool) -> Self {
        Self::Bool(value)
    }
}

impl ParameterValueType for i64 {
    const TYPE: ParameterType = ParameterType::Int;

    fn from_parameter_value(value: &ParameterValue) -> Option<Self> {
        if let &ParameterValue::Int(value) = value {
            Some(value)
        } else {
            None
        }
    }

    fn into_parameter_value(self) -> ParameterValue {
        ParameterValue::Int(self)
    }
}

impl From<i64> for ParameterValue {
    fn from(value: i64) -> Self {
        Self::Int(value)
    }
}

impl ParameterValueType for f64 {
    const TYPE: ParameterType = ParameterType::Float;

    fn from_parameter_value(value: &ParameterValue) -> Option<Self> {
        if let &ParameterValue::Float(value) = value {
            Some(value)
        } else {
            None
        }
    }

    fn into_parameter_value(self) -> ParameterValue {
        ParameterValue::Float(self)
    }
}

impl From<f64> for ParameterValue {
    fn from(value: f64) -> Self {
        Self::Float(value)
    }
}
