/* Copyright (C) 2022-2024 mintsuki and contributors.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <vendor/flanterm/flanterm.h>

// Tries to implement this standard for terminfo
// https://man7.org/linux/man-pages/man4/console_codes.4.html

static const uint32_t col256[] = {
    0x000000, 0x00005f, 0x000087, 0x0000af, 0x0000d7, 0x0000ff, 0x005f00,
    0x005f5f, 0x005f87, 0x005faf, 0x005fd7, 0x005fff, 0x008700, 0x00875f,
    0x008787, 0x0087af, 0x0087d7, 0x0087ff, 0x00af00, 0x00af5f, 0x00af87,
    0x00afaf, 0x00afd7, 0x00afff, 0x00d700, 0x00d75f, 0x00d787, 0x00d7af,
    0x00d7d7, 0x00d7ff, 0x00ff00, 0x00ff5f, 0x00ff87, 0x00ffaf, 0x00ffd7,
    0x00ffff, 0x5f0000, 0x5f005f, 0x5f0087, 0x5f00af, 0x5f00d7, 0x5f00ff,
    0x5f5f00, 0x5f5f5f, 0x5f5f87, 0x5f5faf, 0x5f5fd7, 0x5f5fff, 0x5f8700,
    0x5f875f, 0x5f8787, 0x5f87af, 0x5f87d7, 0x5f87ff, 0x5faf00, 0x5faf5f,
    0x5faf87, 0x5fafaf, 0x5fafd7, 0x5fafff, 0x5fd700, 0x5fd75f, 0x5fd787,
    0x5fd7af, 0x5fd7d7, 0x5fd7ff, 0x5fff00, 0x5fff5f, 0x5fff87, 0x5fffaf,
    0x5fffd7, 0x5fffff, 0x870000, 0x87005f, 0x870087, 0x8700af, 0x8700d7,
    0x8700ff, 0x875f00, 0x875f5f, 0x875f87, 0x875faf, 0x875fd7, 0x875fff,
    0x878700, 0x87875f, 0x878787, 0x8787af, 0x8787d7, 0x8787ff, 0x87af00,
    0x87af5f, 0x87af87, 0x87afaf, 0x87afd7, 0x87afff, 0x87d700, 0x87d75f,
    0x87d787, 0x87d7af, 0x87d7d7, 0x87d7ff, 0x87ff00, 0x87ff5f, 0x87ff87,
    0x87ffaf, 0x87ffd7, 0x87ffff, 0xaf0000, 0xaf005f, 0xaf0087, 0xaf00af,
    0xaf00d7, 0xaf00ff, 0xaf5f00, 0xaf5f5f, 0xaf5f87, 0xaf5faf, 0xaf5fd7,
    0xaf5fff, 0xaf8700, 0xaf875f, 0xaf8787, 0xaf87af, 0xaf87d7, 0xaf87ff,
    0xafaf00, 0xafaf5f, 0xafaf87, 0xafafaf, 0xafafd7, 0xafafff, 0xafd700,
    0xafd75f, 0xafd787, 0xafd7af, 0xafd7d7, 0xafd7ff, 0xafff00, 0xafff5f,
    0xafff87, 0xafffaf, 0xafffd7, 0xafffff, 0xd70000, 0xd7005f, 0xd70087,
    0xd700af, 0xd700d7, 0xd700ff, 0xd75f00, 0xd75f5f, 0xd75f87, 0xd75faf,
    0xd75fd7, 0xd75fff, 0xd78700, 0xd7875f, 0xd78787, 0xd787af, 0xd787d7,
    0xd787ff, 0xd7af00, 0xd7af5f, 0xd7af87, 0xd7afaf, 0xd7afd7, 0xd7afff,
    0xd7d700, 0xd7d75f, 0xd7d787, 0xd7d7af, 0xd7d7d7, 0xd7d7ff, 0xd7ff00,
    0xd7ff5f, 0xd7ff87, 0xd7ffaf, 0xd7ffd7, 0xd7ffff, 0xff0000, 0xff005f,
    0xff0087, 0xff00af, 0xff00d7, 0xff00ff, 0xff5f00, 0xff5f5f, 0xff5f87,
    0xff5faf, 0xff5fd7, 0xff5fff, 0xff8700, 0xff875f, 0xff8787, 0xff87af,
    0xff87d7, 0xff87ff, 0xffaf00, 0xffaf5f, 0xffaf87, 0xffafaf, 0xffafd7,
    0xffafff, 0xffd700, 0xffd75f, 0xffd787, 0xffd7af, 0xffd7d7, 0xffd7ff,
    0xffff00, 0xffff5f, 0xffff87, 0xffffaf, 0xffffd7, 0xffffff, 0x080808,
    0x121212, 0x1c1c1c, 0x262626, 0x303030, 0x3a3a3a, 0x444444, 0x4e4e4e,
    0x585858, 0x626262, 0x6c6c6c, 0x767676, 0x808080, 0x8a8a8a, 0x949494,
    0x9e9e9e, 0xa8a8a8, 0xb2b2b2, 0xbcbcbc, 0xc6c6c6, 0xd0d0d0, 0xdadada,
    0xe4e4e4, 0xeeeeee};

#define CHARSET_DEFAULT     0
#define CHARSET_DEC_SPECIAL 1

void flanterm_context_reinit(struct flanterm_context *ctx) {
  ctx->tab_size             = 8;
  ctx->autoflush            = true;
  ctx->cursor_enabled       = true;
  ctx->scroll_enabled       = true;
  ctx->control_sequence     = false;
  ctx->escape               = false;
  ctx->osc                  = false;
  ctx->osc_escape           = false;
  ctx->rrr                  = false;
  ctx->discard_next         = false;
  ctx->bold                 = false;
  ctx->bg_bold              = false;
  ctx->reverse_video        = false;
  ctx->dec_private          = false;
  ctx->insert_mode          = false;
  ctx->unicode_remaining    = 0;
  ctx->g_select             = 0;
  ctx->charsets[0]          = CHARSET_DEFAULT;
  ctx->charsets[1]          = CHARSET_DEC_SPECIAL;
  ctx->current_charset      = 0;
  ctx->escape_offset        = 0;
  ctx->esc_values_i         = 0;
  ctx->saved_cursor_x       = 0;
  ctx->saved_cursor_y       = 0;
  ctx->current_primary      = (size_t)-1;
  ctx->current_bg           = (size_t)-1;
  ctx->scroll_top_margin    = 0;
  ctx->scroll_bottom_margin = ctx->rows;
  ctx->oob_output           = FLANTERM_OOB_OUTPUT_ONLCR;
}

static void flanterm_putchar(struct flanterm_context *ctx, uint8_t c);

void flanterm_write(struct flanterm_context *ctx,
                    const char              *buf,
                    size_t                   count) {
  for (size_t i = 0; i < count; i++) {
    flanterm_putchar(ctx, buf[i]);
  }

  if (ctx->autoflush) {
    ctx->double_buffer_flush(ctx);
  }
}

static void sgr(struct flanterm_context *ctx) {
  size_t i = 0;

  if (!ctx->esc_values_i)
    goto def;

  for (; i < ctx->esc_values_i; i++) {
    size_t offset;

    if (ctx->esc_values[i] == 0) {
    def:
      if (ctx->reverse_video) {
        ctx->reverse_video = false;
        ctx->swap_palette(ctx);
      }
      ctx->bold            = false;
      ctx->bg_bold         = false;
      ctx->current_primary = (size_t)-1;
      ctx->current_bg      = (size_t)-1;
      ctx->set_text_bg_default(ctx);
      ctx->set_text_fg_default(ctx);
      continue;
    }

    else if (ctx->esc_values[i] == 1) {
      ctx->bold = true;
      if (ctx->current_primary != (size_t)-1) {
        if (!ctx->reverse_video) {
          ctx->set_text_fg_bright(ctx, ctx->current_primary);
        } else {
          ctx->set_text_bg_bright(ctx, ctx->current_primary);
        }
      } else {
        if (!ctx->reverse_video) {
          ctx->set_text_fg_default_bright(ctx);
        } else {
          ctx->set_text_bg_default_bright(ctx);
        }
      }
      continue;
    }

    else if (ctx->esc_values[i] == 5) {
      ctx->bg_bold = true;
      if (ctx->current_bg != (size_t)-1) {
        if (!ctx->reverse_video) {
          ctx->set_text_bg_bright(ctx, ctx->current_bg);
        } else {
          ctx->set_text_fg_bright(ctx, ctx->current_bg);
        }
      } else {
        if (!ctx->reverse_video) {
          ctx->set_text_bg_default_bright(ctx);
        } else {
          ctx->set_text_fg_default_bright(ctx);
        }
      }
      continue;
    }

    else if (ctx->esc_values[i] == 22) {
      ctx->bold = false;
      if (ctx->current_primary != (size_t)-1) {
        if (!ctx->reverse_video) {
          ctx->set_text_fg(ctx, ctx->current_primary);
        } else {
          ctx->set_text_bg(ctx, ctx->current_primary);
        }
      } else {
        if (!ctx->reverse_video) {
          ctx->set_text_fg_default(ctx);
        } else {
          ctx->set_text_bg_default(ctx);
        }
      }
      continue;
    }

    else if (ctx->esc_values[i] == 25) {
      ctx->bg_bold = false;
      if (ctx->current_bg != (size_t)-1) {
        if (!ctx->reverse_video) {
          ctx->set_text_bg(ctx, ctx->current_bg);
        } else {
          ctx->set_text_fg(ctx, ctx->current_bg);
        }
      } else {
        if (!ctx->reverse_video) {
          ctx->set_text_bg_default(ctx);
        } else {
          ctx->set_text_fg_default(ctx);
        }
      }
      continue;
    }

    else if (ctx->esc_values[i] >= 30 && ctx->esc_values[i] <= 37) {
      offset               = 30;
      ctx->current_primary = ctx->esc_values[i] - offset;

      if (ctx->reverse_video) {
        goto set_bg;
      }

    set_fg:
      if ((ctx->bold && !ctx->reverse_video) ||
          (ctx->bg_bold && ctx->reverse_video)) {
        ctx->set_text_fg_bright(ctx, ctx->esc_values[i] - offset);
      } else {
        ctx->set_text_fg(ctx, ctx->esc_values[i] - offset);
      }
      continue;
    }

    else if (ctx->esc_values[i] >= 40 && ctx->esc_values[i] <= 47) {
      offset          = 40;
      ctx->current_bg = ctx->esc_values[i] - offset;

      if (ctx->reverse_video) {
        goto set_fg;
      }

    set_bg:
      if ((ctx->bold && ctx->reverse_video) ||
          (ctx->bg_bold && !ctx->reverse_video)) {
        ctx->set_text_bg_bright(ctx, ctx->esc_values[i] - offset);
      } else {
        ctx->set_text_bg(ctx, ctx->esc_values[i] - offset);
      }
      continue;
    }

    else if (ctx->esc_values[i] >= 90 && ctx->esc_values[i] <= 97) {
      offset               = 90;
      ctx->current_primary = ctx->esc_values[i] - offset;

      if (ctx->reverse_video) {
        goto set_bg_bright;
      }

    set_fg_bright:
      ctx->set_text_fg_bright(ctx, ctx->esc_values[i] - offset);
      continue;
    }

    else if (ctx->esc_values[i] >= 100 && ctx->esc_values[i] <= 107) {
      offset          = 100;
      ctx->current_bg = ctx->esc_values[i] - offset;

      if (ctx->reverse_video) {
        goto set_fg_bright;
      }

    set_bg_bright:
      ctx->set_text_bg_bright(ctx, ctx->esc_values[i] - offset);
      continue;
    }

    else if (ctx->esc_values[i] == 39) {
      ctx->current_primary = (size_t)-1;

      if (ctx->reverse_video) {
        ctx->swap_palette(ctx);
      }

      if (!ctx->bold) {
        ctx->set_text_fg_default(ctx);
      } else {
        ctx->set_text_fg_default_bright(ctx);
      }

      if (ctx->reverse_video) {
        ctx->swap_palette(ctx);
      }

      continue;
    }

    else if (ctx->esc_values[i] == 49) {
      ctx->current_bg = (size_t)-1;

      if (ctx->reverse_video) {
        ctx->swap_palette(ctx);
      }

      if (!ctx->bg_bold) {
        ctx->set_text_bg_default(ctx);
      } else {
        ctx->set_text_bg_default_bright(ctx);
      }

      if (ctx->reverse_video) {
        ctx->swap_palette(ctx);
      }

      continue;
    }

    else if (ctx->esc_values[i] == 7) {
      if (!ctx->reverse_video) {
        ctx->reverse_video = true;
        ctx->swap_palette(ctx);
      }
      continue;
    }

    else if (ctx->esc_values[i] == 27) {
      if (ctx->reverse_video) {
        ctx->reverse_video = false;
        ctx->swap_palette(ctx);
      }
      continue;
    }

    // 256/RGB
    else if (ctx->esc_values[i] == 38 || ctx->esc_values[i] == 48) {
      bool fg = ctx->esc_values[i] == 38;

      i++;
      if (i >= ctx->esc_values_i) {
        break;
      }

      switch (ctx->esc_values[i]) {
      case 2: { // RGB
        if (i + 3 >= ctx->esc_values_i) {
          goto out;
        }

        uint32_t rgb_value = 0;

        rgb_value |= ctx->esc_values[i + 1] << 16;
        rgb_value |= ctx->esc_values[i + 2] << 8;
        rgb_value |= ctx->esc_values[i + 3];

        i += 3;

        (fg ? ctx->set_text_fg_rgb : ctx->set_text_bg_rgb)(ctx, rgb_value);

        break;
      }
      case 5: { // 256 colors
        if (i + 1 >= ctx->esc_values_i) {
          goto out;
        }

        uint32_t col = ctx->esc_values[i + 1];

        i++;

        if (col < 8) {
          (fg ? ctx->set_text_fg : ctx->set_text_bg)(ctx, col);
        } else if (col < 16) {
          (fg ? ctx->set_text_fg_bright : ctx->set_text_bg_bright)(ctx,
                                                                   col - 8);
        } else if (col < 256) {
          uint32_t rgb_value = col256[col - 16];
          (fg ? ctx->set_text_fg_rgb : ctx->set_text_bg_rgb)(ctx, rgb_value);
        }

        break;
      }
      default:
        continue;
      }
    }
  }

out:;
}

static void dec_private_parse(struct flanterm_context *ctx, uint8_t c) {
  ctx->dec_private = false;

  if (ctx->esc_values_i == 0) {
    return;
  }

  bool set;

  switch (c) {
  case 'h':
    set = true;
    break;
  case 'l':
    set = false;
    break;
  default:
    return;
  }

  switch (ctx->esc_values[0]) {
  case 25: {
    if (set) {
      ctx->cursor_enabled = true;
    } else {
      ctx->cursor_enabled = false;
    }
    return;
  }
  }

  if (ctx->callback != NULL) {
    ctx->callback(
        ctx, FLANTERM_CB_DEC, ctx->esc_values_i, (uintptr_t)ctx->esc_values, c);
  }
}

static void linux_private_parse(struct flanterm_context *ctx) {
  if (ctx->esc_values_i == 0) {
    return;
  }

  if (ctx->callback != NULL) {
    ctx->callback(ctx,
                  FLANTERM_CB_LINUX,
                  ctx->esc_values_i,
                  (uintptr_t)ctx->esc_values,
                  0);
  }
}

static void mode_toggle(struct flanterm_context *ctx, uint8_t c) {
  if (ctx->esc_values_i == 0) {
    return;
  }

  bool set;

  switch (c) {
  case 'h':
    set = true;
    break;
  case 'l':
    set = false;
    break;
  default:
    return;
  }

  switch (ctx->esc_values[0]) {
  case 4:
    ctx->insert_mode = set;
    return;
  }

  if (ctx->callback != NULL) {
    ctx->callback(ctx,
                  FLANTERM_CB_MODE,
                  ctx->esc_values_i,
                  (uintptr_t)ctx->esc_values,
                  c);
  }
}

static void osc_parse(struct flanterm_context *ctx, uint8_t c) {
  if (ctx->osc_escape && c == '\\') {
    goto cleanup;
  }

  ctx->osc_escape = false;

  switch (c) {
  case 0x1b:
    ctx->osc_escape = true;
    break;
  case '\a':
    goto cleanup;
  }

  return;

cleanup:
  ctx->osc_escape = false;
  ctx->osc        = false;
  ctx->escape     = false;
}

static void control_sequence_parse(struct flanterm_context *ctx, uint8_t c) {
  if (ctx->escape_offset == 2) {
    switch (c) {
    case '[':
      ctx->discard_next = true;
      goto cleanup;
    case '?':
      ctx->dec_private = true;
      return;
    }
  }

  if (c >= '0' && c <= '9') {
    if (ctx->esc_values_i == FLANTERM_MAX_ESC_VALUES) {
      return;
    }
    ctx->rrr = true;
    ctx->esc_values[ctx->esc_values_i] *= 10;
    ctx->esc_values[ctx->esc_values_i] += c - '0';
    return;
  }

  if (ctx->rrr == true) {
    ctx->esc_values_i++;
    ctx->rrr = false;
    if (c == ';')
      return;
  } else if (c == ';') {
    if (ctx->esc_values_i == FLANTERM_MAX_ESC_VALUES) {
      return;
    }
    ctx->esc_values[ctx->esc_values_i] = 0;
    ctx->esc_values_i++;
    return;
  }

  size_t esc_default;
  switch (c) {
  case 'J':
  case 'K':
  case 'q':
    esc_default = 0;
    break;
  default:
    esc_default = 1;
    break;
  }

  for (size_t i = ctx->esc_values_i; i < FLANTERM_MAX_ESC_VALUES; i++) {
    ctx->esc_values[i] = esc_default;
  }

  if (ctx->dec_private == true) {
    dec_private_parse(ctx, c);
    goto cleanup;
  }

  bool r              = ctx->scroll_enabled;
  ctx->scroll_enabled = false;
  size_t x, y;
  ctx->get_cursor_pos(ctx, &x, &y);

  switch (c) {
  case 'F':
    x = 0;
    // FALLTHRU
  case 'A': {
    if (ctx->esc_values[0] > y)
      ctx->esc_values[0] = y;
    size_t orig_y                   = y;
    size_t dest_y                   = y - ctx->esc_values[0];
    bool   will_be_in_scroll_region = false;
    if ((ctx->scroll_top_margin >= dest_y &&
         ctx->scroll_top_margin <= orig_y) ||
        (ctx->scroll_bottom_margin >= dest_y &&
         ctx->scroll_bottom_margin <= orig_y)) {
      will_be_in_scroll_region = true;
    }
    if (will_be_in_scroll_region && dest_y < ctx->scroll_top_margin) {
      dest_y = ctx->scroll_top_margin;
    }
    ctx->set_cursor_pos(ctx, x, dest_y);
    break;
  }
  case 'E':
    x = 0;
    // FALLTHRU
  case 'e':
  case 'B': {
    if (y + ctx->esc_values[0] > ctx->rows - 1)
      ctx->esc_values[0] = (ctx->rows - 1) - y;
    size_t orig_y                   = y;
    size_t dest_y                   = y + ctx->esc_values[0];
    bool   will_be_in_scroll_region = false;
    if ((ctx->scroll_top_margin >= orig_y &&
         ctx->scroll_top_margin <= dest_y) ||
        (ctx->scroll_bottom_margin >= orig_y &&
         ctx->scroll_bottom_margin <= dest_y)) {
      will_be_in_scroll_region = true;
    }
    if (will_be_in_scroll_region && dest_y >= ctx->scroll_bottom_margin) {
      dest_y = ctx->scroll_bottom_margin - 1;
    }
    ctx->set_cursor_pos(ctx, x, dest_y);
    break;
  }
  case 'a':
  case 'C':
    if (x + ctx->esc_values[0] > ctx->cols - 1)
      ctx->esc_values[0] = (ctx->cols - 1) - x;
    ctx->set_cursor_pos(ctx, x + ctx->esc_values[0], y);
    break;
  case 'D':
    if (ctx->esc_values[0] > x)
      ctx->esc_values[0] = x;
    ctx->set_cursor_pos(ctx, x - ctx->esc_values[0], y);
    break;
  case 'c':
    if (ctx->callback != NULL) {
      ctx->callback(ctx, FLANTERM_CB_PRIVATE_ID, 0, 0, 0);
    }
    break;
  case 'd':
    ctx->esc_values[0] -= 1;
    if (ctx->esc_values[0] >= ctx->rows)
      ctx->esc_values[0] = ctx->rows - 1;
    ctx->set_cursor_pos(ctx, x, ctx->esc_values[0]);
    break;
  case 'G':
  case '`':
    ctx->esc_values[0] -= 1;
    if (ctx->esc_values[0] >= ctx->cols)
      ctx->esc_values[0] = ctx->cols - 1;
    ctx->set_cursor_pos(ctx, ctx->esc_values[0], y);
    break;
  case 'H':
  case 'f':
    if (ctx->esc_values[0] != 0) {
      ctx->esc_values[0]--;
    }
    if (ctx->esc_values[1] != 0) {
      ctx->esc_values[1]--;
    }
    if (ctx->esc_values[1] >= ctx->cols)
      ctx->esc_values[1] = ctx->cols - 1;
    if (ctx->esc_values[0] >= ctx->rows)
      ctx->esc_values[0] = ctx->rows - 1;
    ctx->set_cursor_pos(ctx, ctx->esc_values[1], ctx->esc_values[0]);
    break;
  case 'M': {
    size_t count =
        ctx->esc_values[0] > ctx->rows ? ctx->rows : ctx->esc_values[0];
    for (size_t i = 0; i < count; i++) {
      ctx->scroll(ctx);
    }
    break;
  }
  case 'L': {
    size_t old_scroll_top_margin = ctx->scroll_top_margin;
    ctx->scroll_top_margin       = y;
    size_t count =
        ctx->esc_values[0] > ctx->rows ? ctx->rows : ctx->esc_values[0];
    for (size_t i = 0; i < count; i++) {
      ctx->revscroll(ctx);
    }
    ctx->scroll_top_margin = old_scroll_top_margin;
    break;
  }
  case 'n':
    switch (ctx->esc_values[0]) {
    case 5:
      if (ctx->callback != NULL) {
        ctx->callback(ctx, FLANTERM_CB_STATUS_REPORT, 0, 0, 0);
      }
      break;
    case 6:
      if (ctx->callback != NULL) {
        ctx->callback(ctx, FLANTERM_CB_POS_REPORT, x + 1, y + 1, 0);
      }
      break;
    }
    break;
  case 'q':
    if (ctx->callback != NULL) {
      ctx->callback(ctx, FLANTERM_CB_KBD_LEDS, ctx->esc_values[0], 0, 0);
    }
    break;
  case 'J':
    switch (ctx->esc_values[0]) {
    case 0: {
      size_t rows_remaining = ctx->rows - (y + 1);
      size_t cols_diff      = ctx->cols - (x + 1);
      size_t to_clear       = rows_remaining * ctx->cols + cols_diff + 1;
      for (size_t i = 0; i < to_clear; i++) {
        ctx->raw_putchar(ctx, ' ');
      }
      ctx->set_cursor_pos(ctx, x, y);
      break;
    }
    case 1: {
      ctx->set_cursor_pos(ctx, 0, 0);
      bool b = false;
      for (size_t yc = 0; yc < ctx->rows; yc++) {
        for (size_t xc = 0; xc < ctx->cols; xc++) {
          ctx->raw_putchar(ctx, ' ');
          if (xc == x && yc == y) {
            ctx->set_cursor_pos(ctx, x, y);
            b = true;
            break;
          }
        }
        if (b == true)
          break;
      }
      break;
    }
    case 2:
    case 3:
      ctx->clear(ctx, false);
      break;
    }
    break;
  case '@':
    for (size_t i = ctx->cols - 1;; i--) {
      ctx->move_character(ctx, i + ctx->esc_values[0], y, i, y);
      ctx->set_cursor_pos(ctx, i, y);
      ctx->raw_putchar(ctx, ' ');
      if (i == x) {
        break;
      }
    }
    ctx->set_cursor_pos(ctx, x, y);
    break;
  case 'P':
    for (size_t i = x + ctx->esc_values[0]; i < ctx->cols; i++)
      ctx->move_character(ctx, i - ctx->esc_values[0], y, i, y);
    ctx->set_cursor_pos(ctx, ctx->cols - ctx->esc_values[0], y);
    // FALLTHRU
  case 'X': {
    size_t count =
        ctx->esc_values[0] > ctx->cols ? ctx->cols : ctx->esc_values[0];
    for (size_t i = 0; i < count; i++)
      ctx->raw_putchar(ctx, ' ');
    ctx->set_cursor_pos(ctx, x, y);
    break;
  }
  case 'm':
    sgr(ctx);
    break;
  case 's':
    ctx->get_cursor_pos(ctx, &ctx->saved_cursor_x, &ctx->saved_cursor_y);
    break;
  case 'u':
    ctx->set_cursor_pos(ctx, ctx->saved_cursor_x, ctx->saved_cursor_y);
    break;
  case 'K':
    switch (ctx->esc_values[0]) {
    case 0: {
      for (size_t i = x; i < ctx->cols; i++)
        ctx->raw_putchar(ctx, ' ');
      ctx->set_cursor_pos(ctx, x, y);
      break;
    }
    case 1: {
      ctx->set_cursor_pos(ctx, 0, y);
      for (size_t i = 0; i < x; i++)
        ctx->raw_putchar(ctx, ' ');
      break;
    }
    case 2: {
      ctx->set_cursor_pos(ctx, 0, y);
      for (size_t i = 0; i < ctx->cols; i++)
        ctx->raw_putchar(ctx, ' ');
      ctx->set_cursor_pos(ctx, x, y);
      break;
    }
    }
    break;
  case 'r':
    if (ctx->esc_values[0] == 0) {
      ctx->esc_values[0] = 1;
    }
    if (ctx->esc_values[1] == 0) {
      ctx->esc_values[1] = 1;
    }
    ctx->scroll_top_margin    = 0;
    ctx->scroll_bottom_margin = ctx->rows;
    if (ctx->esc_values_i > 0) {
      ctx->scroll_top_margin = ctx->esc_values[0] - 1;
    }
    if (ctx->esc_values_i > 1) {
      ctx->scroll_bottom_margin = ctx->esc_values[1];
    }
    if (ctx->scroll_top_margin >= ctx->rows ||
        ctx->scroll_bottom_margin > ctx->rows ||
        ctx->scroll_top_margin >= (ctx->scroll_bottom_margin - 1)) {
      ctx->scroll_top_margin    = 0;
      ctx->scroll_bottom_margin = ctx->rows;
    }
    ctx->set_cursor_pos(ctx, 0, 0);
    break;
  case 'l':
  case 'h':
    mode_toggle(ctx, c);
    break;
  case ']':
    linux_private_parse(ctx);
    break;
  }

  ctx->scroll_enabled = r;

cleanup:
  ctx->control_sequence = false;
  ctx->escape           = false;
}

static void restore_state(struct flanterm_context *ctx) {
  ctx->bold            = ctx->saved_state_bold;
  ctx->bg_bold         = ctx->saved_state_bg_bold;
  ctx->reverse_video   = ctx->saved_state_reverse_video;
  ctx->current_charset = ctx->saved_state_current_charset;
  ctx->current_primary = ctx->saved_state_current_primary;
  ctx->current_bg      = ctx->saved_state_current_bg;

  ctx->restore_state(ctx);
}

static void save_state(struct flanterm_context *ctx) {
  ctx->save_state(ctx);

  ctx->saved_state_bold            = ctx->bold;
  ctx->saved_state_bg_bold         = ctx->bg_bold;
  ctx->saved_state_reverse_video   = ctx->reverse_video;
  ctx->saved_state_current_charset = ctx->current_charset;
  ctx->saved_state_current_primary = ctx->current_primary;
  ctx->saved_state_current_bg      = ctx->current_bg;
}

static void escape_parse(struct flanterm_context *ctx, uint8_t c) {
  ctx->escape_offset++;

  if (ctx->osc == true) {
    osc_parse(ctx, c);
    return;
  }

  if (ctx->control_sequence == true) {
    control_sequence_parse(ctx, c);
    return;
  }

  size_t x, y;
  ctx->get_cursor_pos(ctx, &x, &y);

  switch (c) {
  case ']':
    ctx->osc_escape = false;
    ctx->osc        = true;
    return;
  case '[':
    for (size_t i = 0; i < FLANTERM_MAX_ESC_VALUES; i++)
      ctx->esc_values[i] = 0;
    ctx->esc_values_i     = 0;
    ctx->rrr              = false;
    ctx->control_sequence = true;
    return;
  case '7':
    save_state(ctx);
    break;
  case '8':
    restore_state(ctx);
    break;
  case 'c':
    flanterm_context_reinit(ctx);
    ctx->clear(ctx, true);
    break;
  case 'D':
    if (y == ctx->scroll_bottom_margin - 1) {
      ctx->scroll(ctx);
      ctx->set_cursor_pos(ctx, x, y);
    } else {
      ctx->set_cursor_pos(ctx, x, y + 1);
    }
    break;
  case 'E':
    if (y == ctx->scroll_bottom_margin - 1) {
      ctx->scroll(ctx);
      ctx->set_cursor_pos(ctx, 0, y);
    } else {
      ctx->set_cursor_pos(ctx, 0, y + 1);
    }
    break;
  case 'M':
    // "Reverse linefeed"
    if (y == ctx->scroll_top_margin) {
      ctx->revscroll(ctx);
      ctx->set_cursor_pos(ctx, 0, y);
    } else {
      ctx->set_cursor_pos(ctx, 0, y - 1);
    }
    break;
  case 'Z':
    if (ctx->callback != NULL) {
      ctx->callback(ctx, FLANTERM_CB_PRIVATE_ID, 0, 0, 0);
    }
    break;
  case '(':
  case ')':
    ctx->g_select = c - '\'';
    break;
  }

  ctx->escape = false;
}

static bool dec_special_print(struct flanterm_context *ctx, uint8_t c) {
#define FLANTERM_DEC_SPCL_PRN(C) \
  ctx->raw_putchar(ctx, (C));    \
  return true;
  switch (c) {
  case '`':
    FLANTERM_DEC_SPCL_PRN(0x04)
  case '0':
    FLANTERM_DEC_SPCL_PRN(0xdb)
  case '-':
    FLANTERM_DEC_SPCL_PRN(0x18)
  case ',':
    FLANTERM_DEC_SPCL_PRN(0x1b)
  case '.':
    FLANTERM_DEC_SPCL_PRN(0x19)
  case 'a':
    FLANTERM_DEC_SPCL_PRN(0xb1)
  case 'f':
    FLANTERM_DEC_SPCL_PRN(0xf8)
  case 'g':
    FLANTERM_DEC_SPCL_PRN(0xf1)
  case 'h':
    FLANTERM_DEC_SPCL_PRN(0xb0)
  case 'j':
    FLANTERM_DEC_SPCL_PRN(0xd9)
  case 'k':
    FLANTERM_DEC_SPCL_PRN(0xbf)
  case 'l':
    FLANTERM_DEC_SPCL_PRN(0xda)
  case 'm':
    FLANTERM_DEC_SPCL_PRN(0xc0)
  case 'n':
    FLANTERM_DEC_SPCL_PRN(0xc5)
  case 'q':
    FLANTERM_DEC_SPCL_PRN(0xc4)
  case 's':
    FLANTERM_DEC_SPCL_PRN(0x5f)
  case 't':
    FLANTERM_DEC_SPCL_PRN(0xc3)
  case 'u':
    FLANTERM_DEC_SPCL_PRN(0xb4)
  case 'v':
    FLANTERM_DEC_SPCL_PRN(0xc1)
  case 'w':
    FLANTERM_DEC_SPCL_PRN(0xc2)
  case 'x':
    FLANTERM_DEC_SPCL_PRN(0xb3)
  case 'y':
    FLANTERM_DEC_SPCL_PRN(0xf3)
  case 'z':
    FLANTERM_DEC_SPCL_PRN(0xf2)
  case '~':
    FLANTERM_DEC_SPCL_PRN(0xfa)
  case '_':
    FLANTERM_DEC_SPCL_PRN(0xff)
  case '+':
    FLANTERM_DEC_SPCL_PRN(0x1a)
  case '{':
    FLANTERM_DEC_SPCL_PRN(0xe3)
  case '}':
    FLANTERM_DEC_SPCL_PRN(0x9c)
  }
#undef FLANTERM_DEC_SPCL_PRN

  return false;
}

// Following wcwidth related code inherited from:
// https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c

struct interval {
  uint32_t first;
  uint32_t last;
};

/* auxiliary function for binary search in interval table */
static int bisearch(uint32_t ucs, const struct interval *table, int max) {
  int min = 0;
  int mid;

  if (ucs < table[0].first || ucs > table[max].last)
    return 0;
  while (max >= min) {
    mid = (min + max) / 2;
    if (ucs > table[mid].last)
      min = mid + 1;
    else if (ucs < table[mid].first)
      max = mid - 1;
    else
      return 1;
  }

  return 0;
}

int mk_wcwidth(uint32_t ucs) {
  /* sorted list of non-overlapping intervals of non-spacing characters */
  /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */
  static const struct interval combining[] = {
      {0x0300, 0x036F},   {0x0483, 0x0486},   {0x0488, 0x0489},
      {0x0591, 0x05BD},   {0x05BF, 0x05BF},   {0x05C1, 0x05C2},
      {0x05C4, 0x05C5},   {0x05C7, 0x05C7},   {0x0600, 0x0603},
      {0x0610, 0x0615},   {0x064B, 0x065E},   {0x0670, 0x0670},
      {0x06D6, 0x06E4},   {0x06E7, 0x06E8},   {0x06EA, 0x06ED},
      {0x070F, 0x070F},   {0x0711, 0x0711},   {0x0730, 0x074A},
      {0x07A6, 0x07B0},   {0x07EB, 0x07F3},   {0x0901, 0x0902},
      {0x093C, 0x093C},   {0x0941, 0x0948},   {0x094D, 0x094D},
      {0x0951, 0x0954},   {0x0962, 0x0963},   {0x0981, 0x0981},
      {0x09BC, 0x09BC},   {0x09C1, 0x09C4},   {0x09CD, 0x09CD},
      {0x09E2, 0x09E3},   {0x0A01, 0x0A02},   {0x0A3C, 0x0A3C},
      {0x0A41, 0x0A42},   {0x0A47, 0x0A48},   {0x0A4B, 0x0A4D},
      {0x0A70, 0x0A71},   {0x0A81, 0x0A82},   {0x0ABC, 0x0ABC},
      {0x0AC1, 0x0AC5},   {0x0AC7, 0x0AC8},   {0x0ACD, 0x0ACD},
      {0x0AE2, 0x0AE3},   {0x0B01, 0x0B01},   {0x0B3C, 0x0B3C},
      {0x0B3F, 0x0B3F},   {0x0B41, 0x0B43},   {0x0B4D, 0x0B4D},
      {0x0B56, 0x0B56},   {0x0B82, 0x0B82},   {0x0BC0, 0x0BC0},
      {0x0BCD, 0x0BCD},   {0x0C3E, 0x0C40},   {0x0C46, 0x0C48},
      {0x0C4A, 0x0C4D},   {0x0C55, 0x0C56},   {0x0CBC, 0x0CBC},
      {0x0CBF, 0x0CBF},   {0x0CC6, 0x0CC6},   {0x0CCC, 0x0CCD},
      {0x0CE2, 0x0CE3},   {0x0D41, 0x0D43},   {0x0D4D, 0x0D4D},
      {0x0DCA, 0x0DCA},   {0x0DD2, 0x0DD4},   {0x0DD6, 0x0DD6},
      {0x0E31, 0x0E31},   {0x0E34, 0x0E3A},   {0x0E47, 0x0E4E},
      {0x0EB1, 0x0EB1},   {0x0EB4, 0x0EB9},   {0x0EBB, 0x0EBC},
      {0x0EC8, 0x0ECD},   {0x0F18, 0x0F19},   {0x0F35, 0x0F35},
      {0x0F37, 0x0F37},   {0x0F39, 0x0F39},   {0x0F71, 0x0F7E},
      {0x0F80, 0x0F84},   {0x0F86, 0x0F87},   {0x0F90, 0x0F97},
      {0x0F99, 0x0FBC},   {0x0FC6, 0x0FC6},   {0x102D, 0x1030},
      {0x1032, 0x1032},   {0x1036, 0x1037},   {0x1039, 0x1039},
      {0x1058, 0x1059},   {0x1160, 0x11FF},   {0x135F, 0x135F},
      {0x1712, 0x1714},   {0x1732, 0x1734},   {0x1752, 0x1753},
      {0x1772, 0x1773},   {0x17B4, 0x17B5},   {0x17B7, 0x17BD},
      {0x17C6, 0x17C6},   {0x17C9, 0x17D3},   {0x17DD, 0x17DD},
      {0x180B, 0x180D},   {0x18A9, 0x18A9},   {0x1920, 0x1922},
      {0x1927, 0x1928},   {0x1932, 0x1932},   {0x1939, 0x193B},
      {0x1A17, 0x1A18},   {0x1B00, 0x1B03},   {0x1B34, 0x1B34},
      {0x1B36, 0x1B3A},   {0x1B3C, 0x1B3C},   {0x1B42, 0x1B42},
      {0x1B6B, 0x1B73},   {0x1DC0, 0x1DCA},   {0x1DFE, 0x1DFF},
      {0x200B, 0x200F},   {0x202A, 0x202E},   {0x2060, 0x2063},
      {0x206A, 0x206F},   {0x20D0, 0x20EF},   {0x302A, 0x302F},
      {0x3099, 0x309A},   {0xA806, 0xA806},   {0xA80B, 0xA80B},
      {0xA825, 0xA826},   {0xFB1E, 0xFB1E},   {0xFE00, 0xFE0F},
      {0xFE20, 0xFE23},   {0xFEFF, 0xFEFF},   {0xFFF9, 0xFFFB},
      {0x10A01, 0x10A03}, {0x10A05, 0x10A06}, {0x10A0C, 0x10A0F},
      {0x10A38, 0x10A3A}, {0x10A3F, 0x10A3F}, {0x1D167, 0x1D169},
      {0x1D173, 0x1D182}, {0x1D185, 0x1D18B}, {0x1D1AA, 0x1D1AD},
      {0x1D242, 0x1D244}, {0xE0001, 0xE0001}, {0xE0020, 0xE007F},
      {0xE0100, 0xE01EF}};

  /* test for 8-bit control characters */
  if (ucs == 0)
    return 0;
  if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))
    return 1;

  /* binary search in table of non-spacing characters */
  if (bisearch(ucs, combining, sizeof(combining) / sizeof(struct interval) - 1))
    return 0;

  /* if we arrive here, ucs is not a combining or C0/C1 control character */

  return 1 +
         (ucs >= 0x1100 &&
          (ucs <= 0x115f || /* Hangul Jamo init. consonants */
           ucs == 0x2329 || ucs == 0x232a ||
           (ucs >= 0x2e80 && ucs <= 0xa4cf && ucs != 0x303f) || /* CJK ... Yi */
           (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */
           (ucs >= 0xf900 &&
            ucs <= 0xfaff) || /* CJK Compatibility Ideographs */
           (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */
           (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */
           (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */
           (ucs >= 0xffe0 && ucs <= 0xffe6) ||
           (ucs >= 0x20000 && ucs <= 0x2fffd) ||
           (ucs >= 0x30000 && ucs <= 0x3fffd)));
}

// End of https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c inherited code

static int unicode_to_cp437(uint64_t code_point) {
  switch (code_point) {
  case 0x263a:
    return 1;
  case 0x263b:
    return 2;
  case 0x2665:
    return 3;
  case 0x2666:
    return 4;
  case 0x2663:
    return 5;
  case 0x2660:
    return 6;
  case 0x2022:
    return 7;
  case 0x25d8:
    return 8;
  case 0x25cb:
    return 9;
  case 0x25d9:
    return 10;
  case 0x2642:
    return 11;
  case 0x2640:
    return 12;
  case 0x266a:
    return 13;
  case 0x266b:
    return 14;
  case 0x263c:
    return 15;
  case 0x25ba:
    return 16;
  case 0x25c4:
    return 17;
  case 0x2195:
    return 18;
  case 0x203c:
    return 19;
  case 0x00b6:
    return 20;
  case 0x00a7:
    return 21;
  case 0x25ac:
    return 22;
  case 0x21a8:
    return 23;
  case 0x2191:
    return 24;
  case 0x2193:
    return 25;
  case 0x2192:
    return 26;
  case 0x2190:
    return 27;
  case 0x221f:
    return 28;
  case 0x2194:
    return 29;
  case 0x25b2:
    return 30;
  case 0x25bc:
    return 31;

  case 0x2302:
    return 127;
  case 0x00c7:
    return 128;
  case 0x00fc:
    return 129;
  case 0x00e9:
    return 130;
  case 0x00e2:
    return 131;
  case 0x00e4:
    return 132;
  case 0x00e0:
    return 133;
  case 0x00e5:
    return 134;
  case 0x00e7:
    return 135;
  case 0x00ea:
    return 136;
  case 0x00eb:
    return 137;
  case 0x00e8:
    return 138;
  case 0x00ef:
    return 139;
  case 0x00ee:
    return 140;
  case 0x00ec:
    return 141;
  case 0x00c4:
    return 142;
  case 0x00c5:
    return 143;
  case 0x00c9:
    return 144;
  case 0x00e6:
    return 145;
  case 0x00c6:
    return 146;
  case 0x00f4:
    return 147;
  case 0x00f6:
    return 148;
  case 0x00f2:
    return 149;
  case 0x00fb:
    return 150;
  case 0x00f9:
    return 151;
  case 0x00ff:
    return 152;
  case 0x00d6:
    return 153;
  case 0x00dc:
    return 154;
  case 0x00a2:
    return 155;
  case 0x00a3:
    return 156;
  case 0x00a5:
    return 157;
  case 0x20a7:
    return 158;
  case 0x0192:
    return 159;
  case 0x00e1:
    return 160;
  case 0x00ed:
    return 161;
  case 0x00f3:
    return 162;
  case 0x00fa:
    return 163;
  case 0x00f1:
    return 164;
  case 0x00d1:
    return 165;
  case 0x00aa:
    return 166;
  case 0x00ba:
    return 167;
  case 0x00bf:
    return 168;
  case 0x2310:
    return 169;
  case 0x00ac:
    return 170;
  case 0x00bd:
    return 171;
  case 0x00bc:
    return 172;
  case 0x00a1:
    return 173;
  case 0x00ab:
    return 174;
  case 0x00bb:
    return 175;
  case 0x2591:
    return 176;
  case 0x2592:
    return 177;
  case 0x2593:
    return 178;
  case 0x2502:
    return 179;
  case 0x2524:
    return 180;
  case 0x2561:
    return 181;
  case 0x2562:
    return 182;
  case 0x2556:
    return 183;
  case 0x2555:
    return 184;
  case 0x2563:
    return 185;
  case 0x2551:
    return 186;
  case 0x2557:
    return 187;
  case 0x255d:
    return 188;
  case 0x255c:
    return 189;
  case 0x255b:
    return 190;
  case 0x2510:
    return 191;
  case 0x2514:
    return 192;
  case 0x2534:
    return 193;
  case 0x252c:
    return 194;
  case 0x251c:
    return 195;
  case 0x2500:
    return 196;
  case 0x253c:
    return 197;
  case 0x255e:
    return 198;
  case 0x255f:
    return 199;
  case 0x255a:
    return 200;
  case 0x2554:
    return 201;
  case 0x2569:
    return 202;
  case 0x2566:
    return 203;
  case 0x2560:
    return 204;
  case 0x2550:
    return 205;
  case 0x256c:
    return 206;
  case 0x2567:
    return 207;
  case 0x2568:
    return 208;
  case 0x2564:
    return 209;
  case 0x2565:
    return 210;
  case 0x2559:
    return 211;
  case 0x2558:
    return 212;
  case 0x2552:
    return 213;
  case 0x2553:
    return 214;
  case 0x256b:
    return 215;
  case 0x256a:
    return 216;
  case 0x2518:
    return 217;
  case 0x250c:
    return 218;
  case 0x2588:
    return 219;
  case 0x2584:
    return 220;
  case 0x258c:
    return 221;
  case 0x2590:
    return 222;
  case 0x2580:
    return 223;
  case 0x03b1:
    return 224;
  case 0x00df:
    return 225;
  case 0x0393:
    return 226;
  case 0x03c0:
    return 227;
  case 0x03a3:
    return 228;
  case 0x03c3:
    return 229;
  case 0x00b5:
    return 230;
  case 0x03c4:
    return 231;
  case 0x03a6:
    return 232;
  case 0x0398:
    return 233;
  case 0x03a9:
    return 234;
  case 0x03b4:
    return 235;
  case 0x221e:
    return 236;
  case 0x03c6:
    return 237;
  case 0x03b5:
    return 238;
  case 0x2229:
    return 239;
  case 0x2261:
    return 240;
  case 0x00b1:
    return 241;
  case 0x2265:
    return 242;
  case 0x2264:
    return 243;
  case 0x2320:
    return 244;
  case 0x2321:
    return 245;
  case 0x00f7:
    return 246;
  case 0x2248:
    return 247;
  case 0x00b0:
    return 248;
  case 0x2219:
    return 249;
  case 0x00b7:
    return 250;
  case 0x221a:
    return 251;
  case 0x207f:
    return 252;
  case 0x00b2:
    return 253;
  case 0x25a0:
    return 254;
  }

  return -1;
}

static void flanterm_putchar(struct flanterm_context *ctx, uint8_t c) {
  if (ctx->discard_next || (c == 0x18 || c == 0x1a)) {
    ctx->discard_next      = false;
    ctx->escape            = false;
    ctx->control_sequence  = false;
    ctx->unicode_remaining = 0;
    ctx->osc               = false;
    ctx->osc_escape        = false;
    ctx->g_select          = 0;
    return;
  }

  if (ctx->unicode_remaining != 0) {
    if ((c & 0xc0) != 0x80) {
      ctx->unicode_remaining = 0;
      goto unicode_error;
    }

    ctx->unicode_remaining--;
    ctx->code_point |= (uint64_t)(c & 0x3f) << (6 * ctx->unicode_remaining);
    if (ctx->unicode_remaining != 0) {
      return;
    }

    int cc = unicode_to_cp437(ctx->code_point);

    if (cc == -1) {
      size_t replacement_width = (size_t)mk_wcwidth(ctx->code_point);
      if (replacement_width > 0) {
        ctx->raw_putchar(ctx, 0xfe);
      }
      for (size_t i = 1; i < replacement_width; i++) {
        ctx->raw_putchar(ctx, ' ');
      }
    } else {
      ctx->raw_putchar(ctx, cc);
    }
    return;
  }

unicode_error:
  if (c >= 0xc0 && c <= 0xf7) {
    if (c >= 0xc0 && c <= 0xdf) {
      ctx->unicode_remaining = 1;
      ctx->code_point        = (uint64_t)(c & 0x1f) << 6;
    } else if (c >= 0xe0 && c <= 0xef) {
      ctx->unicode_remaining = 2;
      ctx->code_point        = (uint64_t)(c & 0x0f) << (6 * 2);
    } else if (c >= 0xf0 && c <= 0xf7) {
      ctx->unicode_remaining = 3;
      ctx->code_point        = (uint64_t)(c & 0x07) << (6 * 3);
    }
    return;
  }

  if (ctx->escape == true) {
    escape_parse(ctx, c);
    return;
  }

  if (ctx->g_select) {
    ctx->g_select--;
    switch (c) {
    case 'B':
      ctx->charsets[ctx->g_select] = CHARSET_DEFAULT;
      break;
    case '0':
      ctx->charsets[ctx->g_select] = CHARSET_DEC_SPECIAL;
      break;
    }
    ctx->g_select = 0;
    return;
  }

  size_t x, y;
  ctx->get_cursor_pos(ctx, &x, &y);

  switch (c) {
  case 0x00:
  case 0x7f:
    return;
  case 0x1b:
    ctx->escape_offset = 0;
    ctx->escape        = true;
    return;
  case '\t':
    if ((x / ctx->tab_size + 1) >= ctx->cols) {
      ctx->set_cursor_pos(ctx, ctx->cols - 1, y);
      return;
    }
    ctx->set_cursor_pos(ctx, (x / ctx->tab_size + 1) * ctx->tab_size, y);
    return;
  case 0x0b:
  case 0x0c:
  case '\n':
    if (y == ctx->scroll_bottom_margin - 1) {
      ctx->scroll(ctx);
      ctx->set_cursor_pos(
          ctx, (ctx->oob_output & FLANTERM_OOB_OUTPUT_ONLCR) ? 0 : x, y);
    } else {
      ctx->set_cursor_pos(
          ctx, (ctx->oob_output & FLANTERM_OOB_OUTPUT_ONLCR) ? 0 : x, y + 1);
    }
    return;
  case '\b':
    ctx->set_cursor_pos(ctx, x - 1, y);
    return;
  case '\r':
    ctx->set_cursor_pos(ctx, 0, y);
    return;
  case '\a':
    // The bell is handled by the kernel
    if (ctx->callback != NULL) {
      ctx->callback(ctx, FLANTERM_CB_BELL, 0, 0, 0);
    }
    return;
  case 14:
    // Move to G1 set
    ctx->current_charset = 1;
    return;
  case 15:
    // Move to G0 set
    ctx->current_charset = 0;
    return;
  }

  if (ctx->insert_mode == true) {
    for (size_t i = ctx->cols - 1;; i--) {
      ctx->move_character(ctx, i + 1, y, i, y);
      if (i == x) {
        break;
      }
    }
  }

  // Translate character set
  switch (ctx->charsets[ctx->current_charset]) {
  case CHARSET_DEFAULT:
    break;
  case CHARSET_DEC_SPECIAL:
    if (dec_special_print(ctx, c)) {
      return;
    }
    break;
  }

  if (c >= 0x20 && c <= 0x7e) {
    ctx->raw_putchar(ctx, c);
  } else {
    ctx->raw_putchar(ctx, 0xfe);
  }
}
