use serde::{Deserialize, Serialize};

use crate::GCAbiConversion;

/// The quality of a datapoint value
/// Represents a u16 bit field similar to the IEC 61850 protocol with the following structure:
///
/// | bits | Attribute name | Attribute value |
/// |---|---|---|
/// | 0-1 | Validity | Good(00) / Invalid(01) / Reserved(10) / Questionable(11) |
/// | 2 | Overflow | TRUE(1) / FALSE(0) |
/// | 3 | OutOfRange | TRUE(1) / FALSE(0) |
/// | 4 | BadReference | TRUE(1) / FALSE(0) |
/// | 5 | Oscillatory | TRUE(1) / FALSE(0) |
/// | 6 | Failure | TRUE(1) / FALSE(0) |
/// | 7 | OldData | TRUE(1) / FALSE(0) |
/// | 8 | Inconsistent | TRUE(1) / FALSE(0) |
/// | 9 | Inaccurate | TRUE(1) / FALSE(0) |
/// | 10 | Source | Process(0) / Substituted(1) |
/// | 11 | Test | TRUE(1) / FALSE(0) |
/// | 12 | OperatorBlocked | TRUE(1) / FALSE(0) |
///
/// Each attribute has it's own setter and query method.
#[derive(PartialEq, Debug, Clone, Deserialize, Serialize)]
#[repr(transparent)]
pub struct GCDatapointValueQuality(gc_abi_sys::GCDatapointValueQuality);

impl GCDatapointValueQuality {
    pub fn new_good() -> Self {
        GCDatapointValueQuality(0)
    }

    pub fn set_good(&mut self) -> &mut Self {
        self.0 &= !gc_abi_sys::eGCDatapointValueQuality_GC_DATAPOINT_QUALITY_FIRST_TWO_BITS_MASK as u16;
        self
    }

    pub fn set_invalid(&mut self) -> &mut Self {
        self.0 &= !gc_abi_sys::eGCDatapointValueQuality_GC_DATAPOINT_QUALITY_FIRST_TWO_BITS_MASK as u16;
        self.0 |= gc_abi_sys::eGCDatapointValueQuality_GC_DATAPOINT_QUALITY_INVALID as u16;
        self
    }

    pub fn set_questionable(&mut self) -> &mut Self {
        self.0 &= !gc_abi_sys::eGCDatapointValueQuality_GC_DATAPOINT_QUALITY_FIRST_TWO_BITS_MASK as u16;
        self.0 |= gc_abi_sys::eGCDatapointValueQuality_GC_DATAPOINT_QUALITY_QUESTIONABLE as u16;
        self
    }

    pub fn set_overflow(&mut self) -> &mut Self {
        self.0 |= gc_abi_sys::eGCDatapointValueQuality_GC_DATAPOINT_QUALITY_OVERFLOW as u16;
        self
    }

    pub fn set_out_of_range(&mut self) -> &mut Self {
        self.0 |= gc_abi_sys::eGCDatapointValueQuality_GC_DATAPOINT_QUALITY_OUT_OF_RANGE as u16;
        self
    }

    pub fn set_bad_reference(&mut self) -> &mut Self {
        self.0 |= gc_abi_sys::eGCDatapointValueQuality_GC_DATAPOINT_QUALITY_BAD_REFERENCE as u16;
        self
    }

    pub fn set_oscillatory(&mut self) -> &mut Self {
        self.0 |= gc_abi_sys::eGCDatapointValueQuality_GC_DATAPOINT_QUALITY_OSCILLATORY as u16;
        self
    }

    pub fn set_failure(&mut self) -> &mut Self {
        self.0 |= gc_abi_sys::eGCDatapointValueQuality_GC_DATAPOINT_QUALITY_FAILURE as u16;
        self
    }

    pub fn set_old_data(&mut self) -> &mut Self {
        self.0 |= gc_abi_sys::eGCDatapointValueQuality_GC_DATAPOINT_QUALITY_OLD_DATA as u16;
        self
    }

    pub fn set_inconsistent(&mut self) -> &mut Self {
        self.0 |= gc_abi_sys::eGCDatapointValueQuality_GC_DATAPOINT_QUALITY_INCONSISTENT as u16;
        self
    }

    pub fn set_inaccurate(&mut self) -> &mut Self {
        self.0 |= gc_abi_sys::eGCDatapointValueQuality_GC_DATAPOINT_QUALITY_INACCURATE as u16;
        self
    }

    pub fn set_source_processing(&mut self) -> &mut Self {
        self.0 |= gc_abi_sys::eGCDatapointValueQuality_GC_DATAPOINT_QUALITY_SOURCE as u16;
        self
    }

    pub fn set_test(&mut self) -> &mut Self {
        self.0 |= gc_abi_sys::eGCDatapointValueQuality_GC_DATAPOINT_QUALITY_TEST as u16;
        self
    }

    pub fn set_operator_blocked(&mut self) -> &mut Self {
        self.0 |= gc_abi_sys::eGCDatapointValueQuality_GC_DATAPOINT_QUALITY_OPERATOR_BLOCKED as u16;
        self
    }

    pub fn is_good(&self) -> bool {
        self.0 & gc_abi_sys::eGCDatapointValueQuality_GC_DATAPOINT_QUALITY_FIRST_TWO_BITS_MASK as u16 == 0
    }

    pub fn is_invalid(&self) -> bool {
        self.0 & gc_abi_sys::eGCDatapointValueQuality_GC_DATAPOINT_QUALITY_FIRST_TWO_BITS_MASK as u16
            == gc_abi_sys::eGCDatapointValueQuality_GC_DATAPOINT_QUALITY_INVALID as u16
    }

    pub fn is_questionable(&self) -> bool {
        self.0 & gc_abi_sys::eGCDatapointValueQuality_GC_DATAPOINT_QUALITY_FIRST_TWO_BITS_MASK as u16
            == gc_abi_sys::eGCDatapointValueQuality_GC_DATAPOINT_QUALITY_QUESTIONABLE as u16
    }

    pub fn is_overflow(&self) -> bool {
        self.0 & gc_abi_sys::eGCDatapointValueQuality_GC_DATAPOINT_QUALITY_OVERFLOW as u16 != 0
    }

    pub fn is_out_of_range(&self) -> bool {
        self.0 & gc_abi_sys::eGCDatapointValueQuality_GC_DATAPOINT_QUALITY_OUT_OF_RANGE as u16 != 0
    }

    pub fn is_bad_reference(&self) -> bool {
        self.0 & gc_abi_sys::eGCDatapointValueQuality_GC_DATAPOINT_QUALITY_BAD_REFERENCE as u16 != 0
    }

    pub fn is_oscillatory(&self) -> bool {
        self.0 & gc_abi_sys::eGCDatapointValueQuality_GC_DATAPOINT_QUALITY_OSCILLATORY as u16 != 0
    }

    pub fn is_failure(&self) -> bool {
        self.0 & gc_abi_sys::eGCDatapointValueQuality_GC_DATAPOINT_QUALITY_FAILURE as u16 != 0
    }

    pub fn is_old_data(&self) -> bool {
        self.0 & gc_abi_sys::eGCDatapointValueQuality_GC_DATAPOINT_QUALITY_OLD_DATA as u16 != 0
    }

    pub fn is_inconsistent(&self) -> bool {
        self.0 & gc_abi_sys::eGCDatapointValueQuality_GC_DATAPOINT_QUALITY_INCONSISTENT as u16 != 0
    }

    pub fn is_inaccurate(&self) -> bool {
        self.0 & gc_abi_sys::eGCDatapointValueQuality_GC_DATAPOINT_QUALITY_INACCURATE as u16 != 0
    }

    pub fn is_source_processing(&self) -> bool {
        self.0 & gc_abi_sys::eGCDatapointValueQuality_GC_DATAPOINT_QUALITY_SOURCE as u16 != 0
    }

    pub fn is_test(&self) -> bool {
        self.0 & gc_abi_sys::eGCDatapointValueQuality_GC_DATAPOINT_QUALITY_TEST as u16 != 0
    }

    pub fn is_operator_blocked(&self) -> bool {
        self.0 & gc_abi_sys::eGCDatapointValueQuality_GC_DATAPOINT_QUALITY_OPERATOR_BLOCKED as u16 != 0
    }
}

unsafe impl GCAbiConversion for GCDatapointValueQuality {
    type CType = gc_abi_sys::GCDatapointValueQuality;

    fn from_c(c_type: Self::CType) -> Self {
        GCDatapointValueQuality(c_type)
    }

    fn into_c(self) -> Self::CType {
        self.0
    }
}

impl From<u16> for GCDatapointValueQuality {
    fn from(quality: u16) -> Self {
        GCDatapointValueQuality(quality)
    }
}

impl From<GCDatapointValueQuality> for u16 {
    fn from(quality: GCDatapointValueQuality) -> Self {
        quality.0
    }
}

#[cfg(test)]
mod tests {

    use super::*;

    #[test]
    fn test_quality_flags_1() {
        let mut quality = GCDatapointValueQuality::new_good();
        quality.set_invalid(); // This should ne overrided by the questionable flag
        quality.set_questionable();
        quality.set_overflow();
        quality.set_oscillatory();
        quality.set_source_processing();

        assert!(!quality.is_good());
        assert!(!quality.is_invalid());
        assert!(quality.is_questionable());
        assert!(quality.is_overflow());
        assert!(!quality.is_out_of_range());
        assert!(!quality.is_bad_reference());
        assert!(quality.is_oscillatory());
        assert!(!quality.is_failure());
        assert!(!quality.is_old_data());
        assert!(!quality.is_inconsistent());
        assert!(!quality.is_inaccurate());
        assert!(quality.is_source_processing());
        assert!(!quality.is_test());
        assert!(!quality.is_operator_blocked());
    }

    #[test]
    fn test_quality_flags_2() {
        let mut quality = GCDatapointValueQuality::new_good();
        quality.set_out_of_range();
        quality.set_bad_reference();
        quality.set_failure();
        quality.set_old_data();
        quality.set_inconsistent();
        quality.set_inaccurate();
        quality.set_test();
        quality.set_operator_blocked();

        assert!(quality.is_good());
        assert!(!quality.is_invalid());
        assert!(!quality.is_questionable());
        assert!(!quality.is_overflow());
        assert!(quality.is_out_of_range());
        assert!(quality.is_bad_reference());
        assert!(!quality.is_oscillatory());
        assert!(quality.is_failure());
        assert!(quality.is_old_data());
        assert!(quality.is_inconsistent());
        assert!(quality.is_inaccurate());
        assert!(!quality.is_source_processing());
        assert!(quality.is_test());
        assert!(quality.is_operator_blocked());
    }

    #[test]
    fn test_conversion() {
        let quality = GCDatapointValueQuality::new_good();
        let raw_quality: gc_abi_sys::GCDatapointValueQuality = quality.clone().into_c();
        let quality_ref: &GCDatapointValueQuality = unsafe { GCDatapointValueQuality::from_c_ptr(&raw_quality) };

        assert_eq!(quality.0, quality_ref.0);
    }

    #[test]
    fn test_u16_conversion() {
        let quality = GCDatapointValueQuality::new_good();
        let raw_quality: u16 = quality.clone().into();
        let quality_ref: GCDatapointValueQuality = raw_quality.into();

        assert_eq!(quality.0, quality_ref.0);
    }
}
