#include <string.h>
#include <limits.h>
#include "mb.h"

size_t
mb_store_mem_once(const char *s, size_t n, mb_info_t *info)
{
  size_t sum = 0;

  if (n > info->size - info->e) {
    if (info->e && info->io_func.out) {
      size_t m = info->io_func.out(info->buf, info->e, info->io_arg);

      if (m) {
	info->b = info->b > m ? info->b - m : 0;
	info->i = info->i > m ? info->i - m : 0;

	if (m < info->e)
	  memmove(info->buf, &info->buf[m], info->e - m);

	info->e -= m;
      }
    }

    if (n > info->size - info->e) {
      if (!info->e && info->io_func.out) {
	sum = info->io_func.out(s, n, info->io_arg);
	s += sum;

	if ((n -= sum) > info->size - info->e)
	  n = info->size - info->e;
      }
      else
	n = info->size - info->e;
    }
  }

  if (n) {
    memmove(&info->buf[info->e], s, n);
    info->e += n;
    sum += n;
  }

  return sum;
}

#ifndef MB_STORE_MEM_TRY_MAX
#define MB_STORE_MEM_TRY_MAX (3)
#endif

size_t
mb_store_mem(const char *s, size_t n, mb_info_t *info)
{
  size_t try, m, sum;

  for (try = MB_STORE_MEM_TRY_MAX, sum = 0 ; sum < n ;) {
    m = mb_store_mem_once(&s[sum], n - sum, info);

    if (m) {
      sum += m;
      try = MB_STORE_MEM_TRY_MAX;
    }
    else {
      if (!try)
	break;

      --try;
    }
  }

  return sum;
}

size_t
mb_flush_buffer(mb_info_t *info)
{
  size_t sum = 0;

  if (info->io_func.out && !(info->flag & MB_FLAG_DONTFLUSH_BUFFER)) {
    size_t try, m;

    for (try = MB_STORE_MEM_TRY_MAX ; sum < info->e ;) {
      m = info->io_func.out(&info->buf[sum], info->e - sum, info->io_arg);

      if (m) {
	sum += m;
	try = MB_STORE_MEM_TRY_MAX;
      }
      else {
	if (!try)
	  break;

	--try;
      }
    }

    if (sum) {
      info->b = info->b > sum ? info->b - sum : 0;
      info->i = info->i > sum ? info->i - sum : 0;

      if (sum < info->e)
	memmove(info->buf, &info->buf[sum], info->e - sum);

      info->e -= sum;
    }
  }

  return sum;
}

size_t
mb_force_flush_buffer(size_t min, mb_info_t *info)
{
  size_t sum = 0;

  if (info->io_func.out) {
    size_t try, m;

    for (try = (info->flag & MB_FLAG_DONTFLUSH_BUFFER ? 0 : MB_STORE_MEM_TRY_MAX) ; info->size - info->e + sum < min ;) {
      m = info->io_func.out(&info->buf[sum], info->e - sum, info->io_arg);

      if (m) {
	sum += m;
	try = MB_STORE_MEM_TRY_MAX;
      }
      else {
	if (!try)
	  break;

	--try;
      }
    }
  }

  if (info->size - info->e + sum < min && sum < min)
    sum = min;

  if (sum) {
    info->b = info->b > sum ? info->b - sum : 0;
    info->i = info->i > sum ? info->i - sum : 0;

    if ((info->e -= sum))
      memmove(info->buf, &info->buf[sum], info->e);
  }

  return sum;
}

static struct {char *seq; size_t len;}
mb_lsl[] = {
  {"\x0F", 1},
  {"\x0E", 1},
  {"\x1B\x6E", 2},
  {"\x1B\x6F", 2},
},
mb_lsr[] = {
  {"", 0},
  {"\x1B\x7E", 2},
  {"\x1B\x7D", 2},
  {"\x1B\x7C", 2},
},
mb_icv94x94[MB_ESC_IC_MASK + 1] = {
},
mb_icv96[MB_ESC_IC_MASK + 1] = {
},
mb_icv94[MB_ESC_IC_MASK + 1] = {
  {"\x21", 1},
};

void
mb_store_esc_for_char_internal(mb_GnSn_t *gnsn, mb_chartype_t *ct, mb_info_t *info)
{
  if (gnsn->gn < mb_Gn &&
      (info->G.set[gnsn->gn] != ct->set || info->G.fc[gnsn->gn] != ct->fc)) {
    int ic;

    switch (ct->set) {
    case mb_94x94:
      mb_store_octet(0x1B, info);
      mb_store_octet(0x24, info);

      switch (ct->fc) {
      case 0x40:
      case 0x41:
      case 0x42:
	if (gnsn->gn == mb_G0 && !(info->flag & MB_FLAG_28FOR94X94G0))
	  break;
      default:
	mb_store_octet(0x28 + gnsn->gn, info);
	ic = ((ct->fc - MB_ESC_FC_BASE) >> MB_DBC_FC_LEN) & MB_ESC_IC_MASK;

	if (ic > 0 && mb_icv94x94[ic - 1].len)
	  mb_store_mem(mb_icv94x94[ic - 1].seq, mb_icv94x94[ic - 1].len, info);

	break;
      }

      mb_store_octet(ct->fc, info);
      break;
    case mb_96:
      mb_store_octet(0x1B, info);
      mb_store_octet(0x2C + gnsn->gn, info);
      ic = ((ct->fc - MB_ESC_FC_BASE) >> MB_ESC_FC_LEN) & MB_ESC_IC_MASK;

      if (ic > 0 && mb_icv96[ic - 1].len)
	mb_store_mem(mb_icv96[ic - 1].seq, mb_icv96[ic - 1].len, info);

      mb_store_octet((ct->fc & MB_ESC_FC_MASK) + MB_ESC_FC_BASE, info);
      break;
    case mb_94:
      mb_store_octet(0x1B, info);
      mb_store_octet(0x28 + gnsn->gn, info);
      ic = ((ct->fc - MB_ESC_FC_BASE) >> MB_ESC_FC_LEN) & MB_ESC_IC_MASK;

      if (ic > 0 && mb_icv94[ic - 1].len)
	mb_store_mem(mb_icv94[ic - 1].seq, mb_icv94[ic - 1].len, info);

      mb_store_octet((ct->fc & MB_ESC_FC_MASK) + MB_ESC_FC_BASE, info);
      break;
    default:
      return;
    }

    info->G.set[gnsn->gn] = ct->set;
    info->G.fc[gnsn->gn] = ct->fc;
  }

  switch (gnsn->sn) {
  case mb_SL:
    if (info->G.l != gnsn->gn) {
      switch (gnsn->gn) {
      case mb_G0:
      case mb_G1:
      case mb_G2:
      case mb_G3:
	mb_store_mem(mb_lsl[gnsn->gn].seq, mb_lsl[gnsn->gn].len, info);
      default:
	break;
      }

      info->G.l = gnsn->gn;
    }

    break;
  case mb_SR:
    if (info->G.r != gnsn->gn) {
      switch (gnsn->gn) {
      case mb_G0:
      case mb_G1:
      case mb_G2:
      case mb_G3:
	if (info->GRB4 == gnsn->gn) {
	  mb_store_octet(0x1B, info);
	  mb_store_octet(0x25, info);
	  mb_store_octet(0x40, info);
	}
	else
	  mb_store_mem(mb_lsr[gnsn->gn].seq, mb_lsl[gnsn->gn].len, info);

	info->GRB4 = mb_GN;
	break;
      default:
	if (gnsn->gn > mb_Gn && gnsn->gn < mb_GN) {
	  int withreturn = 1;

	  mb_store_octet(0x1B, info);
	  mb_store_octet(0x25, info);

	  switch (gnsn->gn) {
	  case mb_UTF8:
	    mb_store_octet(0x47, info);
	    break;
	  case mb_UTF16:
	  case mb_UTF16BE:
	  case mb_UTF16LE:
	    mb_store_octet(0x2F, info);
	    mb_store_octet(0x4C, info);
	    withreturn = 0;
	    break;
	  default:
	    mb_store_octet(0x21, info);
	    mb_store_octet(0x20 + (((gnsn->gn - mb_MOEINTERNAL) >> 4) & 0xF), info);
	    mb_store_octet(0x30 + ((gnsn->gn - mb_MOEINTERNAL) & 0xF), info);
	    break;
	  }

	  if (info->G.r <= mb_Gn && withreturn)
	    info->GRB4 = info->G.r;
	}

	break;
      }

      info->G.r = gnsn->gn;
    }
  case mb_SSL:
    if (gnsn->gn == mb_G2 || gnsn->gn == mb_G3) {
      mb_store_octet(0x1B, info);
      mb_store_octet(0x4E + gnsn->gn - mb_G2, info);
    }

    break;
  case mb_SSR:
    if (gnsn->gn == mb_G2 || gnsn->gn == mb_G3)
      mb_store_octet(0x8E + gnsn->gn - mb_G2, info);
  default:
    break;
  }
}

mb_GnSn_t
mb_G0SL = {mb_G0, mb_SL},
mb_G1SL = {mb_G1, mb_SL},
mb_G1SR = {mb_G1, mb_SR},
mb_G2SL = {mb_G2, mb_SL},
mb_G2SR = {mb_G2, mb_SR},
mb_G2SSL = {mb_G2, mb_SSL},
mb_G2SSR = {mb_G2, mb_SSR},
mb_G3SL = {mb_G3, mb_SL},
mb_G3SR = {mb_G3, mb_SR},
mb_G3SSL = {mb_G3, mb_SSL},
mb_G3SSR = {mb_G3, mb_SSR};

size_t
mb_94x94L_decoder(mb_wchar_t wc, void *arg, mb_info_t *info)
{
  mb_chartype_t ct;
  mb_wchar_t c = MB_WORD_94x94_DEC(wc, ct.set, ct.fc);

  mb_store_esc_for_char((mb_GnSn_t *)arg, &ct, info);
  mb_store_octet(c / 94 + 0x21, info);
  mb_store_octet(c % 94 + 0x21, info);
  return 2;
}

size_t
mb_94x94R_decoder(mb_wchar_t wc, void *arg, mb_info_t *info)
{
  mb_chartype_t ct;
  mb_wchar_t c = MB_WORD_94x94_DEC(wc, ct.set, ct.fc);

  mb_store_esc_for_char((mb_GnSn_t *)arg, &ct, info);
  mb_store_octet(c / 94 + 0xA1, info);
  mb_store_octet(c % 94 + 0xA1, info);
  return 2;
}

size_t
mb_96L_decoder(mb_wchar_t wc, void *arg, mb_info_t *info)
{
  mb_chartype_t ct;
  mb_wchar_t c = MB_WORD_96_DEC(wc, ct.set, ct.fc);

  mb_store_esc_for_char((mb_GnSn_t *)arg, &ct, info);
  mb_store_octet(c + 0x20, info);
  return 1;
}

size_t
mb_96R_decoder(mb_wchar_t wc, void *arg, mb_info_t *info)
{
  mb_chartype_t ct;
  mb_wchar_t c = MB_WORD_96_DEC(wc, ct.set, ct.fc);

  mb_store_esc_for_char((mb_GnSn_t *)arg, &ct, info);
  mb_store_octet(c + 0xA0, info);
  return 1;
}

size_t
mb_94L_decoder(mb_wchar_t wc, void *arg, mb_info_t *info)
{
  mb_chartype_t ct;
  mb_wchar_t c = MB_WORD_94_DEC(wc, ct.set, ct.fc);

  mb_store_esc_for_char((mb_GnSn_t *)arg, &ct, info);
  mb_store_octet(c + 0x21, info);
  return 1;
}

size_t
mb_94R_decoder(mb_wchar_t wc, void *arg, mb_info_t *info)
{
  mb_chartype_t ct;
  mb_wchar_t c = MB_WORD_94_DEC(wc, ct.set, ct.fc);

  mb_store_esc_for_char((mb_GnSn_t *)arg, &ct, info);
  mb_store_octet(c + 0xA1, info);
  return 1;
}

size_t
mb_CLGL_decoder(mb_wchar_t wc, void *arg, mb_info_t *info)
{
  if (wc >= 0x21 && wc <= 0x7E) {
    mb_chartype_t ct = {mb_94, MB_ASCII_FC};

    mb_store_esc_for_char((mb_GnSn_t *)arg, &ct, info);
  }
  else if (info->flag & MB_FLAG_ASCIIATCTL) {
    mb_GnSn_t gnsn = {mb_G0, mb_SL};
    mb_chartype_t ct = {mb_94, MB_ASCII_FC};

    mb_store_esc_for_char(&gnsn, &ct, info);
  }

  return mb_store_octet(wc, info);
}

size_t
mb_utf16_decoder(mb_wchar_t wc, void *arg, mb_info_t *info)
{
  if (info->G.l != mb_UTF16BE) {
    info->G.l = mb_UTF16BE;

    if (wc != 0xFEFF) {
      mb_store_octet(0xFE, info);
      mb_store_octet(0xFF, info);
    }
  }

  if (wc < 0x10000) {
    mb_store_octet(wc >> 8, info);
    mb_store_octet(wc & 0xFF, info);
    return 2;
  }
  else {
    mb_wchar_t surrogate;

    wc -= 0x10000;
    surrogate = ((wc & (((1U << 10) - 1U) << 10)) << (16 - 10)) | (wc & ((1U << 10) - 1U)) | 0xD800DC00;
    mb_store_octet(wc >> 24, info);
    mb_store_octet((wc >> 16) & 0xFF, info);
    mb_store_octet((wc >> 8) & 0xFF, info);
    mb_store_octet(wc & 0xFF, info);
    return 4;
  }
}

size_t
mb_utf16le_decoder(mb_wchar_t wc, void *arg, mb_info_t *info)
{
  if (wc < 0x10000) {
    mb_store_octet(wc & 0xFF, info);
    mb_store_octet(wc >> 8, info);
    return 2;
  }
  else {
    mb_wchar_t surrogate;

    wc -= 0x10000;
    surrogate = ((wc & (((1U << 10) - 1U) << 10)) << (16 - 10)) | (wc & ((1U << 10) - 1U)) | 0xD800DC00;
    mb_store_octet(wc & 0xFF, info);
    mb_store_octet((wc >> 8) & 0xFF, info);
    mb_store_octet((wc >> 16) & 0xFF, info);
    mb_store_octet(wc >> 24, info);
    return 4;
  }
}

void
mb_store_esc(mb_G_t *new, mb_info_t *info)
{
  mb_GnSn_t gnsn;
  mb_chartype_t ct;

  for (gnsn.gn = mb_G0 ; gnsn.gn < mb_Gn ; ++(gnsn.gn)) {
    gnsn.sn = new->l == gnsn.gn ? mb_SL : new->r == gnsn.gn ? mb_SR : mb_Sn;
    ct.set = new->set[gnsn.gn];
    ct.fc = new->fc[gnsn.gn];
    mb_store_esc_for_char(&gnsn, &ct, info);
  }
}

size_t
mb_store_char_noconv(int c, mb_info_t *info)
{
  if (c == EOF) {
    switch (info->G.r) {
    case mb_UTF16:
    case mb_UTF16BE:
    case mb_UTF16LE:
      break;
    default:
      mb_store_esc(&info->Gsave, info);
      break;
    }

    mb_flush_buffer(info);
    return 0;
  }
  else {
    if (info->flag & MB_FLAG_ASCIIATCTL) {
      mb_GnSn_t gnsn = {mb_G0, mb_SL};
      mb_chartype_t ct = {mb_94, MB_ASCII_FC};

      mb_store_esc_for_char(&gnsn, &ct, info);
    }

    mb_store_octet(c, info);
    return 1;
  }
}

size_t
mb_default_decoder(mb_wchar_t enc, mb_info_t *info)
{
  mb_GnSn_t gnsn;
  mb_chartype_t ct;
  mb_wchar_t c = MB_WORD_DEC(enc, ct.set, ct.fc);
  size_t n;

  gnsn.sn = mb_SL;

  switch (ct.set) {
  case mb_94x94:
    gnsn.gn = mb_G0;
    mb_store_esc_for_char(&gnsn, &ct, info);
    mb_store_octet(c / 94 + 0x21, info);
    mb_store_octet(c % 94 + 0x21, info);
    n = 2;
    break;
  case mb_96:
    gnsn.gn = mb_G1;
    mb_store_esc_for_char(&gnsn, &ct, info);
    mb_store_octet(c + 0x20, info);
    n = 1;
    break;
  case mb_94:
    gnsn.gn = mb_G0;
    mb_store_esc_for_char(&gnsn, &ct, info);
    mb_store_octet(c + 0x21, info);
    n = 1;
    break;
  case mb_SBC:
    if (ct.fc == MB_NOTCHAR_FC)
      return enc == mb_notchar_eof ? mb_store_char_noconv(EOF, info) : 0;
  default:
    {
      char buf[MB_MBC_LEN_MAX];

      n = mb_wchar_to_mbc(enc, buf);
      gnsn.gn = mb_MOEINTERNAL;
      gnsn.sn = mb_SR;
      mb_store_esc_for_char(&gnsn, &ct, info);
      n = mb_store_mem(buf, n, info);
      break;
    }
  }

  return n;
}

static mb_default_decoder_func_t mb_fallback_decoder = mb_default_decoder;

mb_default_decoder_func_t
mb_setup_fallback_decoder(mb_default_decoder_func_t new)
{
  mb_default_decoder_func_t old = mb_fallback_decoder;

  if (new)
    mb_fallback_decoder = new;

  return old;
}

static unsigned char default_domestic_ascii_tab[] = {
#include "domestic-ascii.h"
};

static unsigned char *domestic_ascii_tab = default_domestic_ascii_tab;

unsigned char *
mb_set_domestic_ascii_table(unsigned char *new)
{
  unsigned char *old = domestic_ascii_tab;

  if (new)
    domestic_ascii_tab = new;

  return old;
}

size_t
mb_conv_ascii(mb_wchar_t *ws, mb_wchar_t *ws_end, mb_info_t *info)
{
  size_t n;

  for (n = 0 ; ws < ws_end ; ++ws)
    if (*ws >= MB_94_LOWER && *ws < MB_94_UPPER &&
	domestic_ascii_tab[MB_WORD_SBC_FC_DEC((*ws - MB_94_LOWER) / MB_94_UNIT)]) {
      *ws = (*ws - MB_94_LOWER) % MB_94_UNIT + 0x21;
      ++n;
    }

  return n;
}

static unsigned int jis1flag_tab[] = {
#include "jis1flag.h"
};

int
mb_get_jis1flag(mb_wchar_t c)
{
  unsigned int value;

  return bt_search(c, jis1flag_tab, &value) == bt_failure ? MB_IN_JIS1COMMON : value;
}

size_t
mb_conv_to_jisx0213(mb_wchar_t *ws, mb_wchar_t *ws_end, mb_info_t *info)
{
  size_t n;

  for (n = 0 ; ws < ws_end ; ++ws)
    if (*ws >= MB_WORD_94x94_ENC(0x42, 0) && *ws <= MB_WORD_94x94_ENC(0x42, MB_94x94_UNIT - 1U)) {
      if (!(mb_get_jis1flag(*ws - MB_WORD_94x94_ENC(0x42, 0)) & MB_IN_JISX0208)) {
	*ws = *ws - MB_WORD_94x94_ENC(0x42, 0) + MB_WORD_94x94_ENC(0x4F, 0);
	++n;
      }
    }
    else if (*ws >= MB_WORD_94x94_ENC(0x40, 0) && *ws <= MB_WORD_94x94_ENC(0x40, MB_94x94_UNIT - 1U) &&
	     !(mb_get_jis1flag(*ws - MB_WORD_94x94_ENC(0x40, 0)) & MB_IN_JISC6226)) {
      *ws = *ws - MB_WORD_94x94_ENC(0x40, 0) + MB_WORD_94x94_ENC(0x4F, 0);
      ++n;
    }

  return n;
}

size_t
mb_conv_to_jisx0213_aggressive(mb_wchar_t *ws, mb_wchar_t *ws_end, mb_info_t *info)
{
  size_t n;

  for (n = 0 ; ws < ws_end ; ++ws)
    if (*ws >= MB_WORD_94x94_ENC(0x42, 0) && *ws <= MB_WORD_94x94_ENC(0x42, MB_94x94_UNIT - 1U)) {
      *ws = *ws - MB_WORD_94x94_ENC(0x42, 0) + MB_WORD_94x94_ENC(0x4F, 0);
      ++n;
    }
    else if (*ws >= MB_WORD_94x94_ENC(0x40, 0) && *ws <= MB_WORD_94x94_ENC(0x40, MB_94x94_UNIT - 1U)) {
      *ws = *ws - MB_WORD_94x94_ENC(0x40, 0) + MB_WORD_94x94_ENC(0x4F, 0);
      ++n;
    }

  return n;
}

#ifdef USE_UCS

static unsigned int i2u_tab[] = {
#include "iso-to-ucs.h"
};

size_t
mb_conv_to_ucs(mb_wchar_t *ws, mb_wchar_t *ws_end, mb_info_t *info)
{
  size_t n;

  for (n = 0 ; ws < ws_end ; ++ws)
    if (*ws & MB_NON_UCS_MARK) {
      unsigned int ucs;

      if (bt_search(*ws, i2u_tab, &ucs) != bt_failure) {
	*ws = ucs;
	++n;
      }
    }

  return n;
}

mb_wchar_t
mb_conv1_to_ucs(mb_wchar_t wc, mb_ces_t *ces)
{
  mb_conv_to_ucs(&wc, &wc + 1, NULL);
  return wc;
}

static unsigned int f2h_tab[] = {
#include "full-to-half.h"
};

size_t
mb_conv_f2h(mb_wchar_t *ws, mb_wchar_t *ws_end, mb_info_t *info)
{
  size_t n;
  unsigned int ucs, ucs1;

  for (n = 0 ; ws < ws_end ; ++ws) {
    if (*ws & MB_NON_UCS_MARK) {
      if (bt_search(*ws, i2u_tab, &ucs) == bt_failure)
	continue;
    }
    else
      ucs = *ws;

    if (bt_search(ucs, f2h_tab, &ucs1) != bt_failure) {
      *ws = ucs1;
      ++n;
    }
  }

  return n;
}

static unsigned int h2f_tab[] = {
#include "half-to-full.h"
};

size_t
mb_conv_h2f(mb_wchar_t *ws, mb_wchar_t *ws_end, mb_info_t *info)
{
  size_t n;
  unsigned int ucs, ucs1;

  for (n = 0 ; ws < ws_end ; ++ws) {
    if (*ws & MB_NON_UCS_MARK) {
      if (bt_search(*ws, i2u_tab, &ucs) == bt_failure)
	continue;
    }
    else
      ucs = *ws;

    if (bt_search(ucs, h2f_tab, &ucs1) != bt_failure) {
      *ws = ucs1;
      ++n;
    }
  }

  return n;
}

static unsigned int u2i_tab[] = {
#include "ucs-to-iso.h"
};

static mb_wchar_t u2i_pool[] = {
#include "ucs-to-iso-pool.h"
};

mb_wchar_t
mb_conv_for_decoder(mb_wchar_t wc, mb_decoder_map_t *decmap)
{
  mb_wchar_t ucs;
  unsigned int i;

  if (wc & MB_NON_UCS_MARK) {
    if (bt_search(wc, i2u_tab, &ucs) == bt_failure)
      return wc;
  }
  else
    ucs = wc;

  if (bt_search(ucs, u2i_tab, &i) != bt_failure) {
    mb_wchar_t *p = &u2i_pool[i];
    mb_wchar_range_t *tab = decmap->tab;
    size_t b;

    for (b = 0 ;; ++p) {
      mb_wchar_t wc1 = (*p & ~MB_U2I_POOL_LAST_MARK) + MB_NON_UCS_LOWER;
      size_t e, j;

      for (e = decmap->n ; b < e ;) {
	j = (b + e) / 2;

	if (wc1 < tab[j].wchar_min)
	  e = j;
	else if (wc1 > tab[j].wchar_max)
	  b = j + 1;
	else
	  return wc1;
      }

      if (*p & MB_U2I_POOL_LAST_MARK)
	break;
    }
  }

  return wc;
}

#ifdef USE_WIN1252
size_t
mb_conv_ms_latin1(mb_wchar_t *ws, mb_wchar_t *ws_end, mb_info_t *info)
{
  size_t n;
  mb_wchar_t wc;

  for (n = 0 ; ws < ws_end ; ++ws)
    if (*ws >= MB_WORD_SBC_ENC(MB_CTL_FC, 0x80 & MB_SBC_UNIT_MASK) &&
	*ws <= MB_WORD_SBC_ENC(MB_CTL_FC, 0x9F & MB_SBC_UNIT_MASK)) {
      wc = MB_WORD_SBC_ENC(MB_WIN1252_FC, *ws - MB_WORD_SBC_ENC(MB_CTL_FC, 0x80 & MB_SBC_UNIT_MASK));

      if (mb_conv_to_ucs(&wc, &wc + 1, info)) {
	*ws = wc;
	++n;
      }
    }
    else if (*ws >= 0x80 && *ws <= 0x9F) {
      wc = MB_WORD_SBC_ENC(MB_WIN1252_FC, *ws - 0x80);

      if (mb_conv_to_ucs(&wc, &wc + 1, info)) {
	*ws = wc;
	++n;
      }
    }

  return n;
}
#endif

#endif

size_t
mb_decode(mb_wchar_t *from, mb_wchar_t *from_end, mb_info_t *info)
{
  mb_wchar_t wc, wc1;
  size_t n = 0;
  mb_ces_t *ces;
  mb_conv_t *v;
  mb_decoder_map_t *decmap;
  size_t b, e, i;

  ces = info->ces;
  decmap = ces->decoder;

  while (from < from_end) {
    wc = *from++;
  loop:
    for (b = 0, e = decmap->n ; b < e ;) {
      i = (b + e) / 2;

      if (wc < decmap->tab[i].wchar_min)
	e = i;
      else if (wc > decmap->tab[i].wchar_max)
	b = i + 1;
      else {
	mb_decoder_t *dec = &decmap->dest[i];

	if (dec->func) {
	  n = dec->func(wc, dec->arg, info);
	  goto next;
	}
	else {
	  mb_char_map_t *map;
	  mb_wchar_t c;
	  size_t j, k;
	  mb_wchar_range_t *dectab;

	  map = dec->map;
	  c = wc - dec->wchar_base;
	  k = dec->wcv_pos;
	bsearch:
	  /* This search must not fail */
	  for (b = map->bv[k], e = map->bv[k + 1] ;;) {
	    j = (b + e) / 2;
	    dectab = &map->dectab[j];

	    if (c < dectab->wchar_min)
	      e = j;
	    else if (c > dectab->wchar_max)
	      b = j + 1;
	    else {
	      mb_char_node_t *node = &map->v[dectab->n];

	      mb_store_octet((c - dectab->wchar_min) / node->wcv[k].n + node->char_min, info);
	      ++n;

	      if (node->next.v) {
		c = (c - dectab->wchar_min) % node->wcv[k].n;
		map = &node->next;
		goto bsearch;
	      }
	      else
		goto next;
	    }
	  }
	}

	break;
      }
    }

    if ((v = ces->convv) && *v)
      do {
	if ((wc1 = (*v++)(wc, ces)) != wc) {
	  wc = wc1;
	  goto loop;
	}
      } while (*v);

    if (!(info->flag & MB_FLAG_DISCARD_NOTPREFERED_CHAR))
      n += ces->default_decoder ? ces->default_decoder(*(from - 1), info) : mb_fallback_decoder(*(from - 1), info);
  next:
    ;
  }

  return n;
}

size_t
mb_store_wchar(mb_wchar_t enc, mb_info_t *info)
MB_STORE_WCHAR

size_t
mb_conv_for_specific_ces(mb_wchar_t *ws, mb_wchar_t *ws_end, mb_ces_t *ces)
{
  mb_wchar_t wc, wc1;
  mb_conv_t *v;
  mb_decoder_map_t *decmap;
  size_t b, e, n;

  if (!ces)
    return 0;

  decmap = ces->decoder;

  for (n = 0 ; ws < ws_end ; ++ws) {
    wc = *ws;
  loop:
    for (b = 0, e = decmap->n ; b < e ;) {
      size_t i = (b + e) / 2;

      if (wc < decmap->tab[i].wchar_min)
	e = i;
      else if (wc > decmap->tab[i].wchar_max)
	b = i + 1;
      else {
	*ws = wc;
	++n;
	goto next;
      }
    }

    if ((v = ces->convv) && *v)
      do {
	if ((wc1 = (*v++)(wc, ces)) != wc) {
	  wc = wc1;
	  goto loop;
	}
      } while (*v);
  next:
    ;
  }

  return n;
}

size_t
mb_conv_for_ces(mb_wchar_t *ws, mb_wchar_t *ws_end, mb_info_t *info)
{
  return info ? mb_conv_for_specific_ces(ws, ws_end, info->ces) : 0;
}

size_t
mb_store_char(const char *s, size_t e, mb_info_t *info)
{
  mb_wchar_t wc;
  int b = mb_mem_to_wchar_internal(s, e, wc);

  if (b > 0)
    mb_store_wchar(wc, info);
  else if (b < 0)
    b = 0;
  else
    mb_store_char_noconv(EOF, info);

  return b;
}

int
mb_putc(int c, mb_info_t *info)
{
  if (info->aux_n > 0) {
    if ((c & 0xC0) == 0x80) {
      info->auxbuf[(info->aux_i)++] = c;

      if (!(info->aux_i < info->aux_n)) {
	mb_wchar_t wc;

	mb_mem_to_wchar_internal(info->auxbuf, info->aux_n, wc);
	mb_decode(&wc, &wc + 1, info);
	info->aux_i = info->aux_n = 0;
      }

      goto end;
    }
    else {
      size_t i;

      for (i = 0 ; i < info->aux_i ; ++i)
	mb_store_octet(info->auxbuf[i], info);

      info->aux_i = info->aux_n = 0;
    }
  }

  if ((c & 0xC0) == 0xC0) {
    info->auxbuf[0] = c;
    info->aux_i = 1;
    info->aux_n = MB_MBC_LEN_MAX;
  }
  else
    mb_store_sbc(c, info);
end:
  return c;
}

size_t
mb_putmem(const char *s, size_t n, mb_info_t *info)
{
  size_t i;

  for (i = 0 ; i < n && info->aux_i > 0 ;)
    mb_putc((unsigned char)s[i++], info);

  if (i < n) {
    const char *p, *ep;
    mb_wchar_t ws[BUFSIZ], *ewp;
    int cn;

    for (p = s + i, ep = s + n, ewp = ws ; p < ep ;) {
      if ((cn = mb_mem_to_wchar_internal(p, ep - p, *ewp)) < 1)
	cn = 1;

      if (++ewp >= ws + sizeof(ws) / sizeof(ws[0])) {
	mb_decode(ws, ewp, info);
	ewp = ws;
      }

      p += cn;
    }

    if (ewp > ws)
      mb_decode(ws, ewp, info);

    i = p - s;
  }

  return i;
}

size_t
mb_putstr(const char *s, mb_info_t *info)
{
  return mb_putmem(s, strlen(s), info);
}

void
mb_flush_auxbuf(mb_info_t *info)
{
  if (info->aux_i > 0) {
    size_t i;

    for (i = 0 ; i < info->aux_i ; ++i)
      mb_store_octet(info->auxbuf[i], info);
  }

  info->aux_i = info->aux_n = 0;
}

void
mb_flush(mb_info_t *info)
{
  mb_flush_auxbuf(info);
  mb_flush_buffer(info);
}

static size_t
mb_mem_write(const char *s, size_t n, void *ap)
{
  mb_info_t *info = ap;

  if (info->size - info->e < MB_MBC_LEN_MAX) {
    size_t nsize = ((info->e + n) / MB_MEM_DELTA + 1) * MB_MEM_DELTA;
    char *nbuf = alt_call_realloc(info->buf, nsize);

    if (!nbuf)
      return 0;

    info->buf = nbuf;
    info->size = nsize;
  }

  return 0;
}

char *
mb_vmem2iso_setup(mb_info_t *info, size_t n, mb_setup_t *setup, const char *op, va_list ap)
{
  if ((info->buf = alt_call_malloc_atomic(n))) {
    info->size = n;
    mb_vinit_w(info, info, mb_mem_write, setup, op, ap);
    info->flag |= MB_FLAG_DONTFLUSH_BUFFER;
    return info->buf;
  }
  else
    return NULL;
}

char *
mb_mem2iso_setup(mb_info_t *info, size_t n, mb_setup_t *setup, const char *op, ...)
{
  char *ret;
  va_list ap;

  va_start(ap, op);
  ret = mb_vmem2iso_setup(info, n, setup, op, ap);
  va_end(ap);
  return ret;
}

char *
mb_vmem2iso(const char *s, size_t *p_n, mb_setup_t *setup, const char *op, va_list ap)
{
  mb_info_t info;

  if (mb_vmem2iso_setup(&info, *p_n, setup, op, ap)) {
    mb_putmem(s, *p_n, &info);
    mb_flush_auxbuf(&info);
    *p_n = info.e;
    return info.buf;
  }
  else
    return NULL;
}

char *
mb_vstr2iso(const char *s, mb_setup_t *setup, const char *op, va_list ap)
{
  size_t n = strlen(s) + 1;

  return mb_vmem2iso(s, &n, setup, op, ap);
}

char *
mb_str2iso(const char *s, mb_setup_t *setup, const char *op, ...)
{
  va_list ap;
  char *iso;

  va_start(ap, op);
  iso = mb_vstr2iso(s, setup, op, ap);
  va_end(ap);
  return iso;
}

#define MB_PRINTF_FLAG_MINUS (1 << 0)
#define MB_PRINTF_FLAG_PLUS (1 << 1)
#define MB_PRINTF_FLAG_SPACE (1 << 2)
#define MB_PRINTF_FLAG_SHARP (1 << 3)
#define MB_PRINTF_FLAG_ZERO (1 << 4)

int
mb_vprintf(mb_info_t *info, const char *format, va_list ap)
{
  const char *b, *f, *s;
  const mb_wchar_t *ws;
  char *e;
  char tf[sizeof("%-+ #0.ld") + sizeof(int) * CHAR_BIT / 3 + 1] = "%";
  size_t fe, fs;
  char buf[3 + sizeof(double) * CHAR_BIT * 2 / 3 + 2 + sizeof(long) * CHAR_BIT / 3 + sizeof(void *) * CHAR_BIT / 3];
  int n, flag, width, precision, tn, i, j;
  mb_setup_t setup = {};
  mb_info_t copy;

  mb_setsetup(&setup, "@", (char *)info->ces->namev[0]);
  mb_vmem2iso_setup(&copy, MB_MEM_DELTA, &setup, "", ap);

  for (n = 0, b = format ; (f = strchr(b, '%')) ;) {
    if (b < f)
      n += mb_putmem(b, f - b, info);

    b = f++;
    flag = width = 0;
    fe = 1;

    for (;; ++f)
      switch ((unsigned char)*f) {
      case '-':
	flag |= MB_PRINTF_FLAG_MINUS;
	break;
      case '+':
	flag |= MB_PRINTF_FLAG_PLUS;
	break;
      case ' ':
	flag |= MB_PRINTF_FLAG_SPACE;
	break;
      case '#':
	flag |= MB_PRINTF_FLAG_SHARP;
	break;
      case '0':
	flag |= MB_PRINTF_FLAG_ZERO;
	break;
      default:
	goto for_width;
      }

  for_width:
    if ((unsigned char)*f == '*') {
      ++f;
      width = va_arg(ap, int);
    }
    else {
      width = strtol(f, &e, 10);

      if (e > f)
	f = e;
    }

    if (width < 0) {
      flag |= MB_PRINTF_FLAG_MINUS;
      width = -width;
    }

    if (flag & MB_PRINTF_FLAG_MINUS)
      tf[fe++] = '-';

    if (flag & MB_PRINTF_FLAG_PLUS)
      tf[fe++] = '+';

    if (flag & MB_PRINTF_FLAG_SPACE)
      tf[fe++] = ' ';

    if (flag & MB_PRINTF_FLAG_SHARP)
      tf[fe++] = '#';

    if (flag & MB_PRINTF_FLAG_ZERO)
      tf[fe++] = '0';

    precision = -1;

    if ((unsigned char)*f == '.') {
      tf[fe++] = '.';

      if ((unsigned char)*++f == '*') {
	precision = va_arg(ap, int);
	++f;
      }
      else {
	precision = strtol(f, &e, 10);
	f = e;
      }

      fe += sprintf(&tf[fe], "%d", precision);
    }

    switch ((unsigned char)f[fs = strcspn(f, "cdeEfgGinopsuxX%")]) {
    case 'c':
      if (fs)
	goto error;

      tf[fe++] = 'c';
      tf[fe++] = '\0';
      tn = sprintf(buf, tf, va_arg(ap, int));
      break;
    case 'd':
    case 'i':
    case 'o':
    case 'u':
    case 'x':
    case 'X':
      switch (fs) {
      case 1:
	if ((unsigned char)f[0] == 'h')
	  tf[fe++] = 'h';
	else if ((unsigned char)f[0] == 'l') {
	  tf[fe++] = 'l';
	  tf[fe++] = f[1];
	  tf[fe++] = '\0';
	  tn = sprintf(buf, tf, va_arg(ap, long));
	  break;
	}
	else
	  goto error;
      case 0:
	tf[fe++] = f[fs];
	tf[fe++] = '\0';
	tn = sprintf(buf, tf, va_arg(ap, int));
	break;
      default:
	goto error;
      }

      break;
    case 'e':
      switch (fs) {
      case 1:
	if (f[0] == 'l') {
	  tf[fe++] = 'l';
	  tf[fe++] = f[1];
	  tf[fe++] = '\0';
	  tn = sprintf(buf, tf, va_arg(ap, long double));
	  break;
	}
	else
	  goto error;
      case 0:
	tf[fe++] = f[0];
	tf[fe++] = '\0';
	tn = sprintf(buf, tf, va_arg(ap, double));
	break;
      default:
	goto error;
      }

      break;
    case 'E':
    case 'f':
    case 'g':
    case 'G':
      switch (fs) {
      case 1:
	if (f[0] == 'L') {
	  tf[fe++] = 'L';
	  tf[fe++] = f[1];
	  tf[fe++] = '\0';
	  tn = sprintf(buf, tf, va_arg(ap, long double));
	  break;
	}
	else
	  goto error;
      case 0:
	tf[fe++] = f[0];
	tf[fe++] = '\0';
	tn = sprintf(buf, tf, va_arg(ap, double));
	break;
      default:
	goto error;
      }

      break;
    case 'n':
      switch (fs) {
      case 1:
	switch ((unsigned char)f[0]) {
	case 'h':
	  *(va_arg(ap, short *)) = n;
	  break;
	case 'l':
	  *(va_arg(ap, long *)) = n;
	  break;
	default:
	  goto error;
	}

	break;
      case 0:
	*(va_arg(ap, int *)) = n;
	break;
      default:
	goto error;
      }

      b = f + fs + 1;
      continue;
    case 'p':
      if (fs)
	goto error;

      tf[fe++] = 'p';
      tf[fe++] = '\0';
      tn = sprintf(buf, tf, va_arg(ap, void *));
      break;
    case 's':
      if (!fs) {
	copy.G = info->G;
	copy.aux_i = copy.aux_n = copy.b = copy.i = copy.e = 0;

	for (s = va_arg(ap, char *) ; *s ;) {
	  tn = copy.e;
	  mb_putc((unsigned char)*s++, &copy);

	  if (precision >= 0 && copy.e > precision) {
	    copy.e = tn;
	    break;
	  }
	}
      }
      else if (fs == 1 && f[0] == 'w') {
	copy.G = info->G;
	copy.aux_i = copy.aux_n = copy.b = copy.i = copy.e = 0;

	for (ws = va_arg(ap, mb_wchar_t *) ; *ws ;) {
	  tn = copy.e;
	  mb_store_wchar(*ws++, &copy);

	  if (precision >= 0 && copy.e > precision) {
	    copy.e = tn;
	    break;
	  }
	}
      }
      else
	goto error;

      info->G = copy.G;
      mb_flush(info);

      if (copy.buf) {
	if (precision < 0 || precision > copy.e)
	  precision = copy.e;

	if (flag & MB_PRINTF_FLAG_MINUS) {
	  n += mb_store_mem(copy.buf, precision, info);

	  for (; width > precision ; --width) {
	    mb_store_octet(' ', info);
	    ++n;
	  }
	}
	else {
	  for (; width > precision ; --width) {
	    mb_store_octet(' ', info);
	    ++n;
	  }

	  n += mb_store_mem(copy.buf, precision, info);
	}
      }
      else if (width > 0)
	for (; width > 0 ; --width) {
	  mb_store_octet(' ', info);
	  ++n;
	}

      b = f + fs + 1;
      continue;
    case '%':
      if (fs)
	goto error;

      buf[0] = '%';
      buf[1] = '\0';
      tn = 1;
      break;
    default:
    error:
      n += mb_putmem(b++, 1, info);
      continue;
    }

    b = f + fs + 1;

    if (width > tn)
      if (flag & MB_PRINTF_FLAG_MINUS) {
	n += mb_putmem(buf, tn, info);

	for (; tn < width ; ++tn)
	  n += mb_putmem(" ", 1, info);
      }
      else if (flag & MB_PRINTF_FLAG_ZERO) {
	for (i = 0 ; i < tn && strchr("-+0 ", (unsigned char)buf[i]) ; ++i)
	  ;

	if (i < tn && strchr("xX", (unsigned char)buf[i]))
	  ++i;

	n += mb_putmem(buf, i, info);

	for (j = tn ; j < width ; ++j)
	  n += mb_putmem("0", 1, info);

	n += mb_putmem(&buf[i], tn - i, info);
      }
      else {
	for (j = tn ; j < width ; ++j)
	  n += mb_putmem(" ", 1, info);

	n += mb_putmem(buf, tn, info);
      }
    else
      n += mb_putmem(buf, tn, info);
  }

  if (*b)
    n += mb_putstr(b, info);

  if (copy.buf)
    alt_call_free(copy.buf);

  return n;
}

int
mb_printf(mb_info_t *info, const char *format, ...)
{
  va_list ap;
  int n;

  va_start(ap, format);
  n = mb_vprintf(info, format, ap);
  va_end(ap);
  return n;
}

static const char mb_b64_alpha[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

static void
mb_b64_flush_encode(mb_b64_t *b64)
{
  switch (b64->se) {
  case 1:
    b64->d[0] = mb_b64_alpha[(unsigned char)b64->d[0]];
    b64->d[1] = mb_b64_alpha[(unsigned char)b64->d[1]];
    b64->d[2] = b64->d[3] = '=';
    goto flush;
  case 2:
    b64->d[0] = mb_b64_alpha[(unsigned char)b64->d[0]];
    b64->d[1] = mb_b64_alpha[(unsigned char)b64->d[1]];
    b64->d[2] = mb_b64_alpha[(unsigned char)b64->d[2]];
    b64->d[3] = '=';
    goto flush;
  case 3:
    b64->d[0] = mb_b64_alpha[(unsigned char)b64->d[0]];
    b64->d[1] = mb_b64_alpha[(unsigned char)b64->d[1]];
    b64->d[2] = mb_b64_alpha[(unsigned char)b64->d[2]];
    b64->d[3] = mb_b64_alpha[(unsigned char)b64->d[3]];
  flush:
    if (!(b64->dn)) {
      if (b64->opt->pre)
	mb_store_mem(b64->opt->pre, b64->opt->pree, b64->orig);
    }
    else if (b64->opt->llen > 0 && !(b64->dn % b64->opt->llen)) {
      mb_store_mem(b64->opt->eol, b64->opt->eole, b64->orig);
      b64->dn = 0;
    }

    mb_store_mem(b64->d, 4, b64->orig);
    b64->d[0] = b64->d[1] = b64->d[2] = b64->d[3] = b64->se = 0;
    b64->dn += 4;
  default:
    break;
  }
}

static size_t
mb_b64_write_encode(const char *s, size_t se, mb_b64_t *b64)
{
  size_t sb, n;

  for (n = sb = 0 ; sb < se ; ++sb) {
    ++n;

    switch (b64->se) {
    case 0:
      b64->d[0] = ((unsigned char)s[sb] & 0xFC) >> 2;
      b64->d[1] = ((unsigned char)s[sb] & 0x03) << 4;
      break;
    case 1:
      b64->d[1] |= ((unsigned char)s[sb] & 0xF0) >> 4;
      b64->d[2] = ((unsigned char)s[sb] & 0x0F) << 2;
      break;
    default:
      b64->d[2] |= ((unsigned char)s[sb] & 0xC0) >> 6;
      b64->d[3] = (unsigned char)s[sb] & 0x3F;
      ++(b64->se);
      mb_b64_flush_encode(b64);
      continue;
    }

    ++(b64->se);
  }

  return n;
}

static size_t
mb_putmem_b64encode_flush(const char *s, size_t b, size_t e, mb_b64_t *b64, mb_info_t *work)
{
  size_t n = 0;

  if (b64->enc_p) {
    if (b < e)
      n += mb_b64_write_encode(&s[b], e - b, b64);

    n += mb_store_char_noconv(EOF, work);

    if (work->e) {
      n += mb_b64_write_encode(work->buf, work->e, b64);
      work->b = work->i = work->e = 0;
    }

    mb_b64_flush_encode(b64);
    b64->enc_p = 0;
    b64->dn = 0;

    if (b64->opt->post)
      n += mb_store_mem(b64->opt->post, b64->opt->poste, b64->orig);
  }
  else if (b < e)
    n += mb_store_mem(&s[b], e - b, b64->orig);

  return n;
}

size_t
mb_putmem_b64encode(const char *s, size_t se, mb_b64opt_t *b64opt, mb_info_t *info)
{
  size_t wb, sb, cb, ce, n;
  mb_wchar_t c;
  char buf[MB_ESC_LEN_MAX + MB_MBC_LEN_MAX];
  mb_info_t work = {};
  mb_b64_t b64 = {};

  mb_setup_by_ces(info->ces, &work);
  work.flag |= MB_FLAG_ASCIIATCTL;
  work.io_func.out = NULL;
  work.io_arg = NULL;
  work.buf = buf;
  work.size = sizeof(buf);
  work.b = work.i = work.e = 0;
  b64.opt = b64opt;
  b64.orig = info;

  for (wb = sb = n = 0 ; sb < se ;) {
    cb = 0;
    ce = se - sb;
    c = mb_mem_to_wchar(&s[sb], &cb, &ce);

    if (ce > 1) {
      if (wb < sb)
	n += mb_b64_write_encode(&s[sb], sb - wb, &b64);

      mb_store_wchar(c, &work);
      n += mb_b64_write_encode(buf, work.e, &b64);
      work.b = work.i = work.e = 0;
      wb = sb + ce;
      b64.enc_p = 1;
    }
    else if (strchr("\x09\x0A\x0D\x20", c)) {
      n += mb_putmem_b64encode_flush(s, wb, sb, &b64, &work);
      mb_store_octet(c, info);
      wb = sb + ce;
    }

    sb += ce;
  }

  n += mb_putmem_b64encode_flush(s, wb, se, &b64, &work);
  return n;
}

char *
mb_vmem2b64(const char *s, size_t *p_n, const char *title, mb_setup_t *setup, const char *op, va_list ap)
{
  mb_info_t info;

  if (mb_vmem2iso_setup(&info, *p_n, setup, op, ap)) {
    size_t cslen = strlen(title);
    char *pre;

    if ((pre = alt_call_malloc_atomic(cslen + sizeof("=??b?")))) {
      mb_b64opt_t b64opt;

      memcpy(pre, "=?", sizeof("=?") - 1);
      memcpy(&pre[sizeof("=?") - 1], title, cslen);
      memcpy(&pre[sizeof("=?") - 1 + cslen], "?b?", sizeof("?b?"));
      b64opt.pre = pre;
      b64opt.pree = cslen + sizeof("=??b?") - 1;
      b64opt.post = "?=";
      b64opt.poste = sizeof("?=") - 1;
      b64opt.llen = 76;
      b64opt.eol = "\n\t";
      b64opt.eole = sizeof("\n\t") - 1;
      *p_n = mb_putmem_b64encode(s, *p_n, &b64opt, &info);
    }
    else {
      alt_call_free(info.buf);
      info.buf = NULL;
    }
  }

  return info.buf;
}

char *
mb_mem2b64(const char *s, size_t *p_n, const char *title, mb_setup_t *setup, const char *op, ...)
{
  va_list ap;
  char *b64;

  va_start(ap, op);
  b64 = mb_vmem2b64(s, p_n, title, setup, op, ap);
  va_end(ap);
  return b64;
}

char *
mb_vstr2b64(const char *s, const char *title, mb_setup_t *setup, const char *op, va_list ap)
{
  size_t n = strlen(s) + 1;

  return mb_vmem2b64(s, &n, title, setup, op, ap);
}

char *
mb_str2b64(const char *s, const char *title, mb_setup_t *setup, const char *op, ...)
{
  va_list ap;
  char *b64;

  va_start(ap, op);
  b64 = mb_vstr2b64(s, title, setup, op, ap);
  va_end(ap);
  return b64;
}
