//
// Copyright (C) 2024 Automated Design Corp.. All Rights Reserved.
// Created Date: 2024-10-23 06:12:47
// -----
// Last Modified: 2024-11-04 07:41:41
// -----
//
//

//! This module provides an implementation based upon the CANopen object dictionary
//! or CAN over EtherCAT (CoE) systems. The object
//! dictionary is the core mechanism for configuration and communication with
//! CANopen devices. Each entry in the dictionary is represented by an index, which
//! may contain sub-indices for more complex data structures such as arrays and
//! records.
//!
//! The `MechObjectDictionary` struct serves as a collection of these entries, each
//! described by its index, object type, access rights, data type, and value. The
//! module supports serialization and deserialization to/from JSON to facilitate
//! easy configuration management and device integration.
//!
//! Key Components:
//! - `MechObjectDictionary`: The main container for the object dictionary entries.
//! - `MechObjectDictionaryEntry`: Represents a single entry in the object dictionary.
//! - `RegisterValue`: Enum representing either a single value or a set of sub-indices.
//! - `AccessRights`: Enum describing the access permissions for an entry (ReadWrite, ReadOnly, WriteOnly).
//! - `ObjectType`: Enum for the type of object (Array, Record, Variable).
//!
//! Features:
//! - Create and manage object dictionary entries.
//! - Support for both single values and arrays of sub-indices.
//! - Serialization and deserialization to/from JSON for configuration persistence.
//!
//! Example Usage:
//! ```no_run
//! use mechutil::object_dictionary::{MechObjectDictionary, ObjectType, AccessRights, MechObjectDictionaryValue};
//! use mechutil::register_value::MechCommandRegisterValue;
//!
//! let mut dictionary = MechObjectDictionary::new(
//!     0x000401,  // Device Type (generic I/O device)
//!     1000,      // Heartbeat interval of 1000 ms
//!     12345,     // Vendor ID
//!     67890,     // Product Code
//! );
//! let initial_value = MechCommandRegisterValue::new();  // Assume this creates a default value.
//! dictionary.add_entry(0x1000, ObjectType::Array, "Example Object".to_string(), "uint32".to_string(), AccessRights::ReadWrite, true, 5, initial_value);
//! ```
//!
//! In general, for custom functionality, the objects should be in the range 0x2000–0x5FFF, **Manufacturer-Specific Objects**.
//! See the common_canopen_objects.md doc for CANOpen standard dictionary objects and their addresses.
//!
//!

//
// TODO:
// - [] PDO-like features.
// - - [] Register PDOs
// - - [] Map registers to a PDO
// - []  API for making required objects:
// - - 0x1000 Device Type (Read Only)
// - - 0x1001 Error Register (Read Only)
// - - 0x1017 Producer Heartbeat Time (Const)
// - - 0x1018 Identity Object (Read Only)
//
// - [] Look into this:
// - - "In addition you need Communication Parameters and Mapping parameters for every PDO, but these are allowed to be constants.""

use crate::register_value::MechCommandRegisterValue;
use anyhow::anyhow;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;

/// Enum for access rights (read/write, read-only, write-only) for the object dictionary entry.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum AccessRights {
    ReadWrite,
    Const,
    ReadOnly,
    WriteOnly,
}

/// Enum for the object type in the object dictionary entry (e.g., array, record, variable).
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ObjectType {
    /// A collection of homogeneous sub-indices, meaning all sub-indices within an Array must have the same data type.
    Array,
    /// A collection of heterogeneous sub-indices, meaning each sub-index can represent a different type of data
    Record,
    /// A single value. This object does not contain sub-indices.
    Variable,
}

/// Represents an entry in the CANopen Object Dictionary.
///
/// This struct models the data, metadata, and access properties for a specific
/// entry within a CANopen-compliant Object Dictionary, where each entry is
/// identified by a unique 16-bit `index`.
///
/// ### Fields
/// - `index`: The 16-bit address of the object in the Object Dictionary.
/// - `object_type`: Specifies the type of object. This can be an `Array`, `Record`,
///   or `Variable`, which determines if the object contains a list of values,
///   a structured set of different data types, or a single value.
/// - `name`: A descriptive name for the object, helping to identify its purpose
///   or function within the device configuration.
/// - `datatype`: The data type of the object as a `String`. This string represents
///   the underlying data format (e.g., `"uint32"`, `"int16"`, `"float"`). The `datatype`
///   string is informational, ensuring compatibility across systems without imposing
///   a strict type. To enforce or interpret data types programmatically, use the
///   appropriate `from_*` and `to_*` methods in `MechCommandRegisterValue`.
/// - `access_rights`: Defines the access permissions for the entry, including `ReadOnly`,
///   `WriteOnly`, `ReadWrite`, or `Const` (constant, unmodifiable data).
/// - `mandatory`: Indicates whether the object is required for the device to be CANopen-compliant
///   (`true` if mandatory) or is optional (`false`).
/// - `value`: Stores the entry’s value(s). This field can either hold a single value or
///   a collection of sub-indices, depending on the object type.
///
/// ### Example Usage
/// ```no_run
/// use mechutil::object_dictionary::{MechObjectDictionaryEntry, ObjectType, AccessRights, MechObjectDictionaryValue};
/// use mechutil::register_value::MechCommandRegisterValue;
///
/// let entry = MechObjectDictionaryEntry {
///     index: 0x1018,
///     object_type: ObjectType::Record,
///     name: "Identity Object".to_string(),
///     datatype: "record".to_string(),
///     access_rights: AccessRights::ReadOnly,
///     mandatory: true,
///     value: MechObjectDictionaryValue::SubIndices(vec![
///         MechCommandRegisterValue::from_uint32(12345),  // Vendor ID
///         MechCommandRegisterValue::from_uint32(67890),  // Product Code
///         MechCommandRegisterValue::from_uint32(1),      // Revision Number
///         MechCommandRegisterValue::from_uint32(12345678), // Serial Number
///     ]),
/// };
/// ```
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MechObjectDictionaryEntry {
    pub index: u16,                       // 16-bit address of the object in the dictionary
    pub object_type: ObjectType,          // Type of object (array, record, variable)
    pub name: String,                     // Descriptive name of the object
    pub datatype: String,                 // Data type of the variable
    pub access_rights: AccessRights,      // Access rights (read/write, read-only, write-only)
    pub mandatory: bool, // Indicates if the object is mandatory (true) or optional (false)
    pub value: MechObjectDictionaryValue, // The value or sub-indices for the object
}

/// Defines the value storage for an entry in the CANopen Object Dictionary.
///
/// `MechObjectDictionaryValue` can store a single `MechCommandRegisterValue`
/// or a vector of `MechCommandRegisterValue`s, representing an array or record
/// structure with multiple sub-indices. This enum allows flexibility in how
/// values are stored within the Object Dictionary:
/// - `Single`: Used for entries that have a single value (e.g., device-specific parameters).
/// - `SubIndices`: Used for entries that contain multiple values, typically
///   when `object_type` is `Record` or `Array`.
///
/// ### Example Usage
/// ```no_run
/// use mechutil::object_dictionary::{MechObjectDictionaryValue};
/// use mechutil::register_value::MechCommandRegisterValue;
///
/// // Single value example
/// let single_value = MechObjectDictionaryValue::Single(MechCommandRegisterValue::from_int32(42));
///
/// // Array of values example
/// let array_value = MechObjectDictionaryValue::SubIndices(vec![
///     MechCommandRegisterValue::from_uint32(0),
///     MechCommandRegisterValue::from_uint32(1),
///     MechCommandRegisterValue::from_uint32(2),
/// ]);
/// ```
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum MechObjectDictionaryValue {
    Single(MechCommandRegisterValue),
    SubIndices(Vec<MechCommandRegisterValue>),
}

/// `MechObjectDictionary` is a structured collection of entries for a CANopen device's Object Dictionary.
///
/// This dictionary serves as the central data structure for defining and organizing device parameters,
/// configuration settings, and runtime information according to CANopen standards. Each entry in the
/// dictionary is mapped by its index and may represent a single value, a structured record, or an array of
/// homogeneous elements.
///
/// ### Key Features
/// - **Standard Entries**: The `new` method initializes the dictionary with commonly used CANopen objects
///   (e.g., Device Type, Error Register, Producer Heartbeat Time, Identity Object).
/// - **Customizable**: New entries can be added dynamically to the dictionary with specific properties
///   like access rights and data types.
/// - **Supports Single and Array Entries**: Entries can either hold a single value or a collection of sub-indices
///   (e.g., for records or arrays).
///
/// ### Example Usage
/// ```no_run
/// use mechutil::object_dictionary::{MechObjectDictionary, ObjectType, AccessRights};
/// use mechutil::register_value::MechCommandRegisterValue;
///
/// // Initialize a new object dictionary with standard entries and default values
/// let mut dictionary = MechObjectDictionary::new(
///     0x000401,  // Device Type (generic I/O device)
///     1000,      // Heartbeat interval of 1000 ms
///     12345,     // Vendor ID
///     67890,     // Product Code
/// );
///
/// // Adding a custom entry for a device-specific parameter (single value)
/// let custom_value = MechCommandRegisterValue::from_int32(42);
/// dictionary.add_value_entry(
///     0x2000,
///     ObjectType::Variable,
///     "Custom Parameter".to_string(),
///     "int32".to_string(),
///     AccessRights::ReadWrite,
///     false,
///     custom_value,
/// );
///
/// // Adding an array entry for logging error history (multiple sub-indices)
/// let error_history_value = MechCommandRegisterValue::from_uint32(0);
/// dictionary.add_array_entry(
///     0x1003,
///     ObjectType::Array,
///     "Error History".to_string(),
///     "uint32".to_string(),
///     AccessRights::ReadWrite,
///     false,
///     5,   // Number of sub-indices for error codes
///     error_history_value,
/// );
///
/// // Reading the value of a single-value entry (e.g., Device Type)
/// if let Ok(value) = dictionary.get_index_value(0x1000) {
///     println!("Device Type: {:?}", value);
/// }
///
/// // Reading a specific sub-index value from an array entry (e.g., Error History at index 1)
/// if let Ok(error_code) = dictionary.get_sub_index_value(0x1003, 1) {
///     println!("Error Code at sub-index 1: {:?}", error_code);
/// }
/// ```
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MechObjectDictionary {
    registers: HashMap<u16, MechObjectDictionaryEntry>, // Register index to entry mapping
}

impl MechObjectDictionary {
    /// Creates a new `MechObjectDictionary` with standard default entries.
    ///
    /// This function initializes the dictionary with commonly used CANopen objects, including:
    /// - `0x1000` Device Type (Read Only): Specifies the type of the device.
    /// - `0x1001` Error Register (Read Only): Holds error flags for device status monitoring.
    /// - `0x1017` Producer Heartbeat Time (Const): Sets the interval for heartbeat messages.
    /// - `0x1018` Identity Object (Read Only): Contains identification information about the device.
    ///
    /// # Parameters
    /// - `device_type`: The initial value for the `0x1000` Device Type entry.
    /// - `heartbeat_time_interval_ms`: The interval in milliseconds for the `0x1017` Producer Heartbeat Time entry.
    /// - `vendor_id`: The Vendor ID for the `0x1018:01` sub-index of the Identity Object.
    /// - `product_code`: The Product Code for the `0x1018:02` sub-index of the Identity Object.
    ///
    /// # Example
    /// ```no_run
    /// use mechutil::object_dictionary::MechObjectDictionary;
    /// let dictionary = MechObjectDictionary::new(
    ///     0x2001,     // Device Type for a software device
    ///     1000,      // Heartbeat interval of 1000 ms (1 second)
    ///     12345,     // Vendor ID
    ///     67890,     // Product Code
    /// );
    /// ```
    pub fn new(
        device_type: u32,
        heartbeat_time_interval_ms: u16,
        vendor_id: u32,
        product_code: u32,
    ) -> Self {
        let mut dictionary = MechObjectDictionary {
            registers: HashMap::new(),
        };

        // Default initial value with zero values for each register
        let default_value = MechCommandRegisterValue::new();

        // 0x1000 Device Type
        dictionary.add_value_entry(
            0x1000,
            ObjectType::Variable,
            "Device Type".to_string(),
            "uint32".to_string(),
            AccessRights::ReadOnly,
            true,
            MechCommandRegisterValue::from_uint32(device_type),
        );

        // 0x1001 Error Register
        dictionary.add_value_entry(
            0x1001,
            ObjectType::Variable,
            "Error Register".to_string(),
            "uint8".to_string(),
            AccessRights::ReadOnly,
            true,
            default_value.clone(),
        );

        // 0x1017 Producer Heartbeat Time
        dictionary.add_value_entry(
            0x1017,
            ObjectType::Variable,
            "Producer Heartbeat Time".to_string(),
            "uint16".to_string(),
            AccessRights::Const,
            true,
            MechCommandRegisterValue::from_uint16(heartbeat_time_interval_ms),
        );

        // 0x1018 Identity Object (Typically a Record with multiple sub-indices)
        dictionary.add_array_entry(
            0x1018,
            ObjectType::Record,
            "Identity Object".to_string(),
            "record".to_string(),
            AccessRights::ReadOnly,
            true,
            4, // Example: Identity Object might have 4 sub-indices for Vendor ID, Product Code, Revision Number, Serial Number
            default_value,
        );

        let _ = dictionary.set_value(
            0x1018,
            Some(1),
            MechCommandRegisterValue::from_uint32(vendor_id),
        );
        let _ = dictionary.set_value(
            0x1018,
            Some(2),
            MechCommandRegisterValue::from_uint32(product_code),
        );
        // revision number
        let _ = dictionary.set_value(0x1018, Some(3), MechCommandRegisterValue::from_uint32(0));
        // serial number
        let _ = dictionary.set_value(0x1018, Some(4), MechCommandRegisterValue::from_uint32(0));

        return dictionary;
    }

    /// Adds a new entry to the object dictionary with an initial value.
    /// If `num_sub_indices` is greater than 1, it creates sub-indices with the provided initial value;
    /// otherwise, it creates a single value entry.
    ///
    /// # Parameters
    /// - `index`: The register index.
    /// - `object_type`: The type of the object (array, record, variable).
    /// - `name`: The name of the object.
    /// - `datatype`: The data type of the object.
    /// - `access_rights`: The access rights for this object (read/write, read-only, write-only).
    /// - `mandatory`: Whether the object is mandatory or optional.
    /// - `num_indices`: The number of indices/sub-indices in this value. If 0 or 1, it creates a single value.
    /// - `initial_value`: The initial value to set for the entry (either single or in each sub-index).
    ///
    /// # Example
    /// ```
    /// use mechutil::object_dictionary::MechObjectDictionary;
    /// use mechutil::object_dictionary::AccessRights;
    /// use mechutil::object_dictionary::ObjectType;
    /// use mechutil::register_value::MechCommandRegisterValue;
    /// let mut dictionary = MechObjectDictionary::new(
    ///     0x000401,  // Device Type (generic I/O device)
    ///     1000,      // Heartbeat interval of 1000 ms
    ///     12345,     // Vendor ID
    ///     67890,     // Product Code
    /// );
    /// let initial_value = MechCommandRegisterValue::new();  // Assume this creates a default value.
    /// dictionary.add_entry(0x1000, ObjectType::Array, "Example Object".to_string(), "uint32".to_string(), AccessRights::ReadWrite, true, 5, initial_value);
    /// ```
    pub fn add_entry(
        &mut self,
        index: u16,
        object_type: ObjectType,
        name: String,
        datatype: String,
        access_rights: AccessRights,
        mandatory: bool,
        num_indices: usize,
        initial_value: MechCommandRegisterValue,
    ) {
        let value = if num_indices <= 1 {
            // Create a single entry with the provided initial value
            MechObjectDictionaryValue::Single(initial_value)
        } else {
            // Create an array of sub-indices, each initialized with the provided initial value
            // The first index of the array is used to store the size/number of values. So, we need to
            // add an additional index to the num_indices.
            MechObjectDictionaryValue::SubIndices(vec![initial_value; num_indices + 1])
        };

        let entry = MechObjectDictionaryEntry {
            index,
            object_type,
            name,
            datatype,
            access_rights,
            mandatory,
            value,
        };

        self.registers.insert(index, entry);
    }

    /// Adds a new entry with a single value to the object dictionary.
    /// This function is used for entries that do not have sub-indices and only require a single `MechCommandRegisterValue`.
    ///
    /// # Parameters
    /// - `index`: The register index of the object.
    /// - `object_type`: The type of the object (e.g., array, record, variable).
    /// - `name`: A string representing the name of the object.
    /// - `datatype`: The data type of the object.
    /// - `access_rights`: Access permissions for this entry (read/write, read-only, write-only).
    /// - `mandatory`: A boolean indicating whether the object is mandatory or optional.
    /// - `initial_value`: The initial value to set for the entry.
    ///
    /// # Example
    /// ```
    /// use mechutil::object_dictionary::MechObjectDictionary;
    /// use mechutil::object_dictionary::ObjectType;
    /// use mechutil::object_dictionary::AccessRights;
    /// use mechutil::register_value::MechCommandRegisterValue;
    /// let mut dictionary = MechObjectDictionary::new(
    ///     0x000401,  // Device Type (generic I/O device)
    ///     1000,      // Heartbeat interval of 1000 ms
    ///     12345,     // Vendor ID
    ///     67890,     // Product Code
    /// );
    /// let initial_value = MechCommandRegisterValue::from_int32(12345);
    /// dictionary.add_value_entry(
    ///     0x1000,
    ///     ObjectType::Variable,
    ///     "Example Value".to_string(),
    ///     "int32".to_string(),
    ///     AccessRights::ReadWrite,
    ///     true,
    ///     initial_value,
    /// );
    /// ```
    pub fn add_value_entry(
        &mut self,
        index: u16,
        object_type: ObjectType,
        name: String,
        datatype: String,
        access_rights: AccessRights,
        mandatory: bool,
        initial_value: MechCommandRegisterValue,
    ) {
        let value = MechObjectDictionaryValue::Single(initial_value);

        let entry = MechObjectDictionaryEntry {
            index,
            object_type,
            name,
            datatype,
            access_rights,
            mandatory,
            value,
        };

        self.registers.insert(index, entry);
    }

    /// Adds a new entry with multiple sub-indices to the object dictionary.
    /// This function is used for entries that require an array of sub-indices, each initialized to `initial_value`.
    ///
    /// # Parameters
    /// - `index`: The register index of the object.
    /// - `object_type`: The type of the object (e.g., array, record, variable).
    /// - `name`: A string representing the name of the object.
    /// - `datatype`: The data type of the object.
    /// - `access_rights`: Access permissions for this entry (read/write, read-only, write-only).
    /// - `mandatory`: A boolean indicating whether the object is mandatory or optional.
    /// - `num_sub_indices`: The number of sub-indices within the entry (each initialized with `initial_value`).
    /// - `initial_value`: The initial value to set for each sub-index in the array.
    ///
    /// # Example
    /// ```
    /// use mechutil::object_dictionary::AccessRights;
    /// use mechutil::object_dictionary::ObjectType;
    /// use mechutil::register_value::MechCommandRegisterValue;
    /// use mechutil::object_dictionary::MechObjectDictionary;
    /// let mut dictionary = MechObjectDictionary::new(
    ///     0x000401,  // Device Type (generic I/O device)
    ///     1000,      // Heartbeat interval of 1000 ms
    ///     12345,     // Vendor ID
    ///     67890,     // Product Code
    /// );
    /// let initial_value = MechCommandRegisterValue::from_int32(0);
    /// dictionary.add_array_entry(
    ///     0x2000,
    ///     ObjectType::Array,
    ///     "Example Array".to_string(),
    ///     "int32".to_string(),
    ///     AccessRights::ReadWrite,
    ///     true,
    ///     5,
    ///     initial_value,
    /// );
    /// ```    
    pub fn add_array_entry(
        &mut self,
        index: u16,
        object_type: ObjectType,
        name: String,
        datatype: String,
        access_rights: AccessRights,
        mandatory: bool,
        num_sub_indices: usize,
        initial_value: MechCommandRegisterValue,
    ) {
        // Create an array of sub-indices, each initialized with the provided initial value
        // The first index of the array is used to store the size/number of values. So, we need to
        // add an additional index to the num_indices.
        let value = MechObjectDictionaryValue::SubIndices(vec![initial_value; num_sub_indices + 1]);

        let entry = MechObjectDictionaryEntry {
            index,
            object_type,
            name,
            datatype,
            access_rights,
            mandatory,
            value,
        };

        self.registers.insert(index, entry);

        // Set the 0 index to the number of indices.
        let _ = self.set_value(
            index,
            Some(0),
            MechCommandRegisterValue::from_uint16(num_sub_indices as u16),
        );
    }

    /// Checks if an entry exists in the object dictionary.
    ///
    /// # Parameters
    /// - `index`: The register index.
    ///
    /// # Returns
    /// - `true` if the index exists, otherwise `false`.
    ///
    /// # Example
    /// ```no_run
    /// use mechutil::{object_dictionary::{MechObjectDictionary, ObjectType,AccessRights}};
    /// use mechutil::register_value::MechCommandRegisterValue;
    /// let mut dictionary = MechObjectDictionary::new(
    ///     0x000401,  // Device Type (generic I/O device)
    ///     1000,      // Heartbeat interval of 1000 ms
    ///     12345,     // Vendor ID
    ///     67890,     // Product Code
    /// );
    /// let initial_value = MechCommandRegisterValue::new();  // Assume this creates a default value.
    /// dictionary.add_entry(0x1000, ObjectType::Array, "Example Object".to_string(), "uint32".to_string(), AccessRights::ReadWrite, true, 5, initial_value);
    /// assert!(dictionary.entry_exists(0x1000));  // Returns true
    /// ```
    pub fn entry_exists(&self, index: u16) -> bool {
        self.registers.contains_key(&index)
    }

    /// Reads the value of an object dictionary entry without sub-indices.
    ///
    /// # Parameters
    /// - `index`: The register index.
    ///
    /// # Returns
    /// - `Result<&MechCommandRegisterValue, anyhow::Error>`: Returns the value if found and valid,
    ///   or an error message if the entry does not exist or if it contains sub-indices.
    pub fn get_index_value(&self, index: u16) -> Result<&MechCommandRegisterValue, anyhow::Error> {
        let entry;
        match self.registers.get(&index) {
            Some(res) => entry = res,
            None => return Err(anyhow!("Entry {} not found", index)),
        }

        // Check that this entry is a single value, not an array
        match &entry.value {
            MechObjectDictionaryValue::Single(value) => Ok(value),
            MechObjectDictionaryValue::SubIndices(values) => {
                // Return the value of the first index, which should be the length of this index.
                match values.first() {
                    Some(ret) => Ok(ret),
                    None => {
                        return Err(anyhow!("Index {} is empty.", index));
                    }
                }
            }
        }
    }

    /// Reads the value of a specific sub-index for an entry that has sub-indices.
    ///
    /// # Parameters
    /// - `index`: The register index.
    /// - `sub_index`: The sub-index to retrieve within the entry.
    ///
    /// # Returns
    /// - `Result<&MechCommandRegisterValue, anyhow::Error>`: Returns the value at the specified sub-index if found and valid,
    ///   or an error message if the entry does not exist, if it does not have sub-indices, or if the sub-index is out of bounds.
    pub fn get_sub_index_value(
        &self,
        index: u16,
        sub_index: u16,
    ) -> Result<&MechCommandRegisterValue, anyhow::Error> {
        let entry;
        match self.registers.get(&index) {
            Some(res) => entry = res,
            None => return Err(anyhow!("Entry {} not found", index)),
        }

        // Check that this entry has sub-indices
        match &entry.value {
            MechObjectDictionaryValue::Single(_) => Err(anyhow!(
                "Entry does not contain sub-indices; use `get_index_value` instead".to_string()
            )),
            MechObjectDictionaryValue::SubIndices(values) => match values.get(sub_index as usize) {
                Some(ret) => return Ok(ret),
                None => {
                    return Err(anyhow!("Sub-index {} out of bounds", sub_index));
                }
            },
        }
    }

    /// Retrieves all sub-index values of a specified entry in the Object Dictionary.
    /// For convenience.
    ///
    /// This function is useful for obtaining the full list of values when an entry
    /// contains multiple sub-indices (e.g., in array or record types).
    ///
    /// # Parameters
    /// - `index`: The 16-bit register index of the object in the Object Dictionary.
    ///
    /// # Returns
    /// - `Ok(Vec<MechCommandRegisterValue>)`: A vector containing all sub-index values
    ///   if the specified entry exists and contains sub-indices.
    /// - `Err(anyhow::Error)`: An error if the entry does not exist or does not contain sub-indices.
    ///
    /// # Errors
    /// Returns an error if the entry:
    /// - Does not exist in the Object Dictionary.
    /// - Exists but does not have sub-indices.
    ///
    /// # Example
    /// ```no_run
    /// use mechutil::object_dictionary::MechObjectDictionary;
    /// use mechutil::register_value::MechCommandRegisterValue;
    ///
    /// let dictionary = MechObjectDictionary::new(0x000401, 1000, 12345, 67890);
    ///
    /// // Assuming an array entry at index 0x2000 has been added to the dictionary.
    /// let result = dictionary.get_all_sub_index_values(0x2000);
    /// match result {
    ///     Ok(values) => {
    ///         for (i, value) in values.iter().enumerate() {
    ///             println!("Sub-index {}: {:?}", i, value);
    ///         }
    ///     },
    ///     Err(e) => println!("Error retrieving sub-index values: {}", e),
    /// }
    /// ```
    pub fn get_all_sub_index_values(
        &self,
        index: u16,
    ) -> Result<Vec<MechCommandRegisterValue>, anyhow::Error> {
        let entry;
        match self.registers.get(&index) {
            Some(res) => entry = res,
            None => return Err(anyhow!("Entry {} not found", index)),
        }

        // Check that this entry has sub-indices
        match &entry.value {
            MechObjectDictionaryValue::Single(_) => {
                Err(anyhow!("Entry does not contain sub-indices!"))
            }
            MechObjectDictionaryValue::SubIndices(values) => {
                return Ok(values.clone());
            }
        }
    }

    /// Retrieves a specified range of sub-index values for an entry in the Object Dictionary.
    /// For convenience.
    ///
    /// This function provides a subset of sub-indices between `start` and `end` for entries
    /// with multiple sub-indices (such as arrays). If `end` is negative, it includes all indices
    /// from `start` to the last index.
    ///
    /// # Parameters
    /// - `index`: The 16-bit register index of the object in the Object Dictionary.
    /// - `start`: The starting sub-index (inclusive).
    /// - `end`: The ending sub-index (inclusive). If `end` is negative, it defaults to the last sub-index.
    ///
    /// # Returns
    /// - `Ok(Vec<MechCommandRegisterValue>)`: A vector containing values in the specified range,
    ///   if the entry exists and contains sub-indices within the range.
    /// - `Err(anyhow::Error)`: An error if the entry does not exist, does not contain sub-indices, or if
    ///   the specified range is out of bounds.
    ///
    /// # Errors
    /// Returns an error if:
    /// - The entry does not exist or does not contain sub-indices.
    /// - The specified `start` index is out of bounds.
    /// - The specified `end` index is out of bounds.
    ///
    /// # Example
    /// ```no_run
    /// use mechutil::object_dictionary::MechObjectDictionary;
    /// use mechutil::register_value::MechCommandRegisterValue;
    ///
    /// let dictionary = MechObjectDictionary::new(0x000401, 1000, 12345, 67890);
    ///
    /// // Assuming an array entry at index 0x2000 with multiple sub-indices has been added.
    /// let result = dictionary.get_sub_index_range_values(0x2000, 1, 3);
    /// match result {
    ///     Ok(values) => {
    ///         for (i, value) in values.iter().enumerate() {
    ///             println!("Sub-index {}: {:?}", i + 1, value);
    ///         }
    ///     },
    ///     Err(e) => println!("Error retrieving range of sub-index values: {}", e),
    /// }
    /// ```
    pub fn get_sub_index_range_values(
        &self,
        index: u16,
        start: u16,
        end: i16,
    ) -> Result<Vec<MechCommandRegisterValue>, anyhow::Error> {
        let entry;
        match self.registers.get(&index) {
            Some(res) => entry = res,
            None => return Err(anyhow!("Entry {} not found", index)),
        }

        // Check that this entry has sub-indices
        match &entry.value {
            MechObjectDictionaryValue::Single(_) => {
                Err(anyhow!("Entry does not contain sub-indices!"))
            }
            MechObjectDictionaryValue::SubIndices(values) => {
                if (start as usize) >= values.len() {
                    return Err(anyhow!(
                        "start {} out of range of values len {}",
                        start,
                        values.len()
                    ));
                }

                let end_index;
                if end < 0 {
                    end_index = values.len() - 1;
                } else if (end as usize) < values.len() - 1 {
                    end_index = end as usize;
                } else {
                    return Err(anyhow!(
                        "end {} out of range of values len {}",
                        end,
                        values.len()
                    ));
                }

                return Ok(values[(start as usize)..=end_index].to_vec());
            }
        }
    }

    /// Sets the value of an object dictionary entry at a specific index. For use internally. Does not
    /// take access rights into account.
    ///
    /// # Parameters
    /// - `index`: The register index.
    /// - `sub_index`: Optional sub-index for entries with multiple values (arrays).
    /// - `new_value`: The new value to be set.
    ///
    /// # Returns
    /// - `Result<(), anyhow::Error>`: Returns Ok if the value is set successfully, or an error message if setting is denied or if the entry does not exist.
    pub fn set_value(
        &mut self,
        index: u16,
        sub_index: Option<u16>,
        new_value: MechCommandRegisterValue,
    ) -> Result<(), anyhow::Error> {
        // Get the entry for the specified index.

        let entry;
        match self.registers.get_mut(&index) {
            Some(res) => {
                entry = res;
            }
            None => {
                log::debug!("Entry {} not found. Available entries:", index);
                for key in self.registers.keys() {
                    log::debug!("\t{}", key);
                }

                return Err(anyhow!("Entry {} not found.", index));
            }
        }

        // Set the value based on whether the entry has sub-indices or is a single value.
        match &mut entry.value {
            MechObjectDictionaryValue::Single(value) => {
                if sub_index.is_some() {
                    return Err(anyhow!(
                        "Sub-index provided, but entry {} is not an array",
                        index
                    ));
                }
                *value = new_value; // Directly set the single value
            }
            MechObjectDictionaryValue::SubIndices(values) => {
                if let Some(sub_idx) = sub_index {
                    // Ensure sub-index is within bounds
                    if (sub_idx as usize) < values.len() {
                        values[sub_idx as usize] = new_value;
                    } else {
                        return Err(anyhow!("Sub-index {} out of bounds", sub_idx));
                    }
                } else {
                    return Err(anyhow!("Sub-index is required for array values"));
                }
            }
        }

        Ok(())
    }

    /// Sets the value of an object dictionary entry at a specific index. For use internally. Does not
    /// take access rights into account. Only works for single-value entries.
    ///
    /// # Parameters
    /// - `index`: The register index.
    /// - `new_value`: The new value to be set.
    ///
    /// # Returns
    /// - `Result<(), anyhow::Error>`: Returns Ok if the value is set successfully, or an error message if setting is denied or if the entry does not exist.
    pub fn set_index_value(
        &mut self,
        index: u16,
        new_value: MechCommandRegisterValue,
    ) -> Result<(), anyhow::Error> {
        // Get the entry for the specified index.
        let entry;
        match self.registers.get_mut(&index) {
            Some(res) => entry = res,
            None => return Err(anyhow!("Entry {} not found", index)),
        }

        // Set the value based on whether the entry has sub-indices or is a single value.
        match &mut entry.value {
            MechObjectDictionaryValue::Single(value) => {
                *value = new_value; // Directly set the single value
            }
            MechObjectDictionaryValue::SubIndices(_) => {
                return Err(anyhow!("Sub-index is required for array values"));
            }
        }

        Ok(())
    }

    /// Sets the value of the sub-index an object dictionary entry at a specific index. For use internally. Does not
    /// take access rights into account. Only works for Array or Record entries, not Single value entries.
    ///
    /// # Parameters
    /// - `index`: The register index.
    /// - `sub_index`: sub-index for entries with multiple values (arrays & records).
    /// - `new_value`: The new value to be set.
    ///
    /// # Returns
    /// - `Result<(), anyhow::Error>`: Returns Ok if the value is set successfully, or an error message if setting is denied or if the entry does not exist.
    pub fn set_sub_index_value(
        &mut self,
        index: u16,
        sub_index: u16,
        new_value: MechCommandRegisterValue,
    ) -> Result<(), anyhow::Error> {
        // Get the entry for the specified index.
        let entry;
        match self.registers.get_mut(&index) {
            Some(res) => entry = res,
            None => return Err(anyhow!("Entry {} not found", index)),
        }

        // Set the value based on whether the entry has sub-indices or is a single value.
        match &mut entry.value {
            MechObjectDictionaryValue::Single(value) => {
                return Err(anyhow!(
                    "Sub-index provided, but entry {} is not an array",
                    index
                ));
            }
            MechObjectDictionaryValue::SubIndices(values) => {
                if (sub_index as usize) < values.len() {
                    values[sub_index as usize] = new_value;
                } else {
                    return Err(anyhow!("Sub-index {} out of bounds", sub_index));
                }
            }
        }

        Ok(())
    }

    /// Client/External-facing function to set the value of an object dictionary entry at a specific index, if allowed by access rights.
    /// When taking requests from an external source, use this function to set the value of an Object Dictionary Entry.
    ///
    /// # Parameters
    /// - `index`: The register index.
    /// - `sub_index`: Optional sub-index for entries with multiple values (arrays).
    /// - `new_value`: The new value to be set.
    ///
    /// # Returns
    /// - `Result<(), anyhow::Error>`: Returns Ok if the value is set successfully, or an error message if setting is denied or if the entry does not exist.
    pub fn set_value_external(
        &mut self,
        index: u16,
        sub_index: Option<u16>,
        new_value: MechCommandRegisterValue,
    ) -> Result<(), anyhow::Error> {
        // Get the entry for the specified index.
        let entry;
        match self.registers.get_mut(&index) {
            Some(res) => entry = res,
            None => return Err(anyhow!("Entry {} not found", index)),
        }

        // Check if the entry is writable based on its access rights.
        match entry.access_rights {
            AccessRights::ReadOnly | AccessRights::Const => {
                return Err(anyhow!("Cannot set value: Entry is read-only"));
            }
            AccessRights::WriteOnly | AccessRights::ReadWrite => {}
        }

        return self.set_value(index, sub_index, new_value);
    }

    /// Loads the object dictionary from a JSON file.
    ///
    /// # Parameters
    /// - `filename`: The path to the JSON file.
    ///
    /// # Returns
    /// - `Result<(), Box<dyn std::error::Error>>`: Ok if successful, or an error otherwise.
    ///
    /// # Example
    /// ```no_run
    /// use mechutil::{object_dictionary::MechObjectDictionary};
    /// let mut dictionary = MechObjectDictionary::new(
    ///     0x000401,  // Device Type (generic I/O device)
    ///     1000,      // Heartbeat interval of 1000 ms
    ///     12345,     // Vendor ID
    ///     67890,     // Product Code
    /// );
    /// dictionary.load_from_json("input.json").unwrap();
    /// ```
    pub fn load_from_json(&mut self, filename: &str) -> Result<(), Box<dyn std::error::Error>> {
        let data = fs::read_to_string(filename)?;
        let json_data: HashMap<u16, MechObjectDictionaryEntry> = serde_json::from_str(&data)?;

        for (index, entry) in json_data {
            self.registers.insert(index, entry);
        }

        Ok(())
    }

    /// Serializes the object dictionary to a JSON file.
    ///
    /// # Parameters
    /// - `filename`: The path to the JSON file.
    ///
    /// # Returns
    /// - `Result<(), Box<dyn std::error::Error>>`: Ok if successful, or an error otherwise.
    ///
    /// # Example
    /// ```no_run
    /// use mechutil::{object_dictionary::MechObjectDictionary};
    /// let mut dictionary = MechObjectDictionary::new(
    ///     0x000401,  // Device Type (generic I/O device)
    ///     1000,      // Heartbeat interval of 1000 ms
    ///     12345,     // Vendor ID
    ///     67890,     // Product Code
    /// );
    /// dictionary.save_to_json("output.json").unwrap();
    /// ```
    pub fn save_to_json(&self, filename: &str) -> Result<(), Box<dyn std::error::Error>> {
        let json_data = serde_json::to_string_pretty(&self.registers)?;
        fs::write(filename, json_data)?;
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_create_and_check_entry() {
        let mut dictionary = MechObjectDictionary::new(0x2001, 1000, 0, 0);

        // Create a new entry with 5 sub-indices
        dictionary.add_entry(
            0x1000,
            ObjectType::Array,
            "Example Object".to_string(),
            "uint32".to_string(),
            AccessRights::ReadWrite,
            true,
            5,
            MechCommandRegisterValue::from_uint32(5),
        );

        // Check if the entry exists
        assert!(dictionary.entry_exists(0x1000));

        // Check if an entry that hasn't been added doesn't exist
        assert!(!dictionary.entry_exists(0x2000));
    }

    #[test]
    fn test_single_value_entry() {
        let mut dictionary = MechObjectDictionary::new(0x2001, 1000, 0, 0);

        // Create a new entry with a single value (no sub-indices)
        dictionary.add_entry(
            0x2000,
            ObjectType::Variable,
            "Single Value Object".to_string(),
            "int16".to_string(),
            AccessRights::ReadOnly,
            false,
            1,
            MechCommandRegisterValue::from_int16(1),
        );

        // Check if the entry exists
        assert!(dictionary.entry_exists(0x2000));

        assert!(
            dictionary
                .set_index_value(0x2000, MechCommandRegisterValue::from_int16(1234))
                .is_ok()
        );

        let val_check = dictionary.get_index_value(0x2000).unwrap();
        assert_eq!(val_check.to_int16().unwrap(), 1234);
    }

    #[test]
    fn test_serialize_to_json() {
        let mut dictionary = MechObjectDictionary::new(0x2001, 1000, 0, 0);

        // Add an entry
        dictionary.add_entry(
            0x1000,
            ObjectType::Array,
            "Test Object".to_string(),
            "uint32".to_string(),
            AccessRights::ReadWrite,
            true,
            3,
            MechCommandRegisterValue::from_int16(3),
        );

        // Serialize to JSON
        let json_data = serde_json::to_string_pretty(&dictionary).unwrap();

        // Check if JSON contains the index and the correct values
        assert!(json_data.contains("\"index\": 4096")); // 0x1000 in decimal is 4096
        assert!(json_data.contains("\"name\": \"Test Object\""));
        assert!(json_data.contains("\"access_rights\": \"ReadWrite\""));
    }

    #[test]
    fn test_deserialize_from_json() {
        let json_data = r#"
        {
            "4096": {
                "index": 4096,
                "object_type": "Array",
                "name":  "Deserialized Object",
                "datatype": "uint32",
                "access_rights": "ReadWrite",
                "mandatory": true,
                "value": {
                    "SubIndices": [
                        {
                            "type_id": "UInt32",
                            "value_len": 4,
                            "num_columns": 1,
                            "num_rows": 1,
                            "data": [1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
                        }
                    ]
                }
            }
        }
        "#;

        let mut dictionary = MechObjectDictionary::new(0x2001, 1000, 0, 0);

        // Deserialize from JSON
        let json_data: HashMap<u16, MechObjectDictionaryEntry> =
            serde_json::from_str(&json_data).unwrap();

        // Check if the entry was deserialized correctly
        dictionary.registers = json_data;
        assert!(dictionary.entry_exists(0x1000));
        let entry = dictionary.registers.get(&0x1000).unwrap();
        assert_eq!(entry.name, "Deserialized Object");
        assert_eq!(entry.datatype, "uint32");
        assert!(matches!(
            entry.value,
            MechObjectDictionaryValue::SubIndices(_)
        ));
    }
}
