//
// Copyright (C) 2024 Automated Design Corp.. All Rights Reserved.
// Created Date: 2024-10-23 06:03:57
// -----
// Last Modified: 2024-11-13 08:30:41
// -----
//
//

use anyhow::anyhow;
use indexmap::IndexMap;
use num_traits::FromPrimitive;
use serde::{Deserialize, Deserializer, Serialize, Serializer, ser::SerializeStruct};

use crate::variant::{VariantTypeId, VariantValue};

/// This is a variant type that can be exchanged with a typical industrial controller.
/// Industrial controllers typically won't exchange JSON structures, which we would
/// normally use with the MechUtil variant module.
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct MechCommandRegisterValue {
    // The data type serialized into this register.
    pub type_id: VariantTypeId,
    // Length of the value, in bytes
    pub value_len: u32,
    // Number of columns in the data.
    // For when there are multiples of the same value type.
    pub num_columns: u32,
    // Number of rows in the data.
    // For when the data is a small matrix.
    // Only valid when nColumns > 1.
    pub num_rows: u32,
    // Serialized bytes of the value.
    // Maximum: 255 bytes.
    pub data: [u8; 255],
}

impl MechCommandRegisterValue {
    /// Constructor
    pub fn new() -> Self {
        Self {
            type_id: VariantTypeId::Null,
            data: [0; 255],
            value_len: 0,
            num_columns: 1,
            num_rows: 1,
        }
    }

    pub fn from_bool(value: bool) -> Self {
        let mut data = [0u8; 255];
        data[0] = value as u8;

        Self {
            type_id: VariantTypeId::Bit,
            value_len: 1,
            data,
            num_columns: 1,
            num_rows: 1,
        }
    }

    pub fn from_byte(value: u8) -> Self {
        let mut data = [0u8; 255];
        data[0] = value;

        Self {
            type_id: VariantTypeId::Byte,
            value_len: 1,
            data,
            num_columns: 1,
            num_rows: 1,
        }
    }

    pub fn from_sbyte(value: i8) -> Self {
        let mut data = [0u8; 255];
        data[0] = value as u8;

        Self {
            type_id: VariantTypeId::SByte,
            value_len: 1,
            data,
            num_columns: 1,
            num_rows: 1,
        }
    }

    pub fn from_int16(value: i16) -> Self {
        let mut data = [0u8; 255];
        data[..2].copy_from_slice(&value.to_le_bytes());

        Self {
            type_id: VariantTypeId::Int16,
            value_len: 2,
            data,
            num_columns: 1,
            num_rows: 1,
        }
    }

    pub fn from_uint16(value: u16) -> Self {
        let mut data = [0u8; 255];
        data[..2].copy_from_slice(&value.to_le_bytes());

        Self {
            type_id: VariantTypeId::UInt16,
            value_len: 2,
            data,
            num_columns: 1,
            num_rows: 1,
        }
    }

    pub fn from_int32(value: i32) -> Self {
        let mut data = [0u8; 255];
        data[..4].copy_from_slice(&value.to_le_bytes());

        Self {
            type_id: VariantTypeId::Int32,
            value_len: 4,
            data,
            num_columns: 1,
            num_rows: 1,
        }
    }

    pub fn from_uint32(value: u32) -> Self {
        let mut data = [0u8; 255];
        data[..4].copy_from_slice(&value.to_le_bytes());

        Self {
            type_id: VariantTypeId::UInt32,
            value_len: 4,
            data,
            num_columns: 1,
            num_rows: 1,
        }
    }

    pub fn from_int64(value: i64) -> Self {
        let mut data = [0u8; 255];
        data[..8].copy_from_slice(&value.to_le_bytes());

        Self {
            type_id: VariantTypeId::Int64,
            value_len: 8,
            data,
            num_columns: 1,
            num_rows: 1,
        }
    }

    pub fn from_uint64(value: u64) -> Self {
        let mut data = [0u8; 255];
        data[..8].copy_from_slice(&value.to_le_bytes());

        Self {
            type_id: VariantTypeId::UInt64,
            value_len: 8,
            data,
            num_columns: 1,
            num_rows: 1,
        }
    }

    pub fn from_real32(value: f32) -> Self {
        let mut data = [0u8; 255];
        data[..4].copy_from_slice(&value.to_le_bytes());

        Self {
            type_id: VariantTypeId::Real32,
            value_len: 4,
            data,
            num_columns: 1,
            num_rows: 1,
        }
    }

    pub fn from_real64(value: f64) -> Self {
        let mut data = [0u8; 255];
        data[..8].copy_from_slice(&value.to_le_bytes());

        Self {
            type_id: VariantTypeId::Real64,
            value_len: 8,
            data,
            num_columns: 1,
            num_rows: 1,
        }
    }

    /// Creates a `MechCommandRegisterValue` from a string.
    ///
    /// The string is stored as bytes in the `data` field, with `type_id` set to `VariantTypeId::String`.
    /// Only the first 255 bytes of the string are stored to fit within the byte limit.
    ///
    /// # Parameters
    /// - `value`: The string to be stored in the register.
    ///
    /// # Returns
    /// A `MechCommandRegisterValue` containing the data.
    pub fn from_string(value: &str) -> Self {
        let bytes = value.as_bytes();
        let mut data = [0u8; 255];

        // Copy up to 255 bytes of the string into the data array
        let copy_len = std::cmp::min(bytes.len(), 255);
        data[..copy_len].copy_from_slice(&bytes[..copy_len]);

        Self {
            type_id: VariantTypeId::String,
            value_len: copy_len as u32,
            data,
            num_columns: 1,
            num_rows: 1,
        }
    }

    /// Convert the structure to a stream of little-endian bytes
    pub fn to_bytes(&self) -> [u8; 269] {
        let mut ret = [0 as u8; 269];
        ret[0..2].copy_from_slice(&(self.type_id as i16).to_le_bytes());
        ret[2..6].copy_from_slice(&self.value_len.to_le_bytes());
        ret[6..10].copy_from_slice(&self.num_columns.to_le_bytes());
        ret[10..14].copy_from_slice(&self.num_rows.to_le_bytes());
        ret[14..269].copy_from_slice(&self.data);

        return ret;
    }

    /// Create from a variant.
    pub fn from_variant(s: &VariantValue) -> Self {
        let bytes = postcard::to_allocvec(s).unwrap();

        let mut data = [0u8; 255]; // Initialize array with 0s

        // Copy the serialized bytes into the data array, up to a maximum of 255 bytes
        let copy_len = std::cmp::min(bytes.len(), 255);
        data[..copy_len].copy_from_slice(&bytes[..copy_len]);

        Self {
            type_id: s.get_type_id(),
            value_len: bytes.len() as u32,
            data: data,
            num_columns: 1,
            num_rows: 1,
        }
    }

    /// Returns the underlying value as a boolean, but only if stored
    /// as a boolean. Otherwise, returns none.
    pub fn to_bool(&self) -> Result<bool, anyhow::Error> {
        if self.type_id == VariantTypeId::Bit {
            return Ok(self.data[0] != 0);
        } else {
            return Err(anyhow!(
                "Cannot convert type_id {:?} to bool!",
                self.type_id
            ));
        }
    }

    pub fn to_byte(&self) -> Result<u8, anyhow::Error> {
        if self.type_id == VariantTypeId::Byte || self.type_id == VariantTypeId::SByte {
            return Ok(self.data[0]);
        } else {
            return Err(anyhow!(
                "Cannot convert type_id {:?} to byte!",
                self.type_id
            ));
        }
    }

    pub fn to_sbyte(&self) -> Result<i8, anyhow::Error> {
        if self.type_id == VariantTypeId::SByte || self.type_id == VariantTypeId::Byte {
            return Ok(self.data[0] as i8);
        } else {
            return Err(anyhow!(
                "Cannot convert type_id {:?} to sbyte!",
                self.type_id
            ));
        }
    }

    pub fn to_int16(&self) -> Result<i16, anyhow::Error> {
        if self.type_id == VariantTypeId::Int16 || self.type_id == VariantTypeId::UInt16 {
            let deserialized: i16 = postcard::from_bytes(&self.data[..2])?;
            Ok(deserialized)
        } else {
            return Err(anyhow!(
                "Cannot convert type_id {:?} to int16!",
                self.type_id
            ));
        }
    }

    pub fn to_uint16(&self) -> Result<u16, anyhow::Error> {
        if self.type_id == VariantTypeId::Int16 || self.type_id == VariantTypeId::UInt16 {
            let deserialized: u16 = postcard::from_bytes(&self.data[..2])?;
            Ok(deserialized)
        } else {
            return Err(anyhow!(
                "Cannot convert type_id {:?} to uint16!",
                self.type_id
            ));
        }
    }

    pub fn to_int32(&self) -> Result<i32, anyhow::Error> {
        if self.type_id == VariantTypeId::Int32 || self.type_id == VariantTypeId::UInt32 {
            let deserialized: i32 = postcard::from_bytes(&self.data[..4])?;
            Ok(deserialized)
        } else {
            return Err(anyhow!(
                "Cannot convert type_id {:?} to int32!",
                self.type_id
            ));
        }
    }

    pub fn to_uint32(&self) -> Result<u32, anyhow::Error> {
        if self.type_id == VariantTypeId::Int32 || self.type_id == VariantTypeId::UInt32 {
            let deserialized: u32 = postcard::from_bytes(&self.data[..4])?;
            Ok(deserialized)
        } else {
            return Err(anyhow!(
                "Cannot convert type_id {:?} to uint32!",
                self.type_id
            ));
        }
    }

    pub fn to_int64(&self) -> Result<i64, anyhow::Error> {
        if self.type_id == VariantTypeId::Int64 || self.type_id == VariantTypeId::UInt64 {
            let deserialized: i64 = postcard::from_bytes(&self.data[..8])?;
            Ok(deserialized)
        } else {
            return Err(anyhow!(
                "Cannot convert type_id {:?} to int64!",
                self.type_id
            ));
        }
    }

    pub fn to_uint64(&self) -> Result<u64, anyhow::Error> {
        if self.type_id == VariantTypeId::Int64 || self.type_id == VariantTypeId::UInt64 {
            let deserialized: u64 = postcard::from_bytes(&self.data[..8])?;
            Ok(deserialized)
        } else {
            return Err(anyhow!(
                "Cannot convert type_id {:?} to uint64!",
                self.type_id
            ));
        }
    }

    pub fn to_real32(&self) -> Result<f32, anyhow::Error> {
        if self.type_id == VariantTypeId::Real32 {
            let deserialized: f32 = postcard::from_bytes(&self.data[..4])?;
            Ok(deserialized)
        } else {
            return Err(anyhow!(
                "Cannot convert type_id {:?} to real32!",
                self.type_id
            ));
        }
    }

    pub fn to_real64(&self) -> Result<f64, anyhow::Error> {
        if self.type_id == VariantTypeId::Real64 {
            let deserialized: f64 = postcard::from_bytes(&self.data[..8])?;
            Ok(deserialized)
        } else {
            return Err(anyhow!(
                "Cannot convert type_id {:?} to real64!",
                self.type_id
            ));
        }
    }

    pub fn to_string(&self) -> Result<String, anyhow::Error> {
        match self.type_id {
            VariantTypeId::String => {
                match String::from_utf8(self.data[..self.value_len as usize].to_vec()) {
                    Ok(val) => return Ok(val),
                    Err(err) => {
                        return Err(anyhow!("Failed to deserialize string: {}", err));
                    }
                }
            }
            VariantTypeId::Bit => match self.to_bool() {
                Ok(ret) => {
                    return Ok(ret.to_string());
                }
                _ => {
                    return Err(anyhow!("Failed to convert Bit value to bool."));
                }
            },
            VariantTypeId::Byte => match self.to_byte() {
                Ok(ret) => {
                    return Ok(ret.to_string());
                }
                _ => {
                    return Err(anyhow!("Failed to convert Byte value to u8."));
                }
            },
            VariantTypeId::SByte => match self.to_sbyte() {
                Ok(ret) => {
                    return Ok(ret.to_string());
                }
                _ => {
                    return Err(anyhow!("Failed to convert SByte value to i8."));
                }
            },
            VariantTypeId::Int16 => match self.to_int16() {
                Ok(ret) => {
                    return Ok(ret.to_string());
                }
                _ => {
                    return Err(anyhow!("Failed to convert Int16 value to i16."));
                }
            },
            VariantTypeId::UInt16 => match self.to_uint16() {
                Ok(ret) => {
                    return Ok(ret.to_string());
                }
                _ => {
                    return Err(anyhow!("Failed to convert UInt16 value to u16."));
                }
            },
            VariantTypeId::Int32 => match self.to_int32() {
                Ok(ret) => {
                    return Ok(ret.to_string());
                }
                _ => {
                    return Err(anyhow!("Failed to convert Int32 value to i32."));
                }
            },
            VariantTypeId::UInt32 => match self.to_uint32() {
                Ok(ret) => {
                    return Ok(ret.to_string());
                }
                _ => {
                    return Err(anyhow!("Failed to convert UInt32 value to u32."));
                }
            },
            VariantTypeId::Int64 => match self.to_int64() {
                Ok(ret) => {
                    return Ok(ret.to_string());
                }
                _ => {
                    return Err(anyhow!("Failed to convert Int64 value to i64."));
                }
            },
            VariantTypeId::UInt64 => match self.to_uint64() {
                Ok(ret) => {
                    return Ok(ret.to_string());
                }
                _ => {
                    return Err(anyhow!("Failed to convert UInt64 value to u64."));
                }
            },
            VariantTypeId::Real32 => match self.to_real32() {
                Ok(ret) => {
                    return Ok(ret.to_string());
                }
                _ => {
                    return Err(anyhow!("Failed to convert Real32 value to f32."));
                }
            },
            VariantTypeId::Real64 => match self.to_real64() {
                Ok(ret) => {
                    return Ok(ret.to_string());
                }
                _ => {
                    return Err(anyhow!("Failed to convert Real64 value to f64."));
                }
            },
            VariantTypeId::Null => Ok("<NULL>".to_string()),
            VariantTypeId::Array => Ok("[ARRAY]".to_string()),
            VariantTypeId::Object => Ok("{OBJECT}".to_string()),
        }
    }

    pub fn to_variant(&self) -> Result<VariantValue, anyhow::Error> {
        match self.type_id {
            VariantTypeId::Null => {
                return Ok(VariantValue::Null);
            }
            VariantTypeId::Bit => match self.to_bool() {
                Ok(ret) => return Ok(VariantValue::Bit(ret)),
                Err(err) => return Err(anyhow!("Failed to conver to bool: {}", err)),
            },
            VariantTypeId::Byte => match self.to_byte() {
                Ok(ret) => return Ok(VariantValue::Byte(ret)),
                Err(err) => return Err(anyhow!("Failed to convert to byte: {}", err)),
            },
            VariantTypeId::SByte => match self.to_sbyte() {
                Ok(ret) => return Ok(VariantValue::SByte(ret)),
                Err(err) => return Err(anyhow!("Failed to convert to sbyte: {}", err)),
            },
            VariantTypeId::Int16 => match self.to_int16() {
                Ok(ret) => return Ok(VariantValue::Int16(ret)),
                Err(err) => return Err(anyhow!("Failed to convert to int16: {}", err)),
            },
            VariantTypeId::UInt16 => match self.to_uint16() {
                Ok(ret) => return Ok(VariantValue::UInt16(ret)),
                Err(err) => return Err(anyhow!("Failed to convert to int16: {}", err)),
            },
            VariantTypeId::Int32 => match self.to_int32() {
                Ok(ret) => return Ok(VariantValue::Int32(ret)),
                Err(err) => return Err(anyhow!("Failed to convert to int32: {}", err)),
            },
            VariantTypeId::UInt32 => match self.to_uint32() {
                Ok(ret) => return Ok(VariantValue::UInt32(ret)),
                Err(err) => return Err(anyhow!("Failed to convert to uint32: {}", err)),
            },
            VariantTypeId::Int64 => match self.to_int64() {
                Ok(ret) => return Ok(VariantValue::Int64(ret)),
                Err(err) => return Err(anyhow!("Failed to convert to int64: {}", err)),
            },
            VariantTypeId::UInt64 => match self.to_uint64() {
                Ok(ret) => return Ok(VariantValue::UInt64(ret)),
                Err(err) => return Err(anyhow!("Failed to convert to uint64: {}", err)),
            },
            VariantTypeId::Real32 => match self.to_real32() {
                Ok(ret) => return Ok(VariantValue::Real32(ret)),
                Err(err) => return Err(anyhow!("Failed to convert to real32: {}", err)),
            },
            VariantTypeId::Real64 => match self.to_real64() {
                Ok(ret) => return Ok(VariantValue::Real64(ret)),
                Err(err) => return Err(anyhow!("Failed to convert to real64: {}", err)),
            },
            VariantTypeId::String => match self.to_string() {
                Ok(ret) => return Ok(VariantValue::String(ret)),
                Err(err) => return Err(anyhow!("Failed to convert to string: {}", err)),
            },
            VariantTypeId::Array => {
                return Err(anyhow!("Conversion to array not currently supported."));
            }
            VariantTypeId::Object => {
                return Err(anyhow!("Conversion to object not currently supported."));
            }
        }
    }
}

// Serialize MechCommandRegisterValue
impl Serialize for MechCommandRegisterValue {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut state = serializer.serialize_struct("MechCommandRegisterValue", 5)?;
        state.serialize_field("type_id", &(self.type_id as i16))?;
        state.serialize_field("data", &self.data[0..self.value_len as usize])?;
        state.serialize_field("value_len", &self.value_len)?;
        state.serialize_field("num_columns", &self.num_columns)?;
        state.serialize_field("num_rows", &self.num_rows)?;
        return state.end();
    }
}

// Deserialize MechCommandRegisterValue
impl<'de> Deserialize<'de> for MechCommandRegisterValue {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        #[derive(Deserialize)]
        struct MechCommandRegisterValueHelper {
            type_id: VariantTypeId,
            data: Vec<u8>,
            value_len: u32,
            num_columns: u32,
            num_rows: u32,
        }

        let helper = MechCommandRegisterValueHelper::deserialize(deserializer)?;
        let mut data_bytes = [0u8; 255];
        // data_bytes.copy_from_slice(&helper.data[..helper.value_len as usize]);

        let source_length = helper.value_len as usize; // The number of bytes to copy

        // Ensure that the source slice length does not exceed the destination array size.
        data_bytes[..source_length].copy_from_slice(&helper.data[..source_length]);

        return Ok(MechCommandRegisterValue {
            type_id: helper.type_id,
            data: data_bytes,
            value_len: helper.value_len,
            num_columns: helper.num_columns,
            num_rows: helper.num_rows,
        });
    }
}

// For convenience, implement converting the MechFsmCommandTuple to a VariantValue.
// Most of our communications libraries use VariantValue as the data type.
impl TryInto<VariantValue> for MechCommandRegisterValue {
    type Error = anyhow::Error;

    /// Convert the MechFsmCommandTuple to a VariantValue.
    fn try_into(self) -> Result<VariantValue, anyhow::Error> {
        let mut map = IndexMap::new();
        map.insert(
            "type_id".to_string(),
            VariantValue::Int16(self.type_id as i16),
        );
        map.insert(
            "value_len".to_string(),
            VariantValue::UInt32(self.value_len),
        );
        map.insert(
            "num_columns".to_string(),
            VariantValue::UInt32(self.num_columns),
        );
        map.insert("num_rows".to_string(), VariantValue::UInt32(self.num_rows));

        let mut var_data = Vec::new();
        for item in self.data {
            var_data.push(VariantValue::Byte(item));
        }

        map.insert("data".to_string(), VariantValue::Array(var_data));

        let ret = VariantValue::Object(Box::new(map));

        return Ok(ret);
    }
}

impl TryFrom<VariantValue> for MechCommandRegisterValue {
    type Error = anyhow::Error;

    fn try_from(value: VariantValue) -> Result<Self, Self::Error> {
        match value.clone() {
            VariantValue::Object(map) => {
                let type_id = map
                    .get("type_id")
                    .and_then(|v| v.to_int16().ok())
                    .and_then(|v| VariantTypeId::from_i16(v))
                    .unwrap_or(VariantTypeId::Null);

                let value_len = map
                    .get("value_len")
                    .and_then(|v| v.to_uint32().ok())
                    .and_then(|v| Some(v))
                    .unwrap_or(0);

                let num_columns = map
                    .get("num_columns")
                    .and_then(|v| v.to_uint32().ok())
                    .and_then(|v| Some(v))
                    .unwrap_or(0);

                let num_rows = map
                    .get("num_rows")
                    .and_then(|v| v.to_uint32().ok())
                    .and_then(|v| Some(v))
                    .unwrap_or(0);

                let mut data = [0 as u8; 255];
                match map.get("data") {
                    Some(VariantValue::Array(array)) => {
                        for (i, item) in array.into_iter().enumerate() {
                            if let VariantValue::Byte(b) = item {
                                data[i] = *b;
                            }
                        }
                    }
                    _ => {
                        return Err(anyhow!(
                            "VariantValue::Object data menu is not Object type."
                        ));
                    }
                }

                Ok(Self {
                    type_id: type_id,
                    value_len: value_len,
                    num_columns: num_columns,
                    num_rows: num_rows,
                    data: data,
                })
            }
            _ => {
                return Err(anyhow!("VariantValue is not Object type."));
            }
        }
    }
}
