/* -*- c-basic-offset:2; tab-width:2; indent-tabs-mode:nil -*- */

#include "vt_line.h"

#include <string.h> /* memset */
#include <pobl/bl_debug.h>
#include <pobl/bl_util.h> /* BL_MIN */
#include <pobl/bl_mem.h>  /* alloca */

#include "vt_ctl_loader.h"
#include "vt_ot_layout.h"

#if 0
#define __DEBUG
#endif

#ifdef __DEBUG
#define END_CHAR_INDEX(line)                                                              \
  ((line)->num_filled_chars == 0 &&                                                    \
           bl_debug_printf("END_CHAR_INDEX()" BL_DEBUG_TAG " num_filled_chars is 0.\n") \
       ? 0                                                                                \
       : (line)->num_filled_chars - 1)
#else
#define END_CHAR_INDEX(line) \
  ((line)->num_filled_chars == 0 ? 0 : (line)->num_filled_chars - 1)
#endif

#define IS_EMPTY(line) ((line)->num_filled_chars == 0)

#define vt_line_is_using_bidi(line) ((line)->ctl_info_type == VINFO_BIDI)
#define vt_line_is_using_iscii(line) ((line)->ctl_info_type == VINFO_ISCII)
#define vt_line_is_using_ot_layout(line) ((line)->ctl_info_type == VINFO_OT_LAYOUT)

/* You can specify this macro by configure script option. */
#if 0
#define OPTIMIZE_REDRAWING
#endif

/* --- static functions --- */

#ifndef NO_DYNAMIC_LOAD_CTL

static int vt_line_set_use_bidi(vt_line_t *line, int flag) {
  int (*func)(vt_line_t *, int);

  if (!(func = vt_load_ctl_bidi_func(VT_LINE_SET_USE_BIDI))) {
    return 0;
  }

  return (*func)(line, flag);
}

static int vt_line_bidi_convert_visual_char_index_to_logical(vt_line_t *line, int char_index) {
  int (*func)(vt_line_t *, int);

  if (!(func = vt_load_ctl_bidi_func(VT_LINE_BIDI_CONVERT_VISUAL_CHAR_INDEX_TO_LOGICAL))) {
    return char_index;
  }

  return (*func)(line, char_index);
}

static int vt_line_bidi_copy_logical_str(vt_line_t *line, vt_char_t *dst,
                                         int beg, /* visual position */
                                         u_int len) {
  int (*func)(vt_line_t *, vt_char_t *, int, u_int);

  if (!(func = vt_load_ctl_bidi_func(VT_LINE_BIDI_COPY_LOGICAL_STR))) {
    return 0;
  }

  return (*func)(line, dst, beg, len);
}

static int vt_line_bidi_is_rtl(vt_line_t *line) {
  int (*func)(vt_line_t *);

  if (!(func = vt_load_ctl_bidi_func(VT_LINE_BIDI_IS_RTL))) {
    return 0;
  }

  return (*func)(line);
}

static int vt_bidi_copy(vt_bidi_state_t dst, vt_bidi_state_t src, int optimize_ctl_info) {
  int (*func)(vt_bidi_state_t, vt_bidi_state_t, int);

  if (!(func = vt_load_ctl_bidi_func(VT_BIDI_COPY))) {
    return 0;
  }

  return (*func)(dst, src, optimize_ctl_info);
}

static int vt_bidi_reset(vt_bidi_state_t state) {
  int (*func)(vt_bidi_state_t);

  if (!(func = vt_load_ctl_bidi_func(VT_BIDI_RESET))) {
    return 0;
  }

  return (*func)(state);
}

static int vt_line_bidi_render(vt_line_t *line, vt_bidi_mode_t bidi_mode, const char *separators) {
  int (*func)(vt_line_t *, vt_bidi_mode_t, const char *);

  if (!(func = vt_load_ctl_bidi_func(VT_LINE_BIDI_RENDER))) {
    return 0;
  }

  return (*func)(line, bidi_mode, separators);
}

static int vt_line_bidi_visual(vt_line_t *line) {
  int (*func)(vt_line_t *);

  if (!(func = vt_load_ctl_bidi_func(VT_LINE_BIDI_VISUAL))) {
    return 0;
  }

  return (*func)(line);
}

static int vt_line_bidi_logical(vt_line_t *line) {
  int (*func)(vt_line_t *);

  if (!(func = vt_load_ctl_bidi_func(VT_LINE_BIDI_LOGICAL))) {
    return 0;
  }

  return (*func)(line);
}

static int vt_line_set_use_iscii(vt_line_t *line, int flag) {
  int (*func)(vt_line_t *, int);

  if (!(func = vt_load_ctl_iscii_func(VT_LINE_SET_USE_ISCII))) {
    return 0;
  }

  return (*func)(line, flag);
}

static int vt_line_iscii_convert_visual_char_index_to_logical(vt_line_t *line, int char_index) {
  int (*func)(vt_line_t *, int);

  if (!(func = vt_load_ctl_iscii_func(VT_LINE_ISCII_CONVERT_VISUAL_CHAR_INDEX_TO_LOGICAL))) {
    return char_index;
  }

  return (*func)(line, char_index);
}

static int vt_iscii_copy(vt_iscii_state_t dst, vt_iscii_state_t src, int optimize_ctl_info) {
  int (*func)(vt_iscii_state_t, vt_iscii_state_t, int);

  if (!(func = vt_load_ctl_iscii_func(VT_ISCII_COPY))) {
    return 0;
  }

  return (*func)(dst, src, optimize_ctl_info);
}

static int vt_iscii_reset(vt_iscii_state_t state) {
  int (*func)(vt_iscii_state_t);

  if (!(func = vt_load_ctl_iscii_func(VT_ISCII_RESET))) {
    return 0;
  }

  return (*func)(state);
}

static int vt_line_iscii_render(vt_line_t *line) {
  int (*func)(vt_line_t *);

  if (!(func = vt_load_ctl_iscii_func(VT_LINE_ISCII_RENDER))) {
    return 0;
  }

  return (*func)(line);
}

static int vt_line_iscii_visual(vt_line_t *line) {
  int (*func)(vt_line_t *);

  if (!(func = vt_load_ctl_iscii_func(VT_LINE_ISCII_VISUAL))) {
    return 0;
  }

  return (*func)(line);
}

static int vt_line_iscii_logical(vt_line_t *line) {
  int (*func)(vt_line_t *);

  if (!(func = vt_load_ctl_iscii_func(VT_LINE_ISCII_LOGICAL))) {
    return 0;
  }

  return (*func)(line);
}

#else /* NO_DYNAMIC_LOAD_CTL */

#ifndef USE_FRIBIDI
#define vt_line_set_use_bidi(line, flag) (0)
#define vt_line_bidi_convert_visual_char_index_to_logical(line, char_index) (char_index)
#define vt_line_bidi_copy_logical_str(line, dst, beg, len) (0)
#define vt_line_bidi_is_rtl(line) (0)
#define vt_bidi_copy(dst, src, optimize) (0)
#define vt_bidi_reset(state) (0)
#define vt_line_bidi_render(line, bidi_mode, separators) (0)
#define vt_line_bidi_visual(line) (0)
#define vt_line_bidi_logical(line) (0)
#else
/* Link functions in libctl/vt_*bidi.c */
int vt_line_set_use_bidi(vt_line_t *line, int flag);
int vt_line_bidi_convert_visual_char_index_to_logical(vt_line_t *line, int char_index);
int vt_line_bidi_copy_logical_str(vt_line_t *line, vt_char_t *dst, int beg, u_int len);
int vt_line_bidi_is_rtl(vt_line_t *line);
int vt_bidi_copy(vt_bidi_state_t dst, vt_bidi_state_t src, int optimize);
int vt_bidi_reset(vt_bidi_state_t state);
int vt_line_bidi_convert_logical_char_index_to_visual(vt_line_t *line, int char_index,
                                                      u_int32_t *meet_pos_info);
int vt_line_bidi_render(vt_line_t *line, vt_bidi_mode_t bidi_mode, const char *separators);
int vt_line_bidi_visual(vt_line_t *line);
int vt_line_bidi_logical(vt_line_t *line);
#endif /* USE_FRIBIDI */

#ifndef USE_IND
#define vt_line_set_use_iscii(line, flag) (0)
#define vt_line_iscii_convert_visual_char_index_to_logical(line, char_index) (char_index)
#define vt_iscii_copy(dst, src, optimize) (0)
#define vt_iscii_reset(state) (0)
#define vt_line_iscii_render(line) (0)
#define vt_line_iscii_visual(line) (0)
#define vt_line_iscii_logical(line) (0)
#else
/* Link functions in libctl/vt_*iscii.c */
int vt_line_set_use_iscii(vt_line_t *line, int flag);
int vt_line_iscii_convert_visual_char_index_to_logical(vt_line_t *line, int visual_char_index);
int vt_iscii_copy(vt_iscii_state_t dst, vt_iscii_state_t src, int optimize);
int vt_iscii_reset(vt_iscii_state_t state);
int vt_line_iscii_convert_logical_char_index_to_visual(vt_line_t *line, int logical_char_index);
int vt_line_iscii_render(vt_line_t *line);
int vt_line_iscii_visual(vt_line_t *line);
int vt_line_iscii_logical(vt_line_t *line);
#endif /* USE_IND */

#endif /* NO_DYNAMIC_LOAD_CTL */

#ifdef USE_OT_LAYOUT

static int vt_line_set_use_ot_layout(vt_line_t *line, int flag) {
  if (flag) {
    if (vt_line_is_using_ot_layout(line)) {
      return 1;
    } else if (line->ctl_info_type != 0) {
      return 0;
    }

    if ((line->ctl_info.ot_layout = vt_ot_layout_new()) == NULL) {
      return 0;
    }

    line->ctl_info_type = VINFO_OT_LAYOUT;
  } else {
    if (vt_line_is_using_ot_layout(line)) {
      vt_ot_layout_destroy(line->ctl_info.ot_layout);
      line->ctl_info_type = 0;
    }
  }

  return 1;
}

/* The caller should check vt_line_is_using_ot_layout() in advance. */
static int vt_line_ot_layout_visual(vt_line_t *line) {
  vt_char_t *src;
  u_int src_len;
  vt_char_t *dst;
  u_int dst_len;
  int dst_pos;
  int src_pos;

  if (line->ctl_info.ot_layout->size == 0 || !line->ctl_info.ot_layout->substituted) {
#ifdef __DEBUG
    bl_warn_printf(BL_DEBUG_TAG " Not need to visualize.\n");
#endif

    return 1;
  }

  src_len = line->num_filled_chars;
  if ((src = vt_str_alloca(src_len)) == NULL) {
    return 0;
  }
  vt_str_init(src, src_len);

  vt_str_copy(src, line->chars, src_len);

  dst_len = line->ctl_info.ot_layout->size;
  if (line->num_chars < dst_len) {
    vt_char_t *chars;

    if ((chars = vt_str_new(dst_len))) {
      /* XXX => shrunk at vt_screen.c and vt_logical_visual_ctl.c */
      vt_str_destroy(line->chars, line->num_chars);
      line->chars = chars;
      line->num_chars = dst_len;
    } else {
      line->ctl_info.ot_layout->size = dst_len = line->num_chars;
    }
  }

  dst = line->chars;

  src_pos = 0;
  for (dst_pos = 0; dst_pos < dst_len; dst_pos++) {
    if (line->ctl_info.ot_layout->num_chars_array[dst_pos] == 0) {
      /*
       * (logical)ACD -> (visual)A0CD -> (shaped)abcd
       *          B              B
       * (B is combining char)
       */
      vt_char_copy(dst + dst_pos, vt_get_base_char(src + src_pos - 1));
      vt_char_set_code(dst + dst_pos, 0);
    } else {
      u_int count;

      vt_char_copy(dst + dst_pos, src + (src_pos++));

      for (count = 1; count < line->ctl_info.ot_layout->num_chars_array[dst_pos]; count++) {
        vt_char_t *comb;
        u_int num;

#ifdef DEBUG
        if (vt_char_is_comb(vt_get_base_char(src + src_pos))) {
          bl_debug_printf(BL_DEBUG_TAG " illegal ot_layout\n");
        }
#endif

        vt_char_combine_simple(dst + dst_pos, vt_get_base_char(src + src_pos));

        comb = vt_get_combining_chars(src + (src_pos++), &num);
        for (; num > 0; num--) {
#ifdef DEBUG
          if (!vt_char_is_comb(comb)) {
            bl_debug_printf(BL_DEBUG_TAG " illegal ot_layout\n");
          }
#endif
          vt_char_combine_simple(dst + dst_pos, comb++);
        }
      }
    }
  }

#ifdef DEBUG
  if (src_pos != src_len) {
    bl_debug_printf(BL_DEBUG_TAG "vt_line_ot_layout_visual() failed: %d -> %d\n", src_len, src_pos);
  }
#endif

  vt_str_final(src, src_len);

  line->num_filled_chars = dst_pos;

  return 1;
}

/* The caller should check vt_line_is_using_ot_layout() in advance. */
static int vt_line_ot_layout_logical(vt_line_t *line) {
  vt_char_t *src;
  u_int src_len;
  vt_char_t *dst;
  int src_pos;

  if (line->ctl_info.ot_layout->size == 0 || !line->ctl_info.ot_layout->substituted) {
#ifdef __DEBUG
    bl_warn_printf(BL_DEBUG_TAG " Not need to logicalize.\n");
#endif

    return 1;
  }

  src_len = line->num_filled_chars;
  if ((src = vt_str_alloca(src_len)) == NULL) {
    return 0;
  }
  vt_str_init(src, src_len);

  vt_str_copy(src, line->chars, src_len);
  dst = line->chars;

  for (src_pos = 0; src_pos < line->ctl_info.ot_layout->size; src_pos++) {
    vt_char_t *comb;
    u_int num;

    if (line->ctl_info.ot_layout->num_chars_array[src_pos] == 0) {
      continue;
    }

    if (vt_char_cs(src + src_pos) == ISO10646_UCS4_1_V) {
      vt_char_set_cs(src + src_pos, ISO10646_UCS4_1);
    }

    if (line->ctl_info.ot_layout->num_chars_array[src_pos] == 1) {
      vt_char_copy(dst, src + src_pos);
    } else {
      vt_char_copy(dst, vt_get_base_char(src + src_pos));

      comb = vt_get_combining_chars(src + src_pos, &num);
      for (; num > 0; num--, comb++) {
        if (vt_char_cs(comb) == ISO10646_UCS4_1_V) {
          vt_char_set_cs(comb, ISO10646_UCS4_1);
        }

        if (vt_char_is_comb(comb)) {
          vt_char_combine_simple(dst, comb);
        } else {
          vt_char_copy(++dst, comb);
        }
      }
    }

    dst++;
  }

  vt_str_final(src, src_len);

  line->num_filled_chars = dst - line->chars;

  return 1;
}

/* The caller should check vt_line_is_using_ot_layout() in advance. */
static int vt_line_ot_layout_convert_logical_char_index_to_visual(vt_line_t *line,
                                                                  int logical_char_index) {
  int visual_char_index;

  if (vt_line_is_empty(line)) {
    return 0;
  }

  if (line->ctl_info.ot_layout->size == 0 || !line->ctl_info.ot_layout->substituted) {
#ifdef __DEBUG
    bl_debug_printf(BL_DEBUG_TAG " logical char_index is same as visual one.\n");
#endif
    return logical_char_index;
  }

  for (visual_char_index = 0; visual_char_index < line->ctl_info.ot_layout->size;
       visual_char_index++) {
    if ((logical_char_index -= line->ctl_info.ot_layout->num_chars_array[visual_char_index]) < 0) {
      break;
    }
  }

  return visual_char_index;
}

/* The caller should check vt_line_is_using_ot_layout() in advance. */
static int vt_line_ot_layout_convert_visual_char_index_to_logical(vt_line_t *line,
                                                                  int visual_char_index) {
  int logical_char_index;
  int count;

  if (vt_line_is_empty(line)) {
    return 0;
  }

  if (line->ctl_info.ot_layout->size == 0 || !line->ctl_info.ot_layout->substituted) {
#ifdef __DEBUG
    bl_debug_printf(BL_DEBUG_TAG " visual char_index is same as logical one.\n");
#endif
    return visual_char_index;
  }

  if (line->ctl_info.ot_layout->size - 1 < visual_char_index) {
    visual_char_index = line->ctl_info.ot_layout->size - 1;
  }

  logical_char_index = 0;
  for (count = 0; count < visual_char_index; count++) {
    logical_char_index += line->ctl_info.ot_layout->num_chars_array[count];
  }

  return logical_char_index;
}

/* The caller should check vt_line_is_using_ot_layout() in advance. */
static int vt_line_ot_layout_render(vt_line_t *line, /* is always modified */
                                    void *term) {
  int ret;
  int visual_mod_beg;
  int visual_mod_end;

  /*
   * Lower case: ASCII
   * Upper case: GLYPH INDEX
   *    (Logical) AAA == (Visual) BBBBB
   * => (Logical) aaa == (Visual) aaa
   * In this case vt_line_is_cleared_to_end() returns 0, so "BB" remains on
   * the screen unless following vt_line_set_modified().
   */
  visual_mod_beg = vt_line_get_beg_of_modified(line);
  if (line->ctl_info.ot_layout->substituted) {
    visual_mod_beg = vt_line_ot_layout_convert_logical_char_index_to_visual(line, visual_mod_beg);
  }

  if (vt_line_is_real_modified(line)) {
    int char_index;
    int complex_shape = line->ctl_info.ot_layout->complex_shape;
    int has_var_width_char = line->ctl_info.ot_layout->has_var_width_char;
    int count;

    line->ctl_info.ot_layout->term = term;

    if ((ret = vt_ot_layout(line->ctl_info.ot_layout, line->chars, line->num_filled_chars)) <= 0) {
      if (complex_shape || has_var_width_char) {
        vt_line_set_modified_all(line);
      }

      return ret;
    }

    complex_shape |= line->ctl_info.ot_layout->complex_shape;
    has_var_width_char |= line->ctl_info.ot_layout->has_var_width_char;

    if (line->ctl_info.ot_layout->substituted) {
      int beg;

      if ((beg = vt_line_ot_layout_convert_logical_char_index_to_visual(
                   line, vt_line_get_beg_of_modified(line))) < visual_mod_beg) {
        visual_mod_beg = beg;
      }
    }

    if (has_var_width_char) {
      visual_mod_end = line->num_chars;
    } else {
      visual_mod_end = vt_line_ot_layout_convert_logical_char_index_to_visual(line,
                         vt_line_get_end_of_modified(line));
    }

    if (complex_shape) {
      /*
       * XXX
       *
       * Input  Logical    Visual
       * !    => !      => !
       *         ^         ^
       * =    => !=     => =/=
       *          ^        ^^^
       * ('=' after '!' should be redrawn in logical, but the first '!' should
       *  be also redrawn in visual.)
       */
      for (count = 0; visual_mod_beg > 0 && count < 4; count++, visual_mod_beg--) {
        if (vt_char_code(line->chars + visual_mod_beg - 1) == 0x20 /* SP */) {
          break;
        }
      }

      /* If trailing characters in this line are all spaces, visual_mod_end is not modified. */
      for (char_index = visual_mod_end + 1; char_index < line->num_filled_chars; char_index++) {
        if (vt_char_code(line->chars + char_index) != 0x20 /* SP */) {
          visual_mod_end = line->num_chars;
          break;
        }
      }
    }
  } else {
    visual_mod_end = vt_line_ot_layout_convert_logical_char_index_to_visual(line,
                       vt_line_get_end_of_modified(line));
  }

  vt_line_set_modified(line, visual_mod_beg, visual_mod_end);

  return 1;
}

#endif /* USE_OT_LAYOUT */

static void set_real_modified(vt_line_t *line, int beg_char_index, int end_char_index) {
  vt_line_set_modified(line, beg_char_index, end_char_index);
  line->is_modified = 2;
}

static void copy_line(vt_line_t *dst, vt_line_t *src, int optimize_ctl_info) {
  u_int copy_len;

#ifdef DEBUG
  if (dst == src) {
    bl_error_printf("copy_line() forbids src == dst\n");
    abort();
  }
#endif

  copy_len = BL_MIN(src->num_filled_chars, dst->num_chars);

  vt_str_copy(dst->chars, src->chars, copy_len);
  dst->num_filled_chars = copy_len;

  dst->change_beg_col = BL_MIN(src->change_beg_col, dst->num_chars);
  dst->change_end_col = BL_MIN(src->change_end_col, dst->num_chars);

  dst->is_modified = src->is_modified;
  dst->is_continued_to_next = src->is_continued_to_next;
  dst->size_attr = src->size_attr;
  dst->mark = src->mark;

#if !defined(NO_DYNAMIC_LOAD_CTL) || defined(USE_FRIBIDI)
  if (vt_line_is_using_bidi(src)) {
    if (vt_line_is_using_bidi(dst) || vt_line_set_use_bidi(dst, 1)) {
      /*
       * Don't use vt_line_bidi_render() here,
       * or it is impossible to call this function in visual context.
       */
      if (dst->num_chars < src->num_filled_chars) {
#ifdef DEBUG
        bl_debug_printf(BL_DEBUG_TAG " Not copy vt_bidi.\n");
#endif
        /*
         * XXX
         * Isn't set_real_modified(dst, 0, END_CHAR_INDEX(dst)) necessary here
         * to force ctl_render() ?
         */
      } else if (vt_bidi_copy(dst->ctl_info.bidi, src->ctl_info.bidi, optimize_ctl_info) == -1) {
        dst->ctl_info_type = 0;
        dst->ctl_info.bidi = NULL;
      }
    }
  } else if (vt_line_is_using_bidi(dst)) {
    vt_line_set_use_bidi(dst, 0);
  }
#endif

#if !defined(NO_DYNAMIC_LOAD_CTL) || defined(USE_IND)
  if (vt_line_is_using_iscii(src)) {
    if (vt_line_is_using_iscii(dst) || vt_line_set_use_iscii(dst, 1)) {
      /*
       * Don't use vt_line_iscii_render() here,
       * or it is impossible to call this function in visual context.
       */
      if (dst->num_chars < src->num_filled_chars) {
#ifdef DEBUG
        bl_debug_printf(BL_DEBUG_TAG " Not copy vt_iscii.\n");
#endif
        /*
         * XXX
         * Isn't set_real_modified(dst, 0, END_CHAR_INDEX(dst)) necessary here
         * to force ctl_render() ?
         */
      } else if (vt_iscii_copy(dst->ctl_info.iscii, src->ctl_info.iscii, optimize_ctl_info) == -1) {
        dst->ctl_info_type = 0;
        dst->ctl_info.iscii = NULL;
      }
    }
  } else if (vt_line_is_using_iscii(dst)) {
    vt_line_set_use_iscii(dst, 0);
  }
#endif

#ifdef USE_OT_LAYOUT
  if (vt_line_is_using_ot_layout(src)) {
    if (vt_line_is_using_ot_layout(dst) || vt_line_set_use_ot_layout(dst, 1)) {
      /*
       * Don't use vt_line_ot_layout_render() here,
       * or it is impossible to call this function in visual context.
       */
      if (dst->num_chars < src->num_filled_chars) {
#ifdef DEBUG
        bl_debug_printf(BL_DEBUG_TAG " Not copy vt_ot_layout.\n");
#endif
        /*
         * XXX
         * Isn't set_real_modified(dst, 0, END_CHAR_INDEX(dst)) necessary here
         * to force ctl_render() ?
         */
      } else if (vt_ot_layout_copy(dst->ctl_info.ot_layout, src->ctl_info.ot_layout,
                                   optimize_ctl_info) == -1) {
        dst->ctl_info_type = 0;
        dst->ctl_info.ot_layout = NULL;
      }
    }
  } else if (vt_line_is_using_ot_layout(dst)) {
    vt_line_set_use_ot_layout(dst, 0);
  }
#endif
}

/* --- global functions --- */

/*
 * Functions which doesn't have to care about visual order.
 */

int vt_line_init(vt_line_t *line, u_int num_chars) {
  memset(line, 0, sizeof(vt_line_t));

  if ((line->chars = vt_str_new(num_chars)) == NULL) {
    return 0;
  }

  line->num_chars = num_chars;

  return 1;
}

/* For vt_log_add() */
int vt_line_clone(vt_line_t *clone, vt_line_t *orig, u_int num_chars) {
  if (vt_line_init(clone, num_chars)) {
    copy_line(clone, orig, 1 /* clone->ctl_info can be uncopied. */);

    return 1;
  } else {
    return 0;
  }
}

void vt_line_final(vt_line_t *line) {
  if (vt_line_is_using_bidi(line)) {
    vt_line_set_use_bidi(line, 0);
  } else if (vt_line_is_using_iscii(line)) {
    vt_line_set_use_iscii(line, 0);
  }
#ifdef USE_OT_LAYOUT
  else if (vt_line_is_using_ot_layout(line)) {
    vt_line_set_use_ot_layout(line, 0);
  }
#endif

  if (line->chars) {
    vt_str_destroy(line->chars, line->num_chars);
  }
}

/*
 * return: actually broken chars.
 */
u_int vt_line_break_boundary(vt_line_t *line, u_int size) {
  int count;

  if (line->num_filled_chars + size > line->num_chars) {
/* over line length */

#ifdef DEBUG
    bl_warn_printf(BL_DEBUG_TAG " breaking from col %d by size %d failed.",
                   line->num_filled_chars, size);
#endif

    size = line->num_chars - line->num_filled_chars;

#ifdef DEBUG
    bl_msg_printf(" ... size modified -> %d\n", size);
#endif
  }

  if (size == 0) {
    /* nothing is done */

    return 0;
  }

  /* padding spaces */
  for (count = line->num_filled_chars; count < line->num_filled_chars + size; count++) {
    vt_char_copy(line->chars + count, vt_sp_ch());
  }

#if 0
  set_real_modified(line, END_CHAR_INDEX(line) + 1, END_CHAR_INDEX(line) + size);
#else
  if (vt_line_is_using_ctl(line) && !vt_line_is_real_modified(line)) {
    /* ctl_render_line() should be called in ctl_render() in
     * vt_logical_visual.c. */
    set_real_modified(line, END_CHAR_INDEX(line) + size, END_CHAR_INDEX(line) + size);
  }
#endif

  line->num_filled_chars += size;

  return size;
}

int vt_line_assure_boundary(vt_line_t *line, int char_index) {
  if (char_index >= line->num_filled_chars) {
    u_int brk_size;

    brk_size = char_index - line->num_filled_chars + 1;

    if (vt_line_break_boundary(line, brk_size) < brk_size) {
      return 0;
    }
  }

  return 1;
}

void vt_line_reset(vt_line_t *line) {
  if (IS_EMPTY(line)) {
    /* already reset */
    return;
  }

#ifdef OPTIMIZE_REDRAWING
  {
    int count;

    count = END_CHAR_INDEX(line);
    while (1) {
      if (!vt_char_equal(line->chars + count, vt_sp_ch())) {
        set_real_modified(line, 0, count);

        break;
      } else if (--count < 0) {
        break;
      }
    }
  }
#else
  set_real_modified(line, 0, END_CHAR_INDEX(line));
#endif

  line->num_filled_chars = 0;

  if (vt_line_is_using_bidi(line)) {
    vt_bidi_reset(line->ctl_info.bidi);
  } else if (vt_line_is_using_iscii(line)) {
    vt_iscii_reset(line->ctl_info.iscii);
  }
#ifdef USE_OT_LAYOUT
  else if (vt_line_is_using_ot_layout(line)) {
    vt_ot_layout_reset(line->ctl_info.ot_layout);
  }
#endif

  line->is_continued_to_next = 0;
  line->size_attr = 0;
}

void vt_line_clear(vt_line_t *line, int char_index) {
  if (char_index >= line->num_filled_chars) {
    return;
  }

#ifdef OPTIMIZE_REDRAWING
  {
    int count;

    count = END_CHAR_INDEX(line);
    while (1) {
      if (!vt_char_equal(line->chars + count, vt_sp_ch())) {
        set_real_modified(line, char_index, count);

        break;
      } else if (--count < char_index) {
        break;
      }
    }
  }
#else
  set_real_modified(line, char_index, END_CHAR_INDEX(line));
#endif

  vt_char_copy(line->chars + char_index, vt_sp_ch());
  line->num_filled_chars = char_index + 1;
  line->is_continued_to_next = 0;
  line->size_attr = 0;
}

int vt_line_clear_with(vt_line_t *line, int char_index, vt_char_t *ch) {
  line->is_continued_to_next = 0;

  return vt_line_fill(
      line, ch, char_index,
      (line->num_chars - vt_str_cols(line->chars, char_index)) / vt_char_cols(ch));
}

int vt_line_overwrite(vt_line_t *line, int beg_char_index, /* >= line->num_filled_chars is OK */
                      vt_char_t *chars, u_int len, u_int cols) {
  int count;
  u_int cols_to_beg;
  u_int cols_rest;
  u_int padding;
  u_int new_len;
  u_int copy_len;
  vt_char_t *copy_src;

  if (len == 0) {
    return 1;
  }

  if (beg_char_index + len > line->num_chars) {
    if (beg_char_index >= line->num_chars) {
#ifdef DEBUG
      bl_warn_printf(BL_DEBUG_TAG " beg[%d] is over num_chars[%d].\n", beg_char_index,
                     line->num_chars);
#endif

      return 0;
    }

#ifdef DEBUG
    bl_warn_printf(BL_DEBUG_TAG " beg_char_index[%d] + len[%d] is over num_chars[%d].\n",
                   beg_char_index, len, line->num_chars);
#endif

    len = line->num_chars - beg_char_index;
  }

  if (beg_char_index > 0) {
    vt_line_assure_boundary(line, beg_char_index - 1);
  }

#ifdef OPTIMIZE_REDRAWING
  if (len <= line->num_filled_chars - beg_char_index) {
    if (vt_str_equal(line->chars + beg_char_index, chars, len)) {
      return 1;
    }
  } else {
    if (vt_str_equal(line->chars + beg_char_index, chars,
                     line->num_filled_chars - beg_char_index)) {
      chars += (line->num_filled_chars - beg_char_index);
      len -= (line->num_filled_chars - beg_char_index);
      beg_char_index = line->num_filled_chars;

      count = 0;
      while (1) {
        if (!vt_char_equal(chars + count, vt_sp_ch())) {
          break;
        } else if (++count >= len) {
          vt_str_copy(line->chars + beg_char_index, chars, len);
          line->num_filled_chars = beg_char_index + len;

          /* Not necessary vt_line_set_modified() */

          return 1;
        }
      }
    }
  }
#endif

  cols_to_beg = vt_str_cols(line->chars, beg_char_index);

  if (cols_to_beg + cols < line->num_chars) {
    int char_index;

    char_index = vt_convert_col_to_char_index(line, &cols_rest, cols_to_beg + cols, 0);

    if (0 < cols_rest && cols_rest < vt_char_cols(line->chars + char_index)) {
      padding = vt_char_cols(line->chars + char_index) - cols_rest;
      char_index++;
    } else {
      padding = 0;
    }

    if (line->num_filled_chars > char_index) {
      copy_len = line->num_filled_chars - char_index;
    } else {
      copy_len = 0;
    }

    copy_src = line->chars + char_index;
  } else {
    padding = 0;
    copy_len = 0;
    copy_src = NULL;
  }

  new_len = beg_char_index + len + padding + copy_len;

  if (new_len > line->num_chars) {
#ifdef DEBUG
    bl_warn_printf(BL_DEBUG_TAG " new line len %d(beg %d ow %d padding %d copy %d) is overflowed\n",
                   new_len, beg_char_index, len, padding, copy_len);
#endif

    new_len = line->num_chars;

    if (new_len > padding + beg_char_index + len) {
      copy_len = new_len - padding - beg_char_index - len;
    } else {
      copy_len = 0;
      padding = new_len - beg_char_index - len;
    }

#ifdef DEBUG
    bl_msg_printf(" ... modified -> new_len %d , copy_len %d\n", new_len, copy_len);
#endif
  }

  if (copy_len > 0) {
    /* making space */
    vt_str_copy(line->chars + beg_char_index + len + padding, copy_src, copy_len);
  }

  for (count = 0; count < padding; count++) {
    vt_char_copy(line->chars + beg_char_index + len + count, vt_sp_ch());
  }

  vt_str_copy(line->chars + beg_char_index, chars, len);

  line->num_filled_chars = new_len;

  set_real_modified(line, beg_char_index, beg_char_index + len + padding - 1);

  return 1;
}

/*
 * Not used for now.
 */
#if 0
int vt_line_overwrite_all(vt_line_t *line, vt_char_t *chars, int len) {
  set_real_modified(line, 0, END_CHAR_INDEX(line));

  vt_str_copy(line->chars, chars, len);
  line->num_filled_chars = len;

  set_real_modified(line, 0, END_CHAR_INDEX(line));

  return 1;
}
#endif

int vt_line_fill(vt_line_t *line, vt_char_t *ch, int beg, /* >= line->num_filled_chars is OK */
                 u_int num) {
  int count;
  int char_index;
  u_int left_cols;
  u_int copy_len;

  if (num == 0) {
    return 1;
  }

  if (beg >= line->num_chars) {
#ifdef DEBUG
    bl_warn_printf(BL_DEBUG_TAG " beg[%d] is over num_chars[%d].\n", beg, line->num_chars);
#endif

    return 0;
  }

  if (beg > 0) {
    vt_line_assure_boundary(line, beg - 1);
  }

#ifdef OPTIMIZE_REDRAWING
  count = 0;
  while (1) {
    if (!vt_char_equal(line->chars + beg + count, ch)) {
      beg += count;
      num -= count;

      if (beg + num <= line->num_filled_chars) {
        count = 0;
        while (1) {
          if (!vt_char_equal(line->chars + beg + num - 1 - count, ch)) {
            num -= count;

            break;
          } else if (count++ == num) {
            /* Never happens */

            return 1;
          }
        }
      }

      break;
    } else if (++count >= num) {
      return 1;
    } else if (beg + count == line->num_filled_chars) {
      beg += count;
      num -= count;

      break;
    }
  }
#endif

  num = BL_MIN(num, line->num_chars - beg);

  char_index = beg;
  left_cols = num * vt_char_cols(ch);

  while (1) {
    if (char_index >= line->num_filled_chars) {
      left_cols = 0;
      copy_len = 0;

      break;
    } else if (left_cols < vt_char_cols(line->chars + char_index)) {
      if (beg + num + left_cols > line->num_chars) {
        left_cols = line->num_chars - beg - num;
        copy_len = 0;
      } else {
        copy_len = line->num_filled_chars - char_index - left_cols;

        if (beg + num + left_cols + copy_len > line->num_chars) {
          /*
           * line->num_chars is equal to or larger than
           * beg + num + left_cols since
           * 'if( beg + num + left_cols > line->num_chars)'
           * is already passed here.
           */
          copy_len = line->num_chars - beg - num - left_cols;
        }
      }

      char_index += (left_cols / vt_char_cols(ch));

      break;
    } else {
      left_cols -= vt_char_cols(line->chars + char_index);
      char_index++;
    }
  }

  if (copy_len > 0) {
    /* making space */
    vt_str_copy(line->chars + beg + num + left_cols, line->chars + char_index, copy_len);
  }

  char_index = beg;

  for (count = 0; count < num; count++) {
    vt_char_copy(&line->chars[char_index++], ch);
  }

  /* padding */
  for (count = 0; count < left_cols; count++) {
    vt_char_copy(&line->chars[char_index++], vt_sp_ch());
  }

  line->num_filled_chars = char_index + copy_len;

  set_real_modified(line, beg, beg + num + left_cols);

  return 1;
}

vt_char_t *vt_char_at(vt_line_t *line, int at) {
  if (at >= line->num_filled_chars) {
    return NULL;
  } else {
    return line->chars + at;
  }
}

int vt_line_set_modified(vt_line_t *line, int beg_char_index, int end_char_index) {
  int count;
  int beg_col;
  int end_col;

  if (beg_char_index > end_char_index) {
#ifdef DEBUG
    bl_warn_printf(BL_DEBUG_TAG " beg_char_index %d > end_char_index %d\n", beg_char_index,
                   end_char_index);
#endif

    return 0;
  }

  if (beg_char_index >= line->num_filled_chars) {
    beg_char_index = END_CHAR_INDEX(line);
  }

  beg_col = 0;
  for (count = 0; count < beg_char_index; count++) {
    beg_col += vt_char_cols(line->chars + count);
  }

  if (end_char_index >= line->num_filled_chars) {
    /*
     * '* 2' assures change_end_col should point over the end of line.
     * If triple width(or wider) characters(!) were to exist, this hack would
     * make
     * no sense...
     */
    end_col = line->num_chars * 2;
  } else {
    end_col = beg_col;
    for (; count <= end_char_index; count++) {
      /*
       * This will be executed at least once, because beg_char_index is never
       * greater than end_char_index.
       */
      end_col += vt_char_cols(line->chars + count);
    }

    /*
     * If vt_char_cols() returns 0, beg_col can be equals to end_col here.
     * If beg_col is equals to end_col, don't minus end_col.
     */
    if (beg_col < end_col) {
      end_col--;
    }
  }

  if (line->is_modified) {
    if (beg_col < line->change_beg_col) {
      line->change_beg_col = beg_col;
    }

    if (end_col > line->change_end_col) {
      line->change_end_col = end_col;
    }
  } else {
    line->change_beg_col = beg_col;
    line->change_end_col = end_col;

    line->is_modified = 1;
  }

  return 1;
}

int vt_line_set_modified_all(vt_line_t *line) {
  line->change_beg_col = 0;

  /*
   * '* 2' assures change_end_col should point over the end of line.
   * If triple width(or wider) characters(!) were to exist, this hack would make
   * no sense...
   */
  line->change_end_col = line->num_chars * 2;

  /* Don't overwrite if line->is_modified == 2 (real modified) */
  if (!line->is_modified) {
    line->is_modified = 1;
  }

  return 1;
}

int vt_line_is_cleared_to_end(vt_line_t *line) {
  if (vt_line_get_num_filled_cols(line) < line->change_end_col + 1) {
    return 1;
  } else {
    return 0;
  }
}

int vt_line_is_modified(vt_line_t *line) { return line->is_modified; }

int vt_line_get_beg_of_modified(vt_line_t *line) {
  if (IS_EMPTY(line)) {
    return 0;
  } else {
    return vt_convert_col_to_char_index(line, NULL, line->change_beg_col, 0);
  }
}

int vt_line_get_end_of_modified(vt_line_t *line) {
  if (IS_EMPTY(line)) {
    return 0;
  } else {
    return vt_convert_col_to_char_index(line, NULL, line->change_end_col, 0);
  }
}

u_int vt_line_get_num_redrawn_chars(vt_line_t *line, int to_end) {
  if (IS_EMPTY(line)) {
    return 0;
  } else if (to_end) {
    return line->num_filled_chars - vt_line_get_beg_of_modified(line);
  } else {
    return vt_line_get_end_of_modified(line) - vt_line_get_beg_of_modified(line) + 1;
  }
}

void vt_line_set_updated(vt_line_t *line) {
  line->is_modified = 0;

  line->change_beg_col = 0;
  line->change_end_col = 0;
}

int vt_line_is_continued_to_next(vt_line_t *line) { return line->is_continued_to_next; }

void vt_line_set_continued_to_next(vt_line_t *line, int flag) { line->is_continued_to_next = flag; }

int vt_convert_char_index_to_col(vt_line_t *line, int char_index, int flag /* BREAK_BOUNDARY */
                                 ) {
  int count;
  int col;

  if (char_index >= line->num_chars) {
#ifdef __DEBUG
    bl_debug_printf(BL_DEBUG_TAG
                    " char index %d is larger than num_chars(%d) ... modified -> %d.\n",
                    char_index, line->num_chars, line->num_chars - 1);
#endif

    char_index = line->num_chars - 1;
  }

  col = 0;

  if ((flag & BREAK_BOUNDARY) && line->num_filled_chars <= char_index) {
    for (count = 0; count < line->num_filled_chars; count++) {
      col += vt_char_cols(line->chars + count);
    }

    col += (char_index - count);
  } else if (line->num_filled_chars > 0) {
    /*
     * excluding the width of the last char.
     */
    int end = BL_MIN(char_index, END_CHAR_INDEX(line));

    for (count = 0; count < end; count++) {
      col += vt_char_cols(line->chars + count);
    }
  }

  return col;
}

int vt_convert_col_to_char_index(vt_line_t *line, u_int *cols_rest, int col,
                                 int flag /* BREAK_BOUNDARY */
                                 ) {
  int char_index;

#ifdef DEBUG
  if (col >= line->num_chars * 2 && cols_rest) {
    bl_warn_printf(BL_DEBUG_TAG
                   " Since col [%d] is over line->num_chars * 2 [%d],"
                   " cols_rest will be corrupt...\n",
                   col, line->num_chars * 2);
  }
#endif

  for (char_index = 0; char_index + 1 < line->num_filled_chars; char_index++) {
    int cols;

    cols = vt_char_cols(line->chars + char_index);
    if (col < cols) {
      goto end;
    }

    col -= cols;
  }

  if (flag & BREAK_BOUNDARY) {
    char_index += col;
    col = 0;
  }

end:
  if (cols_rest != NULL) {
    *cols_rest = col;
  }

  return char_index;
}

int vt_line_reverse_color(vt_line_t *line, int char_index) {
  if (char_index >= line->num_filled_chars) {
    return 0;
  }

  if (vt_char_reverse_color(line->chars + char_index)) {
    vt_line_set_modified(line, char_index, char_index);
  }

  return 1;
}

int vt_line_restore_color(vt_line_t *line, int char_index) {
  if (char_index >= line->num_filled_chars) {
    return 0;
  }

  if (vt_char_restore_color(line->chars + char_index)) {
    vt_line_set_modified(line, char_index, char_index);
  }

  return 1;
}

/*
 * This copys a line as it is and doesn't care about visual order.
 * But bidi parameters are also copyed as it is.
 */
int vt_line_copy(vt_line_t *dst, /* should be initialized ahead */
                 vt_line_t *src) {
  copy_line(dst, src, 0);

  return 1;
}

int vt_line_swap(vt_line_t *line1, vt_line_t *line2) {
  vt_line_t tmp;

  tmp = *line1;
  *line1 = *line2;
  *line2 = tmp;

  return 1;
}

int vt_line_share(vt_line_t *dst, vt_line_t *src) {
  memcpy(dst, src, sizeof(vt_line_t));

  return 1;
}

int vt_line_is_empty(vt_line_t *line) { return IS_EMPTY(line); }

int vt_line_beg_char_index_regarding_rtl(vt_line_t *line) {
  int char_index;

  if (vt_line_is_rtl(line)) {
    for (char_index = 0; char_index < line->num_filled_chars; char_index++) {
      if (!vt_char_equal(line->chars + char_index, vt_sp_ch())) {
        return char_index;
      }
    }
  }

  return 0;
}

int vt_line_end_char_index(vt_line_t *line) {
  if (IS_EMPTY(line)) {
#ifdef __DEBUG
    bl_debug_printf(BL_DEBUG_TAG " num_filled_chars is 0.\n");
#endif

    return 0;
  } else {
    return line->num_filled_chars - 1;
  }
}

u_int vt_line_get_num_filled_cols(vt_line_t *line) {
  return vt_str_cols(line->chars, line->num_filled_chars);
}

u_int vt_line_get_num_filled_chars_except_sp_with_func(vt_line_t *line,
                                                       int (*func)(vt_char_t *, vt_char_t *)) {
  if (IS_EMPTY(line)) {
    return 0;
  } else if (vt_line_is_rtl(line) || line->is_continued_to_next) {
    return line->num_filled_chars;
  } else {
    int char_index;

    for (char_index = END_CHAR_INDEX(line); char_index >= 0; char_index--) {
#if 1
      /* >= 3.0.6 */
      if (!(*func)(line->chars + char_index, vt_sp_ch()))
#else
      /* <= 3.0.5 */
      if (!vt_char_equal(line->chars + char_index, vt_sp_ch()))
#endif
      {
        return char_index + 1;
      }
    }

    return 0;
  }
}

void vt_line_set_size_attr(vt_line_t *line, int size_attr) {
  if (line->size_attr != size_attr) {
    line->size_attr = size_attr;
    vt_line_set_modified_all(line);
  }
}

int vt_line_convert_visual_char_index_to_logical(vt_line_t *line, int char_index) {
  if (vt_line_is_using_bidi(line)) {
    return vt_line_bidi_convert_visual_char_index_to_logical(line, char_index);
  }
#ifdef USE_OT_LAYOUT
  else if (vt_line_is_using_ot_layout(line)) {
    return vt_line_ot_layout_convert_visual_char_index_to_logical(line, char_index);
  }
#endif
  else if (vt_line_is_using_iscii(line)) {
    return vt_line_iscii_convert_visual_char_index_to_logical(line, char_index);
  } else {
    return char_index;
  }
}

int vt_line_is_rtl(vt_line_t *line) {
  if (vt_line_is_using_bidi(line)) {
    return vt_line_bidi_is_rtl(line);
  } else {
    return 0;
  }
}

/*
 * It is assumed that this function is called in *visual* context.
 */
int vt_line_copy_logical_str(vt_line_t *line, vt_char_t *dst, int beg, /* visual position */
                             u_int len) {
  if (vt_line_is_using_bidi(line)) {
    if (vt_line_bidi_copy_logical_str(line, dst, beg, len)) {
      return 1;
    }
  }

  return vt_str_copy(dst, line->chars + beg, len);
}

int vt_line_convert_logical_char_index_to_visual(vt_line_t *line, int char_index,
                                                 u_int32_t *meet_pos_info) {
#ifdef NO_DYNAMIC_LOAD_CTL
  if (line->ctl_info_type) {
#ifdef USE_OT_LAYOUT
    if (vt_line_is_using_ot_layout(line)) {
      char_index = vt_line_ot_layout_convert_logical_char_index_to_visual(line, char_index);
    } else
#endif
        if (vt_line_is_using_bidi(line)) {
#ifdef USE_FRIBIDI
      char_index = vt_line_bidi_convert_logical_char_index_to_visual(line, char_index,
                                                                     meet_pos_info);
#endif
    } else /* if( vt_line_is_using_iscii( line)) */
    {
#ifdef USE_IND
      char_index = vt_line_iscii_convert_logical_char_index_to_visual(line, char_index);
#endif
    }
  }

  return char_index;
#else
  if (line->ctl_info_type) {
#ifdef USE_OT_LAYOUT
    if (vt_line_is_using_ot_layout(line)) {
      char_index = vt_line_ot_layout_convert_logical_char_index_to_visual(line, char_index);
    } else
#endif
        if (vt_line_is_using_bidi(line)) {
      int (*bidi_func)(vt_line_t *, int, int *);

      if ((bidi_func = vt_load_ctl_bidi_func(VT_LINE_BIDI_CONVERT_LOGICAL_CHAR_INDEX_TO_VISUAL))) {
        char_index = (*bidi_func)(line, char_index, meet_pos_info);
      }
    } else /* if( vt_line_is_using_iscii( line)) */
    {
      int (*iscii_func)(vt_line_t *, int);

      if ((iscii_func =
               vt_load_ctl_iscii_func(VT_LINE_ISCII_CONVERT_LOGICAL_CHAR_INDEX_TO_VISUAL))) {
        char_index = (*iscii_func)(line, char_index);
      }
    }
  }

  return char_index;
#endif
}

int vt_line_unuse_ctl(vt_line_t *line) {
  if (line->ctl_info_type) {
    /* *_render() which may be called later works only if vt_line_t is really modified. */
    set_real_modified(line, 0, END_CHAR_INDEX(line));
#ifdef USE_OT_LAYOUT
    if (vt_line_is_using_ot_layout(line)) {
      return vt_line_set_use_ot_layout(line, 0);
    } else
#endif
        if (vt_line_is_using_bidi(line)) {
      return vt_line_set_use_bidi(line, 0);
    } else /* if( vt_line_is_using_iscii( line)) */
    {
      return vt_line_set_use_iscii(line, 0);
    }
  }

  return 0;
}

int vt_line_ctl_render(vt_line_t *line, vt_bidi_mode_t bidi_mode, const char *separators,
                       void *term) {
  int ret;
  int (*set_use_ctl)(vt_line_t *, int);

  if (!vt_line_is_using_ctl(line)) {
    if (
#ifdef USE_OT_LAYOUT
        (!term || !vt_line_set_use_ot_layout(line, 1)) &&
#endif
        !vt_line_set_use_bidi(line, 1) && !vt_line_set_use_iscii(line, 1)) {
      return 0;
    }
  }

  if (line->ctl_info_type) {
#ifdef USE_OT_LAYOUT
    if (vt_line_is_using_ot_layout(line)) {
      if (!term) {
        ret = -1;
      } else if ((ret = vt_line_ot_layout_render(line, term)) >= 0) {
        return ret;
      }

      set_use_ctl = vt_line_set_use_ot_layout;

      if (ret == -1) {
        goto render_bidi;
      } else /* if( ret == -2) */
      {
        goto render_iscii;
      }
    } else
#endif
        if (vt_line_is_using_bidi(line)) {
      if ((ret = vt_line_bidi_render(line, bidi_mode, separators)) >= 0) {
        return ret;
      }

#if !defined(NO_DYNAMIC_LOAD_CTL) || defined(USE_FRIBIDI)
      set_use_ctl = vt_line_set_use_bidi;
#else
/* Never enter here */
#endif

      if (ret == -1) {
#ifdef USE_OT_LAYOUT
        if (!term) {
          return 1;
        }

        goto render_ot_layout;
#else
        return 1;
#endif
      } else /* if( ret == -2) */
      {
        goto render_iscii;
      }
    } else /* if( vt_line_is_using_iscii( line)) */
    {
      if ((ret = vt_line_iscii_render(line)) >= 0) {
        return ret;
      }

#if !defined(NO_DYNAMIC_LOAD_CTL) || defined(USE_IND)
      set_use_ctl = vt_line_set_use_iscii;
#else
/* Never enter here */
#endif

#ifdef USE_OT_LAYOUT
      if (term) {
        goto render_ot_layout;
      } else
#endif
      {
        goto render_bidi;
      }
    }
  }

  return 0;

#ifdef USE_OT_LAYOUT
render_ot_layout:
  (*set_use_ctl)(line, 0);
  vt_line_set_use_ot_layout(line, 1);

  if ((ret = vt_line_ot_layout_render(line, term) != -1)) {
    return ret;
  }

/* Fall through */
#endif

render_bidi:
  if (
#if !defined(NO_DYNAMIC_LOAD_CTL)
      vt_load_ctl_bidi_func(VT_LINE_SET_USE_BIDI)
#elif defined(USE_FRIBIDI)
      1
#else
      0
#endif
          ) {
    (*set_use_ctl)(line, 0);
    vt_line_set_use_bidi(line, 1);

    return vt_line_bidi_render(line, bidi_mode, separators);
  } else {
    return 0;
  }

render_iscii:
  if (
#if !defined(NO_DYNAMIC_LOAD_CTL)
      vt_load_ctl_iscii_func(VT_LINE_SET_USE_ISCII)
#elif defined(USE_ISCII)
      1
#else
      0
#endif
          ) {
    (*set_use_ctl)(line, 0);
    vt_line_set_use_iscii(line, 1);

    return vt_line_iscii_render(line);
  } else {
    return 0;
  }
}

int vt_line_ctl_visual(vt_line_t *line) {
  if (line->ctl_info_type) {
#ifdef USE_OT_LAYOUT
    if (vt_line_is_using_ot_layout(line)) {
      return vt_line_ot_layout_visual(line);
    } else
#endif
        if (vt_line_is_using_bidi(line)) {
      return vt_line_bidi_visual(line);
    } else /* if( vt_line_is_using_iscii( line)) */
    {
      return vt_line_iscii_visual(line);
    }
  }

  return 0;
}

int vt_line_ctl_logical(vt_line_t *line) {
  if (line->ctl_info_type) {
#ifdef USE_OT_LAYOUT
    if (vt_line_is_using_ot_layout(line)) {
      return vt_line_ot_layout_logical(line);
    } else
#endif
        if (vt_line_is_using_bidi(line)) {
      return vt_line_bidi_logical(line);
    } else /* if( vt_line_is_using_iscii( line)) */
    {
      return vt_line_iscii_logical(line);
    }
  }

  return 0;
}

int vt_line_clear_picture(vt_line_t *line) {
  int count;
  int rmpic = 0;

  for (count = 0; count < line->num_filled_chars; count++) {
    vt_char_t *ch;

    if ((ch = vt_get_picture_char(line->chars + count))) {
      rmpic = 1;
      vt_char_copy(ch, vt_sp_ch());
    }
  }

  return rmpic;
}

#ifdef DEBUG

void vt_line_dump(vt_line_t *line) {
  int count;

  for (count = 0; count < line->num_filled_chars; count++) {
    vt_char_dump(line->chars + count);
  }

  bl_msg_printf("\n");
}

#endif
