#ifndef _device_h__
#define _device_h__

/** Device & Channel **
 * 
 * Logical Dracal device composed of metadata and multiple channels.
 * 
 * Each Channel consists of a chip, a numerical Quantity, and (optionally) user-
 * calibration points.
 * 
 * This module does not perform any communication with Dracal hardware. It is limited
 * in scope to holding device-related data and performing operations on that data in
 * a logical fashion.
 *
 */

#include <stdint.h>
#include <string.h>

#include "version.h"
#include "quantity.h"
#include "calpoint.h"
#include "list.h"

#ifdef __cplusplus
extern "C" {
#endif

/**
 * Maximum number of channels a device can hold.
 */
#define DEVICE_MAX_CHANNELS 256

/**
 * Length of a device's serial number string (excluding the terminating character)
 */
#define DEVICE_SERIAL_NUMBER_LEN 7

/**
 * Maximum length of a device's product name (excluding the terminating character)
 */
#define DEVICE_PRODUCT_NAME_LEN 63

/**
 * Return whether two serial number strings are equal. Strings need not be null-terminated,
 * but they need to be DEVICE_SERIAL_NUMBER_LEN characters long.
 */
#define DEVICE_SERIAL_NUMBERS_EQUAL(a, b) (!strncmp((a), (b), DEVICE_SERIAL_NUMBER_LEN))

/**
 * Flag indicating whether the Device is alive, meaning online and operating correctly.
 */
#define DEVICE_FLAG_ALIVE                   0x00000001

/**
 * Flag indicating that the Device is in an error state.
 */
#define DEVICE_FLAG_ERROR                   0x00000002

/**
 * Flag indicating that the Device is a special ticker device. (Used by DracalView)
 */
#define DEVICE_FLAG_TICKER                  0x00000004

/**
 * Flag indicating that the Device is a special math device. (Used by DracalView)
 */
#define DEVICE_FLAG_MATH                    0x00000008

/**
 * Flag indicating that the Device supports user-calibration points
 */
#define DEVICE_FLAG_CALPOINTS               0x00000010

/**
 * Flag indicating that the Device is connected locally
 */
#define DEVICE_FLAG_LOCAL                   0x00000020

/**
 * Flag indicating that the Device is connected remotely via Tenkinet
 */
#define DEVICE_FLAG_TENKINET                0x00000040

/**
 * Flag indicating that the Device is connected remotely via Tenkinet and subscribed for
 * data updates
 */
#define DEVICE_FLAG_TENKINET_SUBSCRIBED     0x00000080

/**
 * Channel which consists of a chip, a numerical Quantity, and (optionally) user-calibration
 * calibration points.
 */
typedef struct Channel {

    uint16_t chip_id;  // see `chip` module
    Quantity quantity;

    uint8_t calpoints_count;
    Point calpoints[CAL_POINTS_COUNT];

} Channel;

/**
 * Logical Dracal Device composed of metadata and multiple Channels.
 */
typedef struct Device {

    char serial_number[DEVICE_SERIAL_NUMBER_LEN + 1];
    char product_name[DEVICE_PRODUCT_NAME_LEN + 1];
    Version version;

    uint8_t port;
    uint32_t flags;  // bit mask; see flag constants defined earlier

    List channels;

} Device;

/**
 * Create a new Device on the heap. It must be deleted later with device_delete().
 * 
 * The newly created Device has all members initialized to zero.
 * 
 * Return: pointer to the newly allocated Device
 */
Device *device_new();

/**
 * Delete a Device previously created with device_new().
 */
void device_delete(Device *device);

/**
 * Initialize a Device declared on the stack. It must be cleared later with device_clear().
 * 
 * All members are set to zero.
 */
void device_init(Device *device);

/**
 * Clear a Device, i.e. free underlying data structures and set all members to zero
 * 
 * This function is intended to be called when the Device is no longer needed.
 * 
 * It is not necessary to call device_clear() before device_delete().
 */
void device_clear(Device *device);

/**
 * Create a deep-copy of a Device. The resulting clone is completely separate and must
 * be deleted later with device_delete().
 * 
 * Return: pointer to the new Device, which must be deleted later with device_delete()
 */
Device *device_clone(const Device *src);

/**
 * Add n Channels to the Device. New Channels have undefined chip and data.
 */
void device_add_channels(Device *device, uint8_t n);

/**
 * Remove unused channels declared by some Devices.
 */
void device_trim_channels(Device *device);

/**
 * Copy channels from src to dst. If the two Devices don't have the same number of channels,
 * the number of channels copied is the smaller of the two.
 */
void device_copy_channels(Device *dst, const Device *src);

/**
 * Invalidate the quantities of all of the Device's channels.
 */
void device_invalidate(Device *device);

/**
 * Find a Device by serial number in a List of Devices.
 * 
 * Arguments:
 * 
 *   list: pointer to a List of Device elements
 *   serial_number: string of the serial number find
 *   remove: boolean indicating whether to remove the matched Device from the List
 * 
 * Return: the matched Device, or NULL if none was found
 */
Device *device_find(List *list, const char *serial_number, char remove);

/**
 * Return whether the given flag mask is set on the Device
 */
#define device_flag_check(device, mask)  (((device)->flags & (mask)) == (mask))

/**
 * Set the given flag mask on the Device
 */
#define device_flag_set(device, mask)  ((device)->flags |= (mask))

/**
 * Clear the given flag mask on the Device
 */
#define device_flag_clear(device, mask)  ((device)->flags &= ~(mask))


#ifdef __cplusplus
}
#endif

#endif
