
#ifndef ETHERCAT_SLAVE_COE_H
#define ETHERCAT_SLAVE_COE_H

#include "../ethercat_types.h"
#include <stdint.h>
#include <stddef.h>

#ifndef EC_COE_INLINE
  #if defined(_MSC_VER)
    #define EC_COE_INLINE static __inline
  #else
    #define EC_COE_INLINE static inline
  #endif
#endif

#ifdef __cplusplus
extern "C" {
#endif

typedef struct {
    ec_sdo_error_t code;
    uint16_t       index;
    uint8_t        subindex;
    uint8_t        is_read;
} ec_coe_sdo_status_t;

EC_COE_INLINE int ec_coe_sdo_is_ok(const ec_coe_sdo_status_t* s)
{
    return (s != NULL && s->code == EC_SDO_NO_ERROR) ? 1 : 0;
}

EC_COE_INLINE const char* ec_coe_sdo_error_str(ec_sdo_error_t code)
{
    switch (code) {
        case EC_SDO_NO_ERROR:                   return "NoError";
        case EC_SDO_TOGGLE_BIT_NOT_CHANGED:     return "ToggleBitNotChanged";
        case EC_SDO_PROTOCOL_TIMEOUT:           return "SDOProtocolTimeout";
        case EC_SDO_INVALID_COMMAND:            return "InvalidCommandSpecifier";
        case EC_SDO_OUT_OF_MEMORY:              return "OutOfMemory";
        case EC_SDO_UNSUPPORTED_ACCESS:         return "UnsupportedAccess";
        case EC_SDO_READ_WRITE_ONLY:            return "ReadWriteOnlyError";
        case EC_SDO_WRITE_READ_ONLY:            return "WriteReadOnlyError";
        case EC_SDO_SUBINDEX_WRITE_ERROR:       return "SubindexWriteError";
        case EC_SDO_COMPLETE_ACCESS_NOT_SUPPORTED: return "CompleteAccessNotSupported";
        case EC_SDO_OBJECT_LENGTH_EXCEEDED:     return "ObjectLengthExceeded";
        case EC_SDO_OBJECT_MAPPED_TO_RXPDO:     return "ObjectMappedToRxPDO";
        case EC_SDO_OBJECT_DOES_NOT_EXIST:      return "ObjectDoesNotExist";
        case EC_SDO_CANNOT_MAP_TO_PDO:          return "CannotBeMappedToPDO";
        case EC_SDO_EXCEEDS_PDO_LENGTH:         return "ExceedsPDOLength";
        case EC_SDO_PARAMETER_INCOMPATIBILITY:  return "ParameterIncompatibility";
        case EC_SDO_INTERNAL_INCOMPATIBILITY:   return "InternalIncompatibility";
        case EC_SDO_HARDWARE_ACCESS_ERROR:      return "HardwareAccessError";
        case EC_SDO_DATA_TYPE_MISMATCH:         return "DataTypeMismatch";
        case EC_SDO_DATA_TYPE_TOO_HIGH:         return "DataTypeTooHigh";
        case EC_SDO_DATA_TYPE_TOO_LOW:          return "DataTypeTooLow";
        case EC_SDO_SUBINDEX_DOES_NOT_EXIST:    return "SubindexDoesNotExist";
        case EC_SDO_VALUE_RANGE_EXCEEDED:       return "ValueRangeExceeded";
        case EC_SDO_VALUE_TOO_HIGH:             return "ValueTooHigh";
        case EC_SDO_VALUE_TOO_LOW:              return "ValueTooLow";
        case EC_SDO_MODULE_IDENT_MISMATCH:      return "ModuleIdentListMismatch";
        case EC_SDO_MAX_LESS_THAN_MIN:          return "MaxLessThanMin";
        case EC_SDO_GENERAL_ERROR:              return "GeneralError";
        case EC_SDO_DATA_TRANSFER_ERROR:        return "DataTransferError";
        case EC_SDO_LOCAL_CONTROL_ERROR:        return "LocalControlError";
        case EC_SDO_DEVICE_STATE_ERROR:         return "DeviceStateError";
        case EC_SDO_DICTIONARY_ERROR:           return "DictionaryError";
        default:                                return "UnknownError";
    }
}

typedef enum {
    EC_COE_ACCESS_NONE          = 0x00,
    EC_COE_ACCESS_READ_PREOP    = 0x01,
    EC_COE_ACCESS_READ_SAFEOP   = 0x02,
    EC_COE_ACCESS_READ_OP       = 0x04,
    EC_COE_ACCESS_WRITE_PREOP   = 0x08,
    EC_COE_ACCESS_WRITE_SAFEOP  = 0x10,
    EC_COE_ACCESS_WRITE_OP      = 0x20,
    EC_COE_ACCESS_READ_ANY      = 0x07,
    EC_COE_ACCESS_WRITE_ANY     = 0x38,
    EC_COE_ACCESS_READ_WRITE_ALL = 0x3F
} ec_coe_object_access_t;

typedef struct {
    int       access_denied;
    uint16_t  index;
    uint8_t   subindex;
    uint8_t   is_read;
    uint16_t  obj_access;
    char      entry_name[64];
    char      message[192];
} ec_coe_access_denied_t;

EC_COE_INLINE int ec_coe_can_read(uint16_t obj_access, ec_state_t state)
{
    switch (state) {
        case EC_STATE_PRE_OP:      return (obj_access & EC_COE_ACCESS_READ_PREOP)  ? 1 : 0;
        case EC_STATE_SAFE_OP:     return (obj_access & EC_COE_ACCESS_READ_SAFEOP) ? 1 : 0;
        case EC_STATE_OPERATIONAL: return (obj_access & EC_COE_ACCESS_READ_OP)     ? 1 : 0;
        default:                   return 0;
    }
}

EC_COE_INLINE int ec_coe_can_write(uint16_t obj_access, ec_state_t state)
{
    switch (state) {
        case EC_STATE_PRE_OP:      return (obj_access & EC_COE_ACCESS_WRITE_PREOP)  ? 1 : 0;
        case EC_STATE_SAFE_OP:     return (obj_access & EC_COE_ACCESS_WRITE_SAFEOP) ? 1 : 0;
        case EC_STATE_OPERATIONAL: return (obj_access & EC_COE_ACCESS_WRITE_OP)     ? 1 : 0;
        default:                   return 0;
    }
}

typedef struct {
    void*    odlist_ptr;
    uint16_t master_index;
    uint16_t slave_index;
    int      count;
    int      loaded;
} ec_coe_odlist_t;

EC_COE_INLINE void ec_coe_odlist_wrap(ec_coe_odlist_t* out,
                                         void* odlist_ptr,
                                         uint16_t master_index,
                                         uint16_t slave_index,
                                         int count)
{
    if (!out) return;
    out->odlist_ptr   = odlist_ptr;
    out->master_index = master_index;
    out->slave_index  = slave_index;
    out->count        = count;
    out->loaded       = (odlist_ptr != NULL) ? 1 : 0;
}

EC_COE_INLINE void ec_coe_odlist_clear(ec_coe_odlist_t* out)
{
    if (!out) return;
    out->odlist_ptr   = NULL;
    out->count        = 0;
    out->loaded       = 0;
}

EC_COE_INLINE void ec_coe_sdo_status_set(ec_coe_sdo_status_t* s,
                                          ec_sdo_error_t code,
                                          uint16_t index, uint8_t subindex,
                                          int is_read)
{
    if (!s) return;
    s->code     = code;
    s->index    = index;
    s->subindex = subindex;
    s->is_read  = (uint8_t)(is_read ? 1 : 0);
}

EC_COE_INLINE void ec_coe_sdo_status_reset(ec_coe_sdo_status_t* s)
{
    if (!s) return;
    s->code     = EC_SDO_NO_ERROR;
    s->index    = 0;
    s->subindex = 0;
    s->is_read  = 0;
}

typedef struct {
    uint16_t index;
    uint8_t  subindex;
} ec_sdo_entry_t;

typedef struct {
    uint16_t index;
    uint8_t  subindex;
    uint8_t* data;
    int      data_size;
} ec_sdo_read_result_t;

#ifndef DYNAMIC_LOAD

uint8_t  dx_sdo_write(uint16_t master_index, uint16_t slave, uint16_t index,
                  uint8_t subindex, BOOL CA, uint8_t* bytes, int length);

char*    dx_sdo_read(uint16_t master_index, uint16_t slave, uint16_t index,
                 uint8_t subindex, BOOL CA, int* out_byte_size);

void*    GetSlaveSDOList(uint16_t master_index, uint16_t slave_index);

void*    GetSlaveSDOListBasic(uint16_t master_index, uint16_t slave_index);
void*    GetSlavePointer_SDO(uint16_t master_index, uint16_t slave_index, uint16_t OE_index);
void*    GetSlavePointer_SDO_WithODList(uint16_t master_index, uint16_t slave_index,
                                        uint16_t OE_index, void* odlist);

char*    GetSlavePointer_SDO_Value(uint16_t master_index, uint16_t slave_index,
                                   uint16_t OD_index, uint8_t OE_index, int* out_byte_size);

char*    GetSlavePointer_SDO_IndexValue(uint16_t master_index, uint16_t slave_index,
                                        uint16_t index, uint8_t subidx, int* out_byte_size);

int      GetMultiSlaveSDOList(uint16_t master_index, uint16_t* slave_indices,
                               int count, void** results);

void     FreeMultiSlaveSDOList(void** results, int count);

int      EmcyGetHistory(uint16_t master, uint16_t slave,
                        ec_emcy_record_t* out, int max);

void     EmcyClearHistory(uint16_t master, uint16_t slave);

int      EmcyGetCount(uint16_t master, uint16_t slave);

int      SDOReadMultiple(uint16_t master_index, uint16_t slave_index,
                         const ec_sdo_entry_t* entries, int count,
                         ec_sdo_read_result_t* results);

#endif

#ifdef __cplusplus
}
#endif

#endif
