#ifndef _WS_DECODE_H_
#define _WS_DECODE_H_

#include <stdint.h>
#include <rfb/rfb.h>

#if defined(__APPLE__)

#include <libkern/OSByteOrder.h>
#define WS_NTOH64(n) OSSwapBigToHostInt64(n)
#define WS_NTOH32(n) OSSwapBigToHostInt32(n)
#define WS_NTOH16(n) OSSwapBigToHostInt16(n)
#define WS_HTON64(n) OSSwapHostToBigInt64(n)
#define WS_HTON16(n) OSSwapHostToBigInt16(n)

#else

#define WS_NTOH64(n) htobe64(n)
#define WS_NTOH32(n) htobe32(n)
#define WS_NTOH16(n) htobe16(n)
#define WS_HTON64(n) htobe64(n)
#define WS_HTON16(n) htobe16(n)

#endif

#define B64LEN(__x) (((__x + 2) / 3) * 12 / 3)
#define WSHLENMAX 14LL  /* 2 + sizeof(uint64_t) + sizeof(uint32_t) */
#define WS_HYBI_MASK_LEN 4

#define ARRAYSIZE(a) ((sizeof(a) / sizeof((a[0]))) / (size_t)(!(sizeof(a) % sizeof((a[0])))))

struct ws_ctx_s;
typedef struct ws_ctx_s ws_ctx_t;

typedef int (*wsEncodeFunc)(rfbClientPtr cl, const char *src, int len, char **dst);
typedef int (*wsDecodeFunc)(ws_ctx_t *wsctx, char *dst, int len);

typedef int (*wsReadFunc)(void *ctx, char *dst, size_t len);

typedef struct ctxInfo_s{
  void *ctxPtr;
  wsReadFunc readFunc;
} ctxInfo_t;

enum {
  /* header not yet received completely */
  WS_HYBI_STATE_HEADER_PENDING,
  /* data available */
  WS_HYBI_STATE_DATA_AVAILABLE,
  WS_HYBI_STATE_DATA_NEEDED,
  /* received a complete frame */
  WS_HYBI_STATE_FRAME_COMPLETE,
  /* received part of a 'close' frame */
  WS_HYBI_STATE_CLOSE_REASON_PENDING,
  /* */
  WS_HYBI_STATE_ERR
};

typedef union ws_mask_s {
  char c[4];
  uint32_t u;
} ws_mask_t;

/* XXX: The union and the structs do not need to be named.
 *      We are working around a bug present in GCC < 4.6 which prevented
 *      it from recognizing anonymous structs and unions.
 *      See http://gcc.gnu.org/bugzilla/show_bug.cgi?id=4784
 */
typedef struct 
#if __GNUC__
__attribute__ ((__packed__)) 
#endif
ws_header_s {
  unsigned char b0;
  unsigned char b1;
  union {
    struct 
#if __GNUC__
    __attribute__ ((__packed__)) 
#endif
           {
      uint16_t l16;
      ws_mask_t m16;
    } s16;
    struct
#if __GNUC__
__attribute__ ((__packed__)) 
#endif
           {
      uint64_t l64;
      ws_mask_t m64;
    } s64;
    ws_mask_t m;
  } u;
} ws_header_t;

typedef struct ws_header_data_s {
  ws_header_t *data;
  /** bytes read */
  int nRead;
  /** mask value */
  ws_mask_t mask;
  /** length of frame header including payload len, but without mask */
  int headerLen;
  /** length of the payload data */
  uint64_t payloadLen;
  /** opcode */
  unsigned char opcode;
  /** fin bit */
  unsigned char fin;
} ws_header_data_t;

typedef struct ws_ctx_s {
    char codeBufDecode[2048 + WSHLENMAX]; /* base64 + maximum frame header length */
    char codeBufEncode[B64LEN(UPDATE_BUF_SIZE) + WSHLENMAX]; /* base64 + maximum frame header length */
    char *writePos;
    unsigned char *readPos;
    int readlen;
    int hybiDecodeState;
    char carryBuf[3];                      /* For base64 carry-over */
    int carrylen;
    int base64;
    ws_header_data_t header;
    uint64_t nReadPayload;
    unsigned char continuation_opcode;
    wsEncodeFunc encode;
    wsDecodeFunc decode;
    ctxInfo_t ctxInfo;
} ws_ctx_t;

enum
{
    WS_OPCODE_CONTINUATION = 0x00,
    WS_OPCODE_TEXT_FRAME = 0x01,
    WS_OPCODE_BINARY_FRAME = 0x02,
    WS_OPCODE_CLOSE = 0x08,
    WS_OPCODE_PING = 0x09,
    WS_OPCODE_PONG = 0x0A,
    WS_OPCODE_INVALID = 0xFF
};

int webSocketsDecodeHybi(ws_ctx_t *wsctx, char *dst, int len);

void hybiDecodeCleanupComplete(ws_ctx_t *wsctx);
#endif
