#pragma once

#include <stdarg.h>

#include "nvim/mbyte_defs.h"
#include "nvim/vterm/vterm_defs.h"

#ifdef DEBUG
# define DEBUG_LOG(...) fprintf(stderr, __VA_ARGS__)
#else
# define DEBUG_LOG(...)
#endif

#define ESC_S "\x1b"

#define INTERMED_MAX 16

#define CSI_ARGS_MAX 32
#define CSI_LEADER_MAX 16

#define BUFIDX_PRIMARY   0
#define BUFIDX_ALTSCREEN 1

#define KEY_ENCODING_DISAMBIGUATE 0x1
#define KEY_ENCODING_REPORT_EVENTS 0x2
#define KEY_ENCODING_REPORT_ALTERNATE 0x4
#define KEY_ENCODING_REPORT_ALL_KEYS 0x8
#define KEY_ENCODING_REPORT_ASSOCIATED 0x10

typedef struct VTermEncoding VTermEncoding;
typedef struct VTermKeyEncodingFlags VTermKeyEncodingFlags;

typedef struct {
  VTermEncoding *enc;

  // This size should be increased if required by other stateful encodings
  char data[4 * sizeof(uint32_t)];
} VTermEncodingInstance;

struct VTermPen {
  VTermColor fg;
  VTermColor bg;
  int uri;
  unsigned bold:1;
  unsigned underline:2;
  unsigned italic:1;
  unsigned blink:1;
  unsigned reverse:1;
  unsigned conceal:1;
  unsigned strike:1;
  unsigned font:4;  // To store 0-9
  unsigned small:1;
  unsigned baseline:2;
};

// https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement
struct VTermKeyEncodingFlags {
  bool disambiguate:1;
  bool report_events:1;
  bool report_alternate:1;
  bool report_all_keys:1;
  bool report_associated:1;
};

struct VTermKeyEncodingStack {
  VTermKeyEncodingFlags items[16];
  uint8_t size;  ///< Number of items in the stack. This is at least 1 and at
                 ///< most the length of the "items" array.
};

struct VTermState {
  VTerm *vt;

  const VTermStateCallbacks *callbacks;
  void *cbdata;

  const VTermStateFallbacks *fallbacks;
  void *fbdata;

  int rows;
  int cols;

  // Current cursor position
  VTermPos pos;

  int at_phantom;  // True if we're on the "81st" phantom column to defer a wraparound

  int scrollregion_top;
  int scrollregion_bottom;  // -1 means unbounded
#define SCROLLREGION_BOTTOM(state) ((state)->scrollregion_bottom > \
                                    -1 ? (state)->scrollregion_bottom : (state)->rows)
  int scrollregion_left;
#define SCROLLREGION_LEFT(state)  ((state)->mode.leftrightmargin ? (state)->scrollregion_left : 0)
  int scrollregion_right;  // -1 means unbounded
#define SCROLLREGION_RIGHT(state) ((state)->mode.leftrightmargin \
                                   && (state)->scrollregion_right > \
                                   -1 ? (state)->scrollregion_right : (state)->cols)

  // Bitvector of tab stops
  uint8_t *tabstops;

  // Primary and Altscreen; lineinfos[1] is lazily allocated as needed
  VTermLineInfo *lineinfos[2];

  // lineinfo will == lineinfos[0] or lineinfos[1], depending on altscreen
  VTermLineInfo *lineinfo;
#define ROWWIDTH(state, \
                 row) ((state)->lineinfo[(row)].doublewidth ? ((state)->cols / 2) : (state)->cols)
#define THISROWWIDTH(state) ROWWIDTH(state, (state)->pos.row)

  // Mouse state
  int mouse_col, mouse_row;
  int mouse_buttons;
  int mouse_flags;
#define MOUSE_WANT_CLICK 0x01
#define MOUSE_WANT_DRAG  0x02
#define MOUSE_WANT_MOVE  0x04

  enum { MOUSE_X10, MOUSE_UTF8, MOUSE_SGR, MOUSE_RXVT, } mouse_protocol;

// Last glyph output, for Unicode recombining purposes
  char grapheme_buf[MAX_SCHAR_SIZE];
  size_t grapheme_len;
  uint32_t grapheme_last;  // last added UTF-32 char
  GraphemeState grapheme_state;
  int combine_width;  // The width of the glyph above
  VTermPos combine_pos;   // Position before movement

  struct {
    unsigned keypad:1;
    unsigned cursor:1;
    unsigned autowrap:1;
    unsigned insert:1;
    unsigned newline:1;
    unsigned cursor_visible:1;
    unsigned cursor_blink:1;
    unsigned cursor_shape:2;
    unsigned alt_screen:1;
    unsigned origin:1;
    unsigned screen:1;
    unsigned leftrightmargin:1;
    unsigned bracketpaste:1;
    unsigned report_focus:1;
    unsigned theme_updates:1;
  } mode;

  VTermEncodingInstance encoding[4], encoding_utf8;
  int gl_set, gr_set, gsingle_set;

  struct VTermPen pen;

  VTermColor default_fg;
  VTermColor default_bg;
  VTermColor colors[16];  // Store the 8 ANSI and the 8 ANSI high-brights only

  int bold_is_highbright;

  unsigned protected_cell : 1;

// Saved state under DEC mode 1048/1049
  struct {
    VTermPos pos;
    struct VTermPen pen;

    struct {
      unsigned cursor_visible:1;
      unsigned cursor_blink:1;
      unsigned cursor_shape:2;
    } mode;
  } saved;

// Temporary state for DECRQSS parsing
  union {
    char decrqss[4];
    struct {
      uint16_t mask;
      enum {
        SELECTION_INITIAL,
        SELECTION_SELECTED,
        SELECTION_QUERY,
        SELECTION_SET_INITIAL,
        SELECTION_SET,
        SELECTION_INVALID,
      } state : 8;
      uint32_t recvpartial;
      uint32_t sendpartial;
    } selection;
  } tmp;

  struct {
    const VTermSelectionCallbacks *callbacks;
    void *user;
    char *buffer;
    size_t buflen;
  } selection;

  // Maintain two stacks, one for primary screen and one for altscreen
  struct VTermKeyEncodingStack key_encoding_stacks[2];
};

struct VTerm {
  const VTermAllocatorFunctions *allocator;
  void *allocdata;

  int rows;
  int cols;

  struct {
    unsigned utf8:1;
    unsigned ctrl8bit:1;
  } mode;

  struct {
    enum VTermParserState {
      NORMAL,
      CSI_LEADER,
      CSI_ARGS,
      CSI_INTERMED,
      DCS_COMMAND,
      // below here are the "string states"
      OSC_COMMAND,
      OSC,
      DCS_VTERM,
      APC,
      PM,
      SOS,
    } state;

    bool in_esc : 1;

    int intermedlen;
    char intermed[INTERMED_MAX];

    union {
      struct {
        int leaderlen;
        char leader[CSI_LEADER_MAX];

        int argi;
        long args[CSI_ARGS_MAX];
      } csi;
      struct {
        int command;
      } osc;
      struct {
        int commandlen;
        char command[CSI_LEADER_MAX];
      } dcs;
    } v;

    const VTermParserCallbacks *callbacks;
    void *cbdata;

    bool string_initial;

    bool emit_nul;
  } parser;

  // len == malloc()ed size; cur == number of valid bytes

  VTermOutputCallback *outfunc;
  void *outdata;

  char *outbuffer;
  size_t outbuffer_len;
  size_t outbuffer_cur;

  char *tmpbuffer;
  size_t tmpbuffer_len;

  VTermState *state;
  VTermScreen *screen;
};

struct VTermEncoding {
  void (*init)(VTermEncoding *enc, void *data);
  void (*decode)(VTermEncoding *enc, void *data, uint32_t cp[], int *cpi, int cplen,
                 const char bytes[], size_t *pos, size_t len);
};

typedef enum {
  ENC_UTF8,
  ENC_SINGLE_94,
} VTermEncodingType;

enum {
  C1_SS3 = 0x8f,
  C1_DCS = 0x90,
  C1_CSI = 0x9b,
  C1_ST  = 0x9c,
  C1_OSC = 0x9d,
};
