#ifndef DP_COMMON_H
#define DP_COMMON_H

#include <nng/nng.h>
#include <stdlib.h>
#include <sys/types.h>
#include "dp_types.h"
#include "log.h"

#ifdef __cplusplus
extern "C" {
#endif

#define dp_fatal(...) log_fatal(__VA_ARGS__)

typedef pid_t doomid_t; // unique id for each doom instance
typedef u16 dmt_t; // for DP_MessageType values

#define DoomScreenWidth   320
#define DoomScreenHeight  240
#define DoomBytesPerPixel   4
#define DoomFramePitch    (DoomScreenWidth * DoomBytesPerPixel)
#define DoomFrameSize     (DoomScreenWidth * DoomScreenHeight * DoomBytesPerPixel)
#define DoomMaxEvents      64

typedef enum DP_MessageType
{
    DP_MT_Invalid,
    DP_MT_DoomState,            // dooms can publish their state (MsgDoomState)
    DP_MT_DoomFrame,            // doom framebuffer data
    DP_MT_Command,              // DP_DoomCommand message to a specific doom instance
    DP_MT_McstCommand,          // DP_DoomCmannd multicast message to all dooms
    DP_MT_Inputs,               // controller publishes input state for dooms to consume
    DP_MT_COUNT
} DP_MessageType;

typedef enum DP_DoomState
{
    DP_DS_Unknown,
    DP_DS_Ready,
    DP_DS_Running,
    DP_DS_Endoom,
    DP_DS_COUNT,
} DP_DoomState;

typedef enum DP_DoomCommand
{
    DP_DC_Noop,
    DP_DC_RunDoom,
    DP_DC_Endoom,
    DP_DC_COUNT,
} DP_DoomCommand;

typedef struct
{
    dmt_t msgType;
} MessageBase;

typedef struct __attribute__((packed, aligned(4)))
{
    MessageBase head;
    doomid_t doomId;
    DP_DoomState doomState;
} MsgDoomState;

typedef struct __attribute__((packed, aligned(4)))
{
    MessageBase head;
    doomid_t doomId;
    u8 frame[DoomFrameSize];
} MsgDoomFrame;

typedef struct __attribute__((packed, aligned(4)))
{
    MessageBase head;
    doomid_t doomId;
    DP_DoomCommand cmd;
} MsgCommand;

typedef struct __attribute__((packed, aligned(4)))
{
    MessageBase head;
    DP_DoomCommand cmd;
} MsgMcstCommand;

// Doom input event types (from d_event.h).
typedef enum doom_evtype_t
{
    DP_ev_keydown,
    DP_ev_keyup,
    DP_ev_mouse,
    DP_ev_joystick
} dp_doom_evtype_t;

// Doom event structure (from d_event.h).
typedef struct
{
    dp_doom_evtype_t	type;
    int		data1;		// keys / mouse/joystick buttons
    int		data2;		// mouse/joystick x move
    int		data3;		// mouse/joystick y move
} dp_doom_event_t;

typedef struct __attribute__((packed, aligned(4)))
{
    MessageBase head;
    u8 eventCount;
    dp_doom_event_t events[DoomMaxEvents];
} MsgInputs;

void dp_errno_fatal(const char *const msg);
void dp_nng_fatal(const char *const msg, int rv);
void dp_nng_init_limits(int ncpu_max, int pool_thread_limit_max, int resolv_thread_limit);
static inline bool dp_nng_is_timeout(int res)
{
    return res == NNG_ETIMEDOUT || res == NNG_EAGAIN;
}

// Controller listens, dooms dial.
static const char *const CtrlUrlIpc = "ipc://666_ctrl.socket"; // controller publishes here
static const char *const DoomUrlIpc = "ipc://666_doom.socket"; // dooms publish here
static const char *const CtrlUrlTcp = "tcp://:42666"; // controller publishes here
static const char *const DoomUrlTcp = "tcp://:42667"; // dooms publish here
static const int CtrlPort = 42666; // controller publishes on this port
static const int DoomPort = 42667; // dooms publish here

nng_socket make_doom_pub(const char *url);
nng_socket make_doom_sub(const char *url);

int dp_recv_new_msg(nng_socket sock, nng_msg **msg_ptr);
int dp_recv_new_msg_nonblock(nng_socket sock, nng_msg **msg_ptr);
int dp_recv_new_msg_flags(nng_socket sock, nng_msg **msg_ptr, int flags);

static inline void *dp_nng_msg_body_if_size_ge(nng_msg *msg, size_t size, bool isFatal)
{
    if (nng_msg_len(msg) >= size)
        return nng_msg_body(msg);

    if (isFatal)
        dp_fatal("message body too short");

    return NULL;
}

// Size checked cast of message body to type, fatal version
#define DP_NNG_BODY_AS(msg, ToType) (ToType *) dp_nng_msg_body_if_size_ge(msg, sizeof(ToType), true)
// Size checked cast of message body to type, non-fatal version
#define DP_NNG_BODY_AS_NF(msg, ToType) (ToType *) dp_nng_msg_body_if_size_ge(msg, sizeof(ToType), false)

extern const char *const DP_DoomState_Strings[];

static inline const char *doomstate_to_string(DP_DoomState ds)
{
    return DP_DoomState_Strings[ds];
}

#ifdef __cplusplus
}
#endif

#endif /* DP_COMMON_H */
