#ifndef _TERMPTY_H__
#define _TERMPTY_H__ 1

#include "config.h"
#include "media.h"
#include "sb.h"

typedef struct _Termcell      Termcell;
typedef struct _Termatt       Termatt;
typedef struct _Termsave      Termsave;
typedef struct _Termsavecomp  Termsavecomp;
typedef struct _Termblock     Termblock;
typedef struct _Termexp       Termexp;
typedef struct _Termpty       Termpty;
typedef struct _Termlink      Term_Link;
typedef struct _TitleIconElem TitleIconElem;

#define COL_DEF        0
#define COL_BLACK      1
#define COL_RED        2
#define COL_GREEN      3
#define COL_YELLOW     4
#define COL_BLUE       5
#define COL_MAGENTA    6
#define COL_CYAN       7
#define COL_WHITE      8
#define COL_INVIS      9

#define COL_INVERSE   10
#define COL_INVERSEBG 11

#define MOUSE_OFF              0
#define MOUSE_X10              1 // Press only
#define MOUSE_NORMAL           2 // Press+release only
#define MOUSE_NORMAL_BTN_MOVE  3 // Press+release+motion while pressed
#define MOUSE_NORMAL_ALL_MOVE  4 // Press+release+all motion

#define MOUSE_EXT_NONE         0
#define MOUSE_EXT_UTF8         1
#define MOUSE_EXT_SGR          2
#define MOUSE_EXT_URXVT        3

// Only for testing purpose
//#define SUPPORT_80_132_COLUMNS 1

#define MOVIE_STATE_PLAY   0
#define MOVIE_STATE_PAUSE  1
#define MOVIE_STATE_STOP   2

#define HL_LINKS_MAX  (1 << 16)

struct _Termlink
{
    const char *key;
    const char *url;
    unsigned int refcount;
};




struct _Termatt
{
   uint8_t fg, bg;
   unsigned short bold : 1;
   unsigned short faint : 1;
   unsigned short italic : 1;
   unsigned short dblwidth : 1;
   unsigned short underline : 1;
   unsigned short blink : 1; // don't intend to support this currently
   unsigned short blink2 : 1; // don't intend to support this currently
   unsigned short inverse : 1;
   unsigned short invisible : 1;
   unsigned short strike : 1;
   unsigned short fg256 : 1;
   unsigned short bg256 : 1;
   unsigned short fgintense : 1;
   unsigned short bgintense : 1;
   // below used for working out text from selections
   unsigned short autowrapped : 1;
   unsigned short newline : 1;
   unsigned short fraktur : 1;
   unsigned short framed : 1;
   unsigned short encircled : 1;
   unsigned short overlined : 1; // TODO: support it
   unsigned short tab_inserted : 1;
   unsigned short tab_last : 1;
#if defined(SUPPORT_80_132_COLUMNS)
   unsigned short is_80_132_mode_allowed : 1;
   unsigned short bit_padding :  9;
#else
   unsigned short bit_padding : 10;
#endif
   uint16_t       link_id;
};

typedef struct _Backlog_Beacon{
    int screen_y;
    int backlog_y;
} Backlog_Beacon;

typedef struct _Term_State {
    Termatt       att;
    unsigned char charset;
    unsigned char charsetch;
    unsigned char chset[4];
    int           top_margin, bottom_margin;
    int           left_margin, right_margin;
    int           had_cr_x, had_cr_y;
    unsigned int  lr_margins : 1;
    unsigned int  restrict_cursor : 1;
    unsigned int  multibyte : 1;
    unsigned int  alt_kp : 1;
    unsigned int  insert : 1;
    unsigned int  appcursor : 1;
    unsigned int  wrap : 1;
    unsigned int  wrapnext : 1;
    unsigned int  crlf : 1;
    unsigned int  send_bs : 1;
    unsigned int  kbd_lock : 1;
    unsigned int  reverse : 1;
    unsigned int  no_autorepeat : 1;
    unsigned int  cjk_ambiguous_wide : 1;
    unsigned int  hide_cursor : 1;
    unsigned int  combining_strike : 1;
    unsigned int  sace_rectangular : 1;
} Term_State;

typedef struct _Term_Cursor {
    int cx;
    int cy;
} Term_Cursor;

struct _Termpty
{
   Evas_Object *obj;
   Config *config;
   Ecore_Event_Handler *hand_exe_exit;
   Ecore_Fd_Handler *hand_fd;
   struct {
      struct {
         void (*func) (void *data);
         void *data;
      } change, set_title, set_icon, cancel_sel, exited, bell, command;
   } cb;
   struct {
      const char *icon;
      /* dynamic title set by xterm, keep it in case user don't want a
       * title any more by setting a empty title
       */
      const char *title;
      /* set by user */
      const char *user_title;
   } prop;
   const char *cur_cmd;
   Termcell *screen, *screen2;
   unsigned int *tabs;
   int circular_offset;
   int circular_offset2;
   Eina_Unicode *buf;
   size_t buflen;
   Eina_Unicode last_char;
   Eina_Bool buf_have_zero;
   unsigned char oldbuf[4];
   Termsave *back;
   size_t backsize, backpos;
   /* this beacon in the backlog tells about the top line in screen
    * coordinates that maps to a line in the backlog */
   Backlog_Beacon backlog_beacon;
   int w, h;
   int fd, slavefd;
   struct ty_sb write_buffer;
   struct {
      int curid;
      Eina_Hash *blocks;
      Eina_Hash *chid_map;
      Eina_List *active;
      Eina_List *expecting;
      unsigned char on : 1;
   } block;
   struct {
      /* start is always the start of the selection
       * so end.y can be < start.y */
      struct {
         int x, y;
      } start, end, orig;
      Eina_Unicode *codepoints;
      time_t last_click;
      unsigned char is_box    : 1;
      unsigned char is_active : 1; // there is a visible selection
      unsigned char makesel   : 1; // selection is being worked on
      unsigned char by_word   : 1;
      unsigned char by_line   : 1;
      unsigned char is_top_to_bottom : 1;
   } selection;
   Term_State termstate;
   Term_Cursor cursor_state;
   Term_Cursor cursor_save[2];
   int exit_code;
   pid_t pid;
   unsigned int altbuf     : 1;
   unsigned int mouse_mode : 3;
   unsigned int mouse_ext  : 2;
   unsigned int bracketed_paste : 1;
   unsigned int decoding_error : 1;
   unsigned int focus_reporting : 1;
   struct {
       Term_Link *links;
       uint8_t *bitmap;
       uint32_t size;
   } hl;
   TitleIconElem *title_icon_stack;
};

struct _Termcell
{
   Eina_Unicode   codepoint;
   Termatt        att;
};

struct _Termsave
{
   unsigned int   gen  : 8;
   unsigned int   comp : 1;
   unsigned int   z    : 1;
   unsigned int   w    : 22;
   /* TODO: union ? */
   Termcell       *cells;
};

/* TODO: RESIZE rewrite Termsavecomp */
struct _Termsavecomp
{
   unsigned int   gen  : 8;
   unsigned int   comp : 1;
   unsigned int   z    : 1;
   unsigned int   w    : 22; // compressed size in bytes
   unsigned int   wout; // output width in Termcells
};

struct _Termblock
{
   Termpty     *pty;
   const char  *path, *link, *chid;
   Evas_Object *obj;
   Eina_List   *cmds;
   int          id;
   Media_Type   type;
   int          refs;
   short        w, h;
   short        x, y;
   unsigned char scale_stretch : 1;
   unsigned char scale_center : 1;
   unsigned char scale_fill : 1;
   unsigned char thumb : 1;
   unsigned char edje : 1;

   unsigned char active : 1;
   unsigned char was_active : 1;
   unsigned char was_active_before : 1;

   unsigned char mov_state : 2;  // movie state marker
};

struct _Termexp
{
   Eina_Unicode ch;
   int left, id;
   int x, y, w, h;
};


void       termpty_init(void);
void       termpty_shutdown(void);

Termpty   *termpty_new(const char *cmd, Eina_Bool login_shell, const char *cd,
                       int w, int h, Config *config, const char *title,
                       Ecore_Window window_id);
void       termpty_free(Termpty *ty);
void       termpty_config_update(Termpty *ty, Config *config);

Termcell  *termpty_cellrow_get(Termpty *ty, int y, ssize_t *wret);
Termcell * termpty_cell_get(Termpty *ty, int y_requested, int x_requested);
ssize_t termpty_row_length(Termpty *ty, int y);

#define TERMPTY_WRITE_STR(_S) \
   termpty_write(ty, _S, strlen(_S))
void       termpty_write(Termpty *ty, const char *input, int len);

void       termpty_resize(Termpty *ty, int new_w, int new_h);
void       termpty_resize_tabs(Termpty *ty, int old_w, int new_w);
void       termpty_backscroll_adjust(Termpty *ty, int *scroll);

pid_t      termpty_pid_get(const Termpty *ty);
void       termpty_block_free(Termblock *tb);
Termblock *termpty_block_new(Termpty *ty, int w, int h, const char *path, const char *link);
void       termpty_block_insert(Termpty *ty, int ch, Termblock *blk);
int        termpty_block_id_get(const Termcell *cell, int *x, int *y);
Termblock *termpty_block_get(const Termpty *ty, int id);
void       termpty_block_chid_update(Termpty *ty, Termblock *blk);
Termblock *termpty_block_chid_get(const Termpty *ty, const char *chid);

void       termpty_cell_codepoint_att_fill(Termpty *ty, Eina_Unicode codepoint, Termatt att, Termcell *dst, int n);
void       termpty_cells_set_content(Termpty *ty, Termcell *cells,
                          Eina_Unicode codepoint, int count);
void       termpty_screen_swap(Termpty *ty);

ssize_t termpty_line_length(const Termcell *cells, ssize_t nb_cells);

void termpty_handle_buf(Termpty *ty, const Eina_Unicode *codepoints, int len);
void termpty_handle_block_codepoint_overwrite_heavy(Termpty *ty, int oldc, int newc);

Term_Link * term_link_new(Termpty *ty);
void term_link_free(Termpty *ty, Term_Link *link);

int
termpty_color_class_get(Termpty *ty, const char *key,
                        int *r, int *g, int *b, int *a);
void
termpty_focus_report(Termpty *ty, Eina_Bool focus);

extern int _termpty_log_dom;

#define TERMPTY_SCREEN(Tpty, X, Y) \
  Tpty->screen[X + (((Y + Tpty->circular_offset) % Tpty->h) * Tpty->w)]

#define TERMPTY_RESTRICT_FIELD(Field, Min, Max) \
   do {                                         \
   if (Field >= Max)                            \
     Field = Max - 1;                           \
   else if (Field < Min)                        \
     Field = Min;                               \
   } while (0)

/* Try to trick the compiler into inlining the first test */
#define HANDLE_BLOCK_CODEPOINT_OVERWRITE(Tpty, OLDC, NEWC)                   \
do {                                                                         \
   if (EINA_UNLIKELY((OLDC | NEWC) & 0x80000000))                            \
       termpty_handle_block_codepoint_overwrite_heavy(Tpty, OLDC, NEWC);     \
} while (0)

#define TERMPTY_CELL_COPY(Tpty, Tsrc, Tdst, N)                               \
do {                                                                         \
   int __i;                                                                  \
                                                                             \
   for (__i = 0; __i < N; __i++)                                             \
     {                                                                       \
        HANDLE_BLOCK_CODEPOINT_OVERWRITE(Tpty,                               \
                                         (Tdst)[__i].codepoint,              \
                                         (Tsrc)[__i].codepoint);             \
        if (EINA_UNLIKELY((Tdst)[__i].att.link_id))                          \
          term_link_refcount_dec(ty, (Tdst)[__i].att.link_id, 1);            \
        if (EINA_UNLIKELY((Tsrc)[__i].att.link_id))                          \
          term_link_refcount_inc(ty, (Tsrc)[__i].att.link_id, 1);            \
     }                                                                       \
   memcpy(Tdst, Tsrc, N * sizeof(Termcell));                                 \
} while (0)


static inline void
term_link_refcount_inc(Termpty *ty, uint16_t link_id, uint16_t count)
{
   Term_Link *link;

   link = &ty->hl.links[link_id];
   link->refcount += count;
}

static inline void
term_link_refcount_dec(Termpty *ty, uint16_t link_id, uint16_t count)
{
   Term_Link *link;

   link = &ty->hl.links[link_id];
   link->refcount -= count;
   if (EINA_UNLIKELY(link->refcount == 0))
     term_link_free(ty, link);
}

static inline Eina_Bool
term_link_eq(Termpty *ty, Term_Link *hl, uint16_t link_id)
{
    Term_Link *hl2;
    uint16_t hl_id;

    if (link_id == 0)
        return EINA_FALSE;

    hl_id = hl - ty->hl.links;
    if (hl_id == link_id)
        return EINA_TRUE;
    hl2 = &ty->hl.links[link_id];
    if (!hl->key || !hl2->key ||
        strcmp(hl->key, hl2->key) != 0)
        return EINA_FALSE;
    return (strcmp(hl->url, hl2->url) == 0);
}

static inline void
termpty_cell_fill(Termpty *ty, Termcell *src, Termcell *dst, int n)
{
   int i;

   if (src)
     {
        for (i = 0; i < n; i++)
          {
             HANDLE_BLOCK_CODEPOINT_OVERWRITE(ty, dst[i].codepoint, src[0].codepoint);
             if (EINA_UNLIKELY(dst[i].att.link_id))
               term_link_refcount_dec(ty, dst[i].att.link_id, 1);

             dst[i] = src[0];
          }
        if (src[0].att.link_id)
          term_link_refcount_inc(ty, src[0].att.link_id, n);
     }
   else
     {
        for (i = 0; i < n; i++)
          {
             HANDLE_BLOCK_CODEPOINT_OVERWRITE(ty, dst[i].codepoint, 0);
             if (EINA_UNLIKELY(dst[i].att.link_id))
               term_link_refcount_dec(ty, dst[i].att.link_id, 1);

             memset(&(dst[i]), 0, sizeof(*dst));
          }
     }
}
#endif
