#ifndef CHIRP_HPP
#define CHIRP_HPP

#include <stdint.h>
#include <stdlib.h>
#include <stdarg.h>
#include "link.h"

#define ALIGN(v, n)  v = v&((n)-1) ? (v&~((n)-1))+(n) : v
#define FOURCC(a, b, c, d)  (((uint32_t)a<<0)|((uint32_t)b<<8)|((uint32_t)c<<16)|((uint32_t)d<<24))

#define CRP_RES_OK                      0
#define CRP_RES_ERROR                   -1
#define CRP_RES_ERROR_RECV_TIMEOUT      LINK_RESULT_ERROR_RECV_TIMEOUT
#define CRP_RES_ERROR_SEND_TIMEOUT      LINK_RESULT_ERROR_SEND_TIMEOUT
#define CRP_RES_ERROR_CRC               -2
#define CRP_RES_ERROR_PARSE             -3
#define CRP_RES_ERROR_MAX_NAK           -4
#define CRP_RES_ERROR_MEMORY            -5
#define CRP_RES_ERROR_NOT_CONNECTED     -6

#define CRP_MAX_NAK                     3
#define CRP_RETRIES                     3
#define CRP_HEADER_TIMEOUT    	        1000
#define CRP_DATA_TIMEOUT                500
#define CRP_IDLE_TIMEOUT                500
#define CRP_SEND_TIMEOUT                1000
#define CRP_MAX_ARGS                    10
#define CRP_BUFSIZE                     0x80
#define CRP_BUFPAD                      8
#define CRP_PROCTABLE_LEN               0x40

#define CRP_START_CODE                  0xaaaa5555

#define CRP_CALL                        0x80
#define CRP_RESPONSE                    0x40
#define CRP_INTRINSIC                   0x20
#define CRP_DATA                        0x10
#define CRP_XDATA                       0x18 // data not associated with no associated procedure)
#define CRP_CALL_ENUMERATE              (CRP_CALL | CRP_INTRINSIC | 0x00)
#define CRP_CALL_INIT                   (CRP_CALL | CRP_INTRINSIC | 0x01)
#define CRP_CALL_ENUMERATE_INFO         (CRP_CALL | CRP_INTRINSIC | 0x02)

#define CRP_ACK                         0x59
#define CRP_NACK                        0x95
#define CRP_MAX_HEADER_LEN              64

#define CRP_ARRAY                       0x80 // bit
#define CRP_FLT                         0x10 // bit
#define CRP_NO_COPY                     (0x10 | 0x20)
#define CRP_HINT                        0x40 // bit
#define CRP_NULLTERM_ARRAY              (0x20 | CRP_ARRAY) // bits
#define CRP_INT8                        0x01
#define CRP_UINT8                       0x01
#define CRP_INT16                       0x02
#define CRP_UINT16                      0x02
#define CRP_INT32                       0x04
#define CRP_UINT32                      0x04
#define CRP_FLT32                       (CRP_FLT | 0x04)
#define CRP_FLT64                       (CRP_FLT | 0x08)
#define CRP_STRING                      (CRP_NULLTERM_ARRAY | CRP_INT8)
#define CRP_TYPE_HINT                   0x64 // type hint identifier
#define CRP_INTS8                       (CRP_INT8 | CRP_ARRAY)
#define CRP_INTS16                      (CRP_INT16 | CRP_ARRAY)
#define CRP_INTS32                      (CRP_INT32 | CRP_ARRAY)
#define CRP_UINTS8                      CRP_INTS8
#define CRP_UINTS8_NO_COPY              (CRP_INTS8 | CRP_NO_COPY)
#define CRP_UINTS16_NO_COPY             (CRP_INTS16 | CRP_NO_COPY)
#define CRP_UINTS32_NO_COPY             (CRP_INTS32 | CRP_NO_COPY)
#define CRP_UINTS16                     CRP_INTS16
#define CRP_UINTS32                     CRP_INTS32
#define CRP_FLTS32                      (CRP_FLT32 | CRP_ARRAY)
#define CRP_FLTS64                      (CRP_FLT64 | CRP_ARRAY)
#define CRP_HINT8                       (CRP_INT8 | CRP_HINT)
#define CRP_HINT16                      (CRP_INT16 | CRP_HINT)
#define CRP_HINT32                      (CRP_INT32 | CRP_HINT)
#define CRP_HINTS8                      (CRP_INT8 | CRP_ARRAY | CRP_HINT)
#define CRP_HINTS16                     (CRP_INT16 | CRP_ARRAY | CRP_HINT)
#define CRP_HINTS32                     (CRP_INT32 | CRP_ARRAY | CRP_HINT)
#define CRP_HFLTS32                     (CRP_FLT32 | CRP_ARRAY | CRP_HINT)
#define CRP_HFLTS64                     (CRP_FLT64 | CRP_ARRAY | CRP_HINT)
#define CRP_HSTRING                     (CRP_STRING | CRP_HINT)
// CRP_HTYPE is for arg lists which are uint8_t arrays
#define CRP_HTYPE(v)                    CRP_TYPE_HINT, (uint8_t)(v>>0&0xff), (uint8_t)(v>>8&0xff), (uint8_t)(v>>16&0xff), (uint8_t)(v>>24&0xff)

// regular call args
#define INT8(v)                         CRP_INT8, v
#define UINT8(v)                        CRP_INT8, v
#define INT16(v)                        CRP_INT16, v
#define UINT16(v)                       CRP_INT16, v
#define INT32(v)                        CRP_INT32, v
#define UINT32(v)                       CRP_INT32, v
#define FLT32(v)                        CRP_FLT32, v
#define FLT64(v)                        CRP_FLT64, v
#define STRING(s)                       CRP_STRING, s
#define INTS8(len, a)                   CRP_INTS8, len, a
#define UINTS8(len, a)                  CRP_INTS8, len, a
#define UINTS8_NO_COPY(len)             CRP_UINTS8_NO_COPY, len
#define UINTS16_NO_COPY(len)            CRP_UINTS16_NO_COPY, len
#define UINTS32_NO_COPY(len)            CRP_UINTS32_NO_COPY, len
#define INTS16(len, a)                  CRP_INTS16, len, a
#define UINTS16(len, a)                 CRP_INTS16, len, a
#define INTS32(len, a)                  CRP_INTS32, len, a
#define UINTS32(len, a)                 CRP_INTS32, len, a
#define FLTS32(len, a)                  CRP_FLTS32, len, a
#define FLTS64(len, a)                  CRP_FLTS64, len, a

// hint call args
#define HINT8(v)                        CRP_HINT8, v
#define UHINT8(v)                       CRP_HINT8, v
#define HINT16(v)                       CRP_HINT16, v
#define UHINT16(v)                      CRP_HINT16, v
#define HINT32(v)                       CRP_HINT32, v
#define UHINT32(v)                      CRP_HINT32, v
#define HFLT32(v)                       CRP_HFLT32, v
#define HFLT64(v)                       CRP_HFLT64, v
#define HSTRING(s)                      CRP_HSTRING, s
#define HINTS8(len, a)                  CRP_HINTS8, len, a
#define UHINTS8(len, a)                 CRP_HINTS8, len, a
#define HINTS16(len, a)                 CRP_HINTS16, len, a
#define UHINTS16(len, a)                CRP_HINTS16, len, a
#define HINTS32(len, a)                 CRP_HINTS32, len, a
#define UHINTS32(len, a)                CRP_HINTS32, len, a
#define HFLTS32(len, a)                 CRP_HFLTS32, len, a
#define HFLTS64(len, a)                 CRP_HFLTS64, len, a
#define HTYPE(v)                        CRP_TYPE_HINT, v

#define INT8_IN(v)                      int8_t & v
#define UINT8_IN(v)                     uint8_t & v
#define INT16_IN(v)                     int16_t & v
#define UINT16_IN(v)                    uint16_t & v
#define INT32_IN(v)                     int32_t & v
#define UINT32_IN(v)                    uint32_t & v
#define FLT32_IN(v)                     float & v
#define FLT64_IN(v)                     double & v
#define STRING_IN(s)                    const char * s
#define INTS8_IN(len, a)                uint32_t & len, int8_t * a
#define UINTS8_IN(len, a)               uint32_t & len, uint8_t * a
#define INTS16_IN(len, a)               uint32_t & len, int16_t * a
#define UINTS16_IN(len, a)              uint32_t & len, uint16_t * a
#define INTS32_IN(len, a)               uint32_t & len, int32_t * a
#define UINTS32_IN(len, a)              uint32_t & len, uint32_t * a
#define FLTS32_IN(len, a)               uint32_t & len, float * a
#define FLTS64_IN(len, a)               uint32_t & len, double * a

#ifndef END
#ifdef __x86_64__
#define END                             (int64_t)0
#else
#define END                             0
#endif
#endif
#define END_OUT_ARGS                    END
#define END_IN_ARGS                     END

// service types
#define SYNC                            0
#define ASYNC                           0x01 // bit
#define RETURN_ARRAY                    0x02 // bit
#define SYNC_RETURN_ARRAY               (SYNC | RETURN_ARRAY)

#define CRP_RETURN(chirp, ...)          chirp->assemble(0, __VA_ARGS__, END)
#define CRP_SEND_XDATA(chirp, ...)      chirp->assemble(CRP_XDATA, __VA_ARGS__, END)
#define callSync(...)                   call(SYNC, __VA_ARGS__, END)
#define callAsync(...)                  call(ASYNC, __VA_ARGS__, END)
#define callSyncArray(...)              call(SYNC_RETURN_ARRAY, __VA_ARGS__, END)

class Chirp;

typedef int16_t ChirpProc; // negative values are invalid

typedef uint32_t (*ProcPtr)(Chirp *);

struct ProcModule
{
    char *procName;
    ProcPtr procPtr;
    uint8_t argTypes[CRP_MAX_ARGS];
    char *procInfo;
};

struct ProcTableExtension
{
    uint8_t argTypes[CRP_MAX_ARGS];
    char *procInfo;
};

struct ProcInfo
{
    char *procName;
    uint8_t *argTypes;
    char *procInfo;
};

struct ProcTableEntry
{
    const char *procName;
    ProcPtr procPtr;
    ChirpProc chirpProc;
    const ProcTableExtension *extension;
};

class Chirp
{
public:
    Chirp(bool hinterested=false, bool client=false, Link *link=NULL);
    ~Chirp();

    virtual int init(bool connect);
    int setLink(Link *link);
    ChirpProc getProc(const char *procName, ProcPtr callback=0);
    int setProc(const char *procName, ProcPtr proc,  ProcTableExtension *extension=NULL);
    int getProcInfo(ChirpProc proc, ProcInfo *info);
    int registerModule(const ProcModule *module);
    void setSendTimeout(uint32_t timeout);
    void setRecvTimeout(uint32_t timeout);

    int call(uint8_t service, ChirpProc proc, ...);
    int call(uint8_t service, ChirpProc proc, va_list args);
    static uint8_t getType(const void *arg);
    int service(bool all=true);
    int assemble(uint8_t type, ...);
    bool connected();

    // utility methods
    static int serialize(Chirp *chirp, uint8_t *buf, uint32_t bufSize, ...);
    static int deserialize(uint8_t *buf, uint32_t len, ...);
    static int vserialize(Chirp *chirp, uint8_t *buf, uint32_t bufSize, va_list *args);
    static int vdeserialize(uint8_t *buf, uint32_t len, va_list *args);
    static int deserializeParse(uint8_t *buf, uint32_t len, void *args[]);
    static int loadArgs(va_list *args, void *recvArgs[]);
    static int getArgList(uint8_t *buf, uint32_t len, uint8_t *argList);
    int useBuffer(uint8_t *buf, uint32_t len);

    static uint16_t calcCrc(uint8_t *buf, uint32_t len);

protected:
    int remoteInit(bool connect);
    int recvChirp(uint8_t *type, ChirpProc *proc, void *args[], bool wait=false); // null pointer terminates
    virtual int handleChirp(uint8_t type, ChirpProc proc, const void *args[]); // null pointer terminates
    virtual void handleXdata(const void *data[]) {(void)data;}
    virtual int sendChirp(uint8_t type, ChirpProc proc);

    uint8_t *m_buf;
    uint8_t *m_bufSave;
    uint32_t m_len;
    uint32_t m_offset;
    uint32_t m_bufSize;
    bool m_errorCorrected;
    bool m_sharedMem;
    bool m_hinformer;
    bool m_hinterested;
    bool m_client;
    uint32_t m_headerLen;
    uint16_t m_headerTimeout;
    uint16_t m_dataTimeout;
    uint16_t m_idleTimeout;
    uint16_t m_sendTimeout;

private:
    int sendHeader(uint8_t type, ChirpProc proc);
    int sendFull(uint8_t type, ChirpProc proc);
    int sendData();
    int sendAck(bool ack); // false=nack
    int sendChirpRetry(uint8_t type, ChirpProc proc);
    int recvHeader(uint8_t *type, ChirpProc *proc, bool wait);
    int recvFull(uint8_t *type, ChirpProc *proc, bool wait);
    int recvData();
    int recvAck(bool *ack, uint16_t timeout); // false=nack
    int32_t handleEnumerate(char *procName, ChirpProc *callback);
    int32_t handleInit(uint16_t *blkSize, uint8_t *hintSource);
    int32_t handleEnumerateInfo(ChirpProc *proc);
    int vassemble(va_list *args);
    void restoreBuffer();

    ChirpProc updateTable(const char *procName, ProcPtr procPtr);
    ChirpProc lookupTable(const char *procName);
    int realloc(uint32_t min=0);
    int reallocTable();

    Link *m_link;
    ProcTableEntry *m_procTable;
    uint16_t m_procTableSize;
    uint16_t m_blkSize;
    uint8_t m_maxNak;
    uint8_t m_retries;
    bool m_call;
    bool m_connected;
};

#endif // CHIRP_H
