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

char mb_version_string[] = MB_VERSION_STRING;
int mb_version = MB_VERSION;

int
mb_call_getc_internal(mb_info_t *info)
{
  size_t n = info->size - info->e;

  if (!n) {
    if (info->b && !(info->flag & MB_FLAG_DONTFLUSH_BUFFER)) {
      info->i -= info->b;

      if ((info->e -= info->b) > 0)
	memmove(info->buf, &info->buf[info->b], info->e);

      info->b = 0;
    }

    if (!(n = info->size - info->e))
      return EOF;
  }

  n = info->io_func.in ? info->io_func.in(&info->buf[info->e], n, info->io_arg) : 0;
  return n ? (info->e += n, (unsigned char)info->buf[(info->i)++]) : EOF;
}

void
mb_fill_inbuf(mb_info_t *info, const char *s, size_t n)
{
  if (n > info->i - info->b) {
    n -= info->i - info->b;

    if (n > info->b) {
      s += n - info->b;
      n = info->b;
    }

    if (n > 0) {
      info->b -= n;
      memmove(&info->buf[info->b], s, n);
    }

    info->i = info->b;
  }
  else
    info->i -= n;
}

mb_wchar_t
mb_encode_failure(mb_info_t *info)
{
  int c;

  c  = (unsigned char)info->buf[(info->b)++];
  info->i = info->b;
  return MB_CTL_ENC(c);
}

mb_wchar_t
mb_94x94_encoder(int c1, int gn, mb_info_t *info)
{
  if ((c1 & 0x7F) < 0x21 || (c1 & 0x7F) > 0x7E)
    return MB_CTL_ENC(c1);
  else {
    int c2 = mb_call_getc(info);
    mb_wchar_t c;

    if (c2 == EOF)
      return mb_notchar_enc_short;

    if ((c1 & 0x80) != (c2 & 0x80))
      return mb_notchar_enc_invalid;

    if ((c2 &= 0x7F) < 0x21 || c2 > 0x7E)
      return mb_notchar_enc_invalid;

    c = ((c1 & 0x7F) - 0x21) * 94 + (c2 - 0x21);
    return MB_WORD_94x94_ENC(info->G.fc[gn], c);
  }
}

mb_wchar_t
mb_96_encoder(int c1, int gn, mb_info_t *info)
{
  return MB_WORD_96_ENC(info->G.fc[gn], (c1 & 0x7F) - 0x20);
}

static unsigned int default_notascii_tab[] = {
#include "notascii.h"
};

static unsigned int *notascii_tab = default_notascii_tab;

unsigned int *
mb_set_localized_ascii_table(unsigned int *new)
{
  unsigned int *old = notascii_tab;

  if (new)
    notascii_tab = new;

  return old;
}

mb_wchar_t
mb_94_encoder(int c1, int gn, mb_info_t *info)
{
  if ((c1 & 0x7F) < 0x21 || (c1 & 0x7F) > 0x7E)
    return MB_CTL_ENC(c1);
  else if (info->G.fc[gn] == MB_ASCII_FC)
    return c1 & 0x7F;
  else {
    mb_wchar_t enc;

    c1 = (c1 & 0x7F) - 0x21;
    enc = MB_WORD_94_ENC(info->G.fc[gn], c1);
    return bt_search(enc & ~MB_NON_UCS_MARK, notascii_tab, NULL) == bt_failure ? MB_WORD_94_ENC(MB_ASCII_FC, c1) : enc;
  }
}

mb_wchar_t (*mb_iso2022_encoderv[])(int c1, int gn, mb_info_t *info) = {
  mb_94x94_encoder,
  mb_96_encoder,
  mb_94_encoder,
};

mb_wchar_t
mb_iso2022_GL_encoder(mb_wchar_t c1, void *dummy_arg, mb_info_t *info)
{
  return mb_iso2022_encoder(c1, info->G.l, info);
}

mb_wchar_t
mb_iso2022_GR_encoder(mb_wchar_t c1, void *dummy_arg, mb_info_t *info)
{
  return mb_iso2022_encoder(c1, info->G.r, info);
}

mb_wchar_t
mb_iso2022_SSL_encoder(mb_wchar_t c1, void *dummy_arg, mb_info_t *info)
{
  return (info->flag & MB_FLAG_NOSSL ?
	  mb_notchar_enc_invalid :
	  mb_iso2022_encoder(c1 % (0x7F - 0x20 + 1) + 0x20, mb_G2 + ((c1 / (0x7F - 0x20 + 1)) & 1), info));
}

mb_wchar_t
mb_iso2022_SSR_encoder(mb_wchar_t c1, void *dummy_arg, mb_info_t *info)
{
  return mb_iso2022_encoder(c1 % (0xFF - 0xA0 + 1) + 0xA0, mb_G2 + ((c1 / (0xFF - 0xA0 + 1)) & 1), info);
}

mb_encoder_map_t *mb_encoder_map[mb_GN] = {
  &mb_encmap_ISO2022, /* G0 */
  &mb_encmap_ISO2022, /* G1 */
  &mb_encmap_ISO2022, /* G2 */
  &mb_encmap_ISO2022, /* G3 */
  &mb_encmap_ISO2022, /* Gn */
  &mb_encmap_UTF8,
  &mb_encmap_UTF16,
  &mb_encmap_UTF16BE,
  &mb_encmap_UTF16LE,
  &mb_encmap_MOEINTERNAL,
#undef defces
#define defces(cesname, mapname) &mb_encmap_ ## mapname,
#include "mbcesdefs.h"
};

void
mb_update_encoder(unsigned int gl, unsigned int gr, mb_info_t *info)
{
  if (gr < mb_GN) {
    if (mb_encoder_map[gr]) {
      info->G.l = gl;

      if (gr > mb_Gn && info->G.r <= mb_Gn)
	info->GRB4 = info->G.r;
      else
	info->GRB4 = mb_GN;

      info->G.r = gr;
      info->encoder = mb_encoder_map[gr];
    }
  }
}

mb_wchar_t
mb_ctl_encoder(mb_wchar_t c, void *arg, mb_info_t *info)
{
  return MB_CTL_ENC(info->buf[info->b]);
}

mb_wchar_t
mb_escfun_sl01(mb_wchar_t c, void *arg, mb_info_t *info)
{
  mb_update_encoder(mb_G0 + 1 - (c & 1), info->G.r, info);
  return mb_notchar_continue;
}

mb_wchar_t
mb_escfun_cs_94x94(mb_wchar_t c, void *arg, mb_info_t *info)
{
  int gn = mb_G0 + ((c >> MB_DBC_FC_LEN) & MB_Gn_MASK);

  info->G.set[gn] = mb_94x94;
  info->G.fc[gn] = (c & MB_DBC_FC_MASK) + MB_ESC_FC_BASE;
  return mb_notchar_continue;
}

mb_wchar_t
mb_escfun_cs_misc(mb_wchar_t c, void *arg, mb_info_t *info)
{
  mb_update_encoder(info->G.l, (c & 0xFF) + mb_MOEINTERNAL, info);
  return mb_notchar_continue;
}

mb_wchar_t
mb_escfun_cs_return(mb_wchar_t c, void *arg, mb_info_t *info)
{
  mb_update_encoder(info->G.l, info->GRB4, info);
  return mb_notchar_continue;
}

mb_wchar_t
mb_escfun_cs_utf8(mb_wchar_t c, void *arg, mb_info_t *info)
{
  mb_update_encoder(info->G.l, mb_UTF8, info);
  return mb_notchar_continue;
}

mb_wchar_t
mb_escfun_cs_utf16(mb_wchar_t c, void *arg, mb_info_t *info)
{
  mb_update_encoder(info->G.l, mb_UTF16, info);
  return mb_notchar_continue;
}

mb_wchar_t
mb_escfun_cs_rev(mb_wchar_t c, void *arg, mb_info_t *info)
{
  return mb_notchar_continue;
}

mb_wchar_t
mb_escfun_cs_94(mb_wchar_t c, void *arg, mb_info_t *info)
{
  int gn = mb_G0 + ((c / (0x7E - 0x40 + 1)) & MB_Gn_MASK);

  info->G.set[gn] = mb_94;
  info->G.fc[gn] = c % (0x7E - 0x40 + 1) + MB_ESC_FC_BASE;
  return mb_notchar_continue;
}

mb_wchar_t
mb_escfun_cs_94_i(mb_wchar_t c, void *arg, mb_info_t *info)
{
  int gn = mb_G0 + ((c / (0x7E - 0x40 + 1)) & MB_Gn_MASK);

  info->G.set[gn] = mb_94;
  info->G.fc[gn] = c % (0x7E - 0x40 + 1) + (1U << MB_ESC_FC_LEN) + MB_ESC_FC_BASE;
  return mb_notchar_continue;
}

mb_wchar_t
mb_escfun_cs_96(mb_wchar_t c, void *arg, mb_info_t *info)
{
  int gn = mb_G1 + ((c / (0x7E - 0x40 + 1)) & MB_Gn_MASK);

  info->G.set[gn] = mb_96;
  info->G.fc[gn] = c % (0x7E - 0x40 + 1) + MB_ESC_FC_BASE;
  return mb_notchar_continue;
}

mb_wchar_t
mb_escfun_sl23(mb_wchar_t c, void *arg, mb_info_t *info)
{
  mb_update_encoder(mb_G2 + 1 - (c & 1), info->G.r, info);
  return mb_notchar_continue;
}

mb_wchar_t
mb_escfun_sr(mb_wchar_t c, void *arg, mb_info_t *info)
{
  mb_update_encoder(info->G.l, mb_G1 + 2 - (c % 3), info);
  return mb_notchar_continue;
}

mb_wchar_t
mb_utf16_encoder(mb_wchar_t c1, void *arg, mb_info_t *info)
{
  int c2;
  mb_wchar_t wc;

  if ((c2 = mb_call_getc(info)) == EOF)
    goto is_eof;

  if ((wc = (c1 << 8) | c2) == 0xFFFE) {
    mb_update_encoder(mb_Gn, mb_UTF16LE, info);
    wc = 0xFEFF;
  }
  else if (wc >= 0xD800 && wc <= 0xDBFF) {
    if ((c1 = mb_call_getc(info)) == EOF)
      goto is_eof;
    else if (c1 < 0xDC || c1 > 0xDF)
      goto is_bad;

    if ((c2 = mb_call_getc(info)) == EOF)
      goto is_eof;

    wc = (((wc - 0xD800) << 10) | ((c1 << 8 | c2) - 0xDC00)) + 0x10000;
  }

  return wc;
is_eof:
  return mb_notchar_enc_short;
is_bad:
  return mb_notchar_enc_invalid;
}

mb_wchar_t
mb_utf16le_encoder(mb_wchar_t c1, void *arg, mb_info_t *info)
{
  int c2;
  mb_wchar_t wc;

  if ((c2 = mb_call_getc(info)) == EOF)
    goto is_eof;

  if ((wc = (c2 << 8) | c1) == 0xFFFE) {
    mb_update_encoder(mb_Gn, mb_UTF16, info);
    wc = 0xFEFF;
  }
  else if (wc >= 0xD800 && wc <= 0xDBFF) {
    if ((c1 = mb_call_getc(info)) == EOF)
      goto is_eof;

    if ((c2 = mb_call_getc(info)) == EOF)
      goto is_eof;
    else if (c2 < 0xDC || c2 > 0xDF)
      goto is_bad;

    wc = (((wc - 0xD800) << 10) | ((c2 << 8 | c1) - 0xDC00)) + 0x10000;
  }

  return wc;
is_eof:
  return mb_notchar_enc_short;
is_bad:
  return mb_notchar_enc_invalid;
}

size_t
mb_apply_convv(mb_wchar_t *ws, mb_wchar_t *ws_end, mb_ws_conv_t *v, mb_info_t *info)
{
  size_t n = 0;

  if (v)
    for (; *v ; ++v)
      n += (*v)(ws, ws_end, info);

  return n;
}

mb_wchar_t
mb_encode(mb_info_t *info, int flag, void **p_to, void *to_end)
{
  int c;
  mb_wchar_t wc;
  void *to;

  to = *p_to;
loop:
  info->b = info->i;

  if ((c = mb_call_getc(info)) == EOF)
    wc = mb_notchar_eof;
  else {
    if (info->encoder->iv[c] >= 0) {
      mb_char_node_t *node;
      mb_wchar_range_t *wcv0;
      mb_encoder_func_t func;
      void *arg;
      mb_char_node_t *v[MB_ESC_LEN_MAX + MB_MBC_LEN_MAX], *nv;
      size_t n;
      ptrdiff_t b, e, i;

      v[0] = node = info->encoder->nv[info->encoder->iv[c]].node;
      n = 1;
      wcv0 = info->encoder->nv[info->encoder->iv[c]].wcv;
      func = node->func;
      arg = node->arg;
    descend:
      if (!(nv = node->next.v)) {
	i = node->next.e;

	for (wc = 0 ; n > 0 ;) {
	  node = v[--n];
	  wc += node->wcv[i].wchar_min + ((unsigned char)info->buf[info->b + n] - node->char_min) * node->wcv[i].n;
	}

	wc += wcv0[i].wchar_min;

	if (func)
	  switch (wc = func(wc, arg, info)) {
	  case mb_notchar_continue:
	    goto loop;
	  case mb_notchar_enc_invalid:
	    goto enc_invalid;
	  case mb_notchar_enc_short:
	    goto enc_short;
	  default:
	    break;
	  }
      found:
	if (flag & MB_ENCODE_TO_WS) {
	  if ((mb_wchar_t *)to < (mb_wchar_t *)to_end) {
	    *(mb_wchar_t *)to = wc;

	    if ((mb_wchar_t *)(to = ((mb_wchar_t *)to) + 1) == (mb_wchar_t *)to_end) {
	      info->b = info->i;
	      goto end;
	    }
	  }
	  else {
	    wc = mb_notchar_continue;
	    info->i = info->b;
	    goto end;
	  }
	}
	else if (!(wc & ~0x7F)) {
	  if (to) {
	    if ((char *)to < (char *)to_end) {
	      *(char *)to = wc;

	      if ((char *)(to = ((char *)to) + 1) == (char *)to_end) {
		info->b = info->i;
		goto end;
	      }
	    }
	    else {
	      wc = mb_notchar_continue;
	      info->i = info->b;
	      goto end;
	    }
	  }
	}
	else if (to) {
	  if ((char *)to_end - (char *)to >= MB_MBC_LEN_MAX) {
	    mb_wchar_to_mbc(wc, (char *)to);

	    if ((char *)(to = ((char *)to) + MB_MBC_LEN_MAX) == (char *)to_end) {
	      info->b = info->i;
	      goto end;
	    }
	  }
	  else {
	    wc = mb_notchar_continue;
	    info->i = info->b;
	    goto end;
	  }
	}

	goto loop;
      }

      if ((c = mb_call_getc(info)) == EOF) {
      enc_short:
	if (flag & MB_ENCODE_SKIP_SHORT) {
	  wc = MB_CTL_ENC(info->buf[info->b]);
	  info->i = info->b + 1;
	  goto found;
	}

	wc = mb_notchar_enc_short;
	info->i = info->b;
	goto end;
      }

      for (b = 0, e = node->next.e ; b < e ;) {
	node = &nv[i = (b + e) / 2];

	if (c < node->char_min)
	  e = i;
	else if (c > node->char_max)
	  b = i + 1;
	else {
	  v[n++] = node;

	  if (node->func) {
	    func = node->func;
	    arg = node->arg;
	  }

	  goto descend;
	}
      }
    }
  enc_invalid:
    if (flag & MB_ENCODE_SKIP_INVALID) {
      wc = MB_CTL_ENC(info->buf[info->b]);
      info->i = info->b + 1;
      goto found;
    }
      
    wc = mb_notchar_enc_invalid;
    info->i = info->b;
  }
end:
  *p_to = to;
  return wc;
}

mb_wchar_t
mb_encode_to_wchar(mb_info_t *info)
MB_ENCODE_TO_WCHAR

mb_wchar_t
mb_fetch_wchar(mb_info_t *info)
MB_FETCH_WCHAR

static int
mb_fetch_char(mb_info_t *info)
{
  mb_wchar_t wc;
  int c;

  if (info->aux_n)
    c = (unsigned char)info->auxbuf[(info->aux_i)++];
  else if ((wc = mb_fetch_wchar(info)) == mb_notchar_eof)
    c = EOF;
  else {
    info->aux_n = mb_wchar_to_mbc(wc, info->auxbuf);
    c = (unsigned char)info->auxbuf[0];
  }

  return c;
}

int
mb_unfetch_char(int c, mb_info_t *info)
{
  if (c != EOF) {
    if (info->aux_i > 0)
      info->auxbuf[--(info->aux_i)] = c;
    else {
      char temp[1];

      temp[0] = c;
      mb_fill_inbuf(info, temp, 1);
    }
  }

  return c;
}

int
mb_getc(mb_info_t *info)
{
  if (info->aux_i < info->aux_n)
    return (unsigned char)info->auxbuf[(info->aux_i)++];
  else
    return mb_fetch_char(info);
}

size_t
mb_getmbc(char *d, mb_info_t *info)
{
  size_t i;
  size_t n;

  i = info->aux_i;
  n = info->aux_n;

  if (i < n) {
    memcpy(d, &info->auxbuf[i], n - i);
    info->aux_i = n;
    return n - i;
  }
  else {
    mb_wchar_t wc;

    if ((wc = mb_fetch_char(info)) == mb_notchar_eof)
      return 0;
    else
      return mb_wchar_to_mbc(wc, d);
  }
}

size_t
mb_getmem(char *d, size_t n, mb_info_t *info)
{
  size_t i;

  for (i = 0 ; i < n && info->aux_i < info->aux_n ;)
    d[i++] = info->auxbuf[(info->aux_i)++];

  if (i < n) {
    void *to;

    info->aux_i = info->aux_n = 0;
    to = d + i;
    mb_encode(info, MB_ENCODE_SKIP_INVALID | MB_ENCODE_SKIP_SHORT, &to, d + n);
    i = (char *)to - d;
  }

  return i;
}

char *
mb_getline(char *d, int n, mb_info_t *info)
{
  if (n > 0) {
    size_t i;
    int c;

    for (--n, i = 0 ; i < n && info->aux_i < info->aux_n ;)
      if ((c = info->auxbuf[(info->aux_i)++]) == '\n')
	goto end;
      else
	d[i++] = c;

    if (i < n) {
      info->aux_i = info->aux_n = 0;

      do {
	if ((c = mb_fetch_char(info)) == EOF)
	  goto end;

	if (info->aux_i < info->aux_n) {
	  if (i + info->aux_n > n) {
	    mb_unfetch_char(c, info);
	    goto end;
	  }

	  memcpy(&d[i], info->auxbuf, info->aux_n);
	  i += info->aux_n;
	}
	else {
	  d[i++] = c;

	  if (c == '\n')
	    goto end;
	}
      } while (i < n);
    }
  end:
    d[i++] = '\0';
  }

  return d;
}

char *
mb_info2mb(mb_info_t *info, size_t n, size_t *p_end)
{
  char *d;

  if (!n)
    n = MB_MBC_LEN_MAX;

  if ((d = alt_call_malloc_atomic(n))) {
    size_t end, size;
    mb_wchar_t wc;
    void *to;

    for (end = 0, size = n ;;) {
      to = &d[end];
      wc = mb_encode(info, MB_ENCODE_SKIP_INVALID | MB_ENCODE_SKIP_SHORT, &to, d + size);
      end = (char *)to - d;

      switch (wc) {
      case mb_notchar_eof:
	if (p_end)
	  *p_end = end;

	d[end] = '\0';
	goto eof_found;
      default:
	if (end + 1 + MB_MBC_LEN_MAX >= size) {
	  size_t nsize = ((end + 1 + MB_MBC_LEN_MAX) / MB_MEM_DELTA + 1) * MB_MEM_DELTA;

	  if (!(d = alt_call_realloc(d, nsize)))
	    goto eof_found; 

	  size = nsize;
	}
      }
    }
  }
eof_found:
  return d;
}

void
mb_vmem2mb_setup(mb_info_t *info, const char *s, size_t n, mb_setup_t *setup, const char *op, va_list ap)
{
  mb_vinit_r(info, NULL, NULL, setup, op, ap);
  info->buf = (char *)s;
  info->e = info->size = n;
}

void
mb_mem2mb_setup(mb_info_t *info, const char *s, size_t n, mb_setup_t *setup, const char *op, ...)
{
  va_list ap;

  va_start(ap, op);
  mb_vmem2mb_setup(info, s, n, setup, op, ap);
  va_end(ap);
}

char *
mb_vmem2mb(const char *s, size_t n, size_t *p_end, mb_setup_t *setup, const char *op, va_list ap)
{
  mb_info_t info = {};

  mb_vmem2mb_setup(&info, s, n, setup, op, ap);
  return mb_info2mb(&info, n, p_end);
}

char *
mb_mem2mb(const char *s, size_t n, size_t *p_end, mb_setup_t *setup, const char *op, ...)
{
  va_list ap;
  char *mb;

  va_start(ap, op);
  mb = mb_vmem2mb(s, n, p_end, setup, op, ap);
  va_end(ap);
  return mb;
}

char *
mb_vstr2mb(const char *s, size_t *p_end, mb_setup_t *setup, const char *op, va_list ap)
{
  return mb_vmem2mb(s, strlen(s) + 1, p_end, setup, op, ap);
}

char *
mb_str2mb(const char *s, size_t *p_end, mb_setup_t *setup, const char *op, ...)
{
  va_list ap;
  char *mb;

  va_start(ap, op);
  mb = mb_vstr2mb(s, p_end, setup, op, ap);
  va_end(ap);
  return mb;
}

void
mb_mkunbound_cs_detector(mb_cs_detector_t *p)
{
  p->orig->io_func.in = p->copy.io_func.in;
  p->orig->io_arg = p->copy.io_arg;
  p->orig->flag &= ~MB_FLAG_CS_DETECTING;

  if (p->free_bag && p->copy.buf)
    p->free_bag(p->copy.buf);

  if (p->free_detector)
    p->free_detector(p);
}

#ifndef DETECT_BUFSIZ
#define DETECT_BUFSIZ (BUFSIZ)
#endif

#ifndef DETECT_MINSIZ
#define DETECT_MINSIZ (DETECT_BUFSIZ)
#endif

#ifndef DETECT_MINBYENCODE_FACTOR
#define DETECT_MINBYENCODE_FACTOR (16)
#endif

#ifndef DETECT_MINBYCHAR_FACTOR
#define DETECT_MINBYCHAR_FACTOR (4)
#endif

size_t
mb_cs_detector_find_best(mb_cs_detector_t *p, size_t *p_same)
{
  long minbyencode, minbychar;
  size_t i, j, k, l, same, near;

  if ((minbyencode = p->processed / DETECT_MINBYENCODE_FACTOR) < DETECT_MINSIZ / DETECT_MINBYENCODE_FACTOR)
    minbyencode = DETECT_MINSIZ / DETECT_MINBYENCODE_FACTOR;

  if ((minbychar = p->processed / DETECT_MINBYCHAR_FACTOR) < DETECT_MINSIZ / DETECT_MINBYCHAR_FACTOR)
    minbychar = DETECT_MINSIZ / DETECT_MINBYCHAR_FACTOR;

  for (i = same = near = 0, j = 1 ; j < p->nstats ; ++j) {
    l = j;
    if (p->stat[i].by_encode > p->stat[l].by_encode)
      goto i_gt_l;
    else if (p->stat[i].by_encode < p->stat[l].by_encode)
      goto i_lt_l;
    else if (p->stat[i].by_char > p->stat[l].by_char)
      goto i_gt_l;
    else if (p->stat[i].by_char == p->stat[l].by_char) {
      if (near)
	near = same = 0;

      p->samev[same++] = l;
      continue;
    }
  i_lt_l:
    k = i;
    i = l;
    l = k;
    same = 0;
  i_gt_l:
    if (p->stat[i].by_encode - p->stat[l].by_encode > minbyencode ||
	p->stat[i].by_char - p->stat[l].by_char > minbychar) {
      if (near) {
	k = p->samev[0];

	if (p->stat[l].by_encode > p->stat[k].by_encode ||
	    (p->stat[l].by_encode == p->stat[k].by_encode &&
	     p->stat[l].by_char > p->stat[k].by_char))
	  p->samev[0] = l;
      }
      else if (!same) {
	p->samev[0] = l;
	near = same = 1;
      }
    }
  }

  *p_same = same;
  return i;
}

static unsigned int default_wc_weight[] = {
#include "mbwcweight.h"
};

static unsigned int *wc_weight = default_wc_weight;

unsigned int *
mb_set_wchar_weight_tab(unsigned int *new)
{
  if (new) {
    unsigned int *old = wc_weight;

    wc_weight = new;
    return old;
  }
  else
    return wc_weight;
}

void
mb_cs_try_detect(mb_cs_detector_t *p)
{
  size_t j;

  for (j = 0 ; j < p->nstats ; ++j) {
    mb_info_t info;
    mb_wchar_t wc, wcv[DETECT_BUFSIZ], *wp, *ewp;
    void *to;
    unsigned int wt;
    size_t b;

    memset(&info, 0, sizeof(info));
    mb_setup_by_ces(p->stat[j].ces, &info);
    info.flag |= MB_FLAG_DONTFLUSH_BUFFER;
    info.buf = &p->copy.buf[p->stat[j].processed];
    info.e = info.size = p->copy.e - p->stat[j].processed;

    for (;;) {
      to = wcv;
      b = info.i = info.b;
      wc = mb_encode(&info, MB_ENCODE_TO_WS, &to, &wcv[sizeof(wcv) / sizeof(wcv[0])]);
      p->stat[j].by_char += info.b - b;

      for (wp = wcv, ewp = to ; wp < ewp ; ++wp)
	if (bt_search(*wp, wc_weight, &wt) != bt_failure)
	  p->stat[j].by_char += wt;

      switch (wc) {
      case mb_notchar_enc_short:
	if (!info.b) {
	  ++(p->stat[j].processed);
	  --(p->stat[j].by_encode);
	  goto next;
	}
      case mb_notchar_eof:
	p->stat[j].processed += info.b;
	goto next;
      case mb_notchar_enc_invalid:
	--(p->stat[j].by_encode);
	info.i = ++(info.b);
	break;
      }
    }
  next:
    if (p->processed < p->stat[j].processed)
      p->processed = p->stat[j].processed;
  }
}

static size_t
mb_cs_detector_read(char *dest, size_t n, void *ap)
{
  mb_cs_detector_t *p;

  p = ap;

  if (p->copy.b < p->copy.e) {
    if (n > p->copy.e - p->copy.b)
      n = p->copy.e - p->copy.b;

    memcpy(dest, &p->copy.buf[p->copy.b], n);

    if ((p->copy.b += n) >= p->copy.e && p->flag & MB_CS_DETECT_FLAG_MKUNBOUND)
      mb_mkunbound_cs_detector(p);

    return n;
  }
  else
    return p->copy.io_func.in(dest, n, p->copy.io_arg);
}

void
mb_restore_G(mb_info_t *info, mb_G_t *G, mb_G_t *Gsave)
{
  size_t i;

  if (G->l != Gsave->l)
    info->G.l = G->l;

  if (G->r != Gsave->r)
    info->G.r = G->r;

  for (i = sizeof(G->set) / sizeof(G->set[0]) ; i > 0 ;) {
    --i;

    if (G->set[i] != Gsave->set[i])
      info->G.set[i] = G->set[i];

    if (G->fc[i] != Gsave->fc[i])
      info->G.fc[i] = G->fc[i];
  }
}

void
mb_setup_by_detected_ces(mb_ces_t *ces, mb_info_t *info)
{
  mb_G_t G, Gsave;

  if ((info->flag & (MB_FLAG_CS_DETECTING | MB_FLAG_UNKNOWNCS)) == (MB_FLAG_CS_DETECTING | MB_FLAG_UNKNOWNCS)) {
    mb_cs_detector_t *p;

    p = info->io_arg;
    G = p->copy.G;
    Gsave = p->copy.Gsave;
  }
  else {
    G = info->G;
    Gsave = info->Gsave;
  }

  mb_setup_by_ces(ces, info);
  mb_restore_G(info, &G, &Gsave);
}

mb_wchar_t
mb_cs_detect_encode(mb_info_t *info, int flag, void **p_to, void *to_end)
{
  if ((info->flag & (MB_FLAG_CS_DETECTING | MB_FLAG_UNKNOWNCS)) == (MB_FLAG_CS_DETECTING | MB_FLAG_UNKNOWNCS)) {
    mb_cs_detector_t *p;
    mb_wchar_t wc;
    void *to;
    size_t m, nsize, same, i;
    char *nbuf;

    p = info->io_arg;
    to = *p_to;

    switch (wc = mb_encode(&p->copy, flag & ~(MB_ENCODE_SKIP_INVALID | MB_ENCODE_SKIP_SHORT), &to, to_end)) {
    case mb_notchar_enc_invalid:
    case mb_notchar_enc_short:
      if (to > *p_to)
	goto end;

      if (!(p->copy.flag & MB_FLAG_DONTFLUSH_BUFFER) && p->copy.b) {
	if ((p->copy.e -= p->copy.b) > 0)
	  memmove(p->copy.buf, &p->copy.buf[p->copy.b], p->copy.e);

	p->copy.i = p->copy.b = 0;
      }

      for (;;) {
	while (p->copy.e < p->copy.size && p->copy.io_func.in &&
	       (m = p->copy.io_func.in(&p->copy.buf[p->copy.e], p->copy.size - p->copy.e, p->copy.io_arg)))
	  p->copy.e += m;

	if (p->copy.e - p->copy.b >= DETECT_MINSIZ) {
	  mb_cs_try_detect(p);
	  i = mb_cs_detector_find_best(p, &same);

	  if (!same)
	    goto found;
	}

	if (p->copy.e < p->copy.size || (p->limit && p->copy.size >= p->limit) || !p->realloc_bag)
	  break;

	nsize = (p->copy.size / 2 + 1) * 3;

	if (p->limit && nsize > p->limit)
	  nsize = p->limit;

	if (!(nbuf = p->realloc_bag(p->copy.buf, nsize)))
	  goto end;

	p->copy.buf = nbuf;
	p->copy.size = nsize;
      }

      mb_cs_try_detect(p);
      i = mb_cs_detector_find_best(p, &same);
    found:
      mb_setup_by_detected_ces(p->stat[i].ces, p->orig);
      p->orig->flag &= ~MB_FLAG_UNKNOWNCS;
      break;
    default:
    end:
      *p_to = to;
      return wc;
    }
  }

  return mb_encode(info, flag, p_to, to_end);
}

void
mb_bind_cs_detector(mb_cs_detector_t *p, mb_info_t *info)
{
  size_t i;

  p->orig = info;
  mb_setup_by_ces(info->ces, &p->copy);
  info->flag |= (MB_FLAG_CS_DETECTING | MB_FLAG_UNKNOWNCS);
  p->copy.flag = info->flag;
  p->copy.flag &= ~(MB_FLAG_CS_DETECTING | MB_FLAG_UNKNOWNCS);

  if ((p->copy.io_func.in = info->io_func.in)) {
    info->io_func.in = mb_cs_detector_read;
    p->copy.e = 0;
  }
  else {
    p->copy.buf = info->buf;
    p->copy.size = info->size;
    p->copy.e = info->e;
  }

  p->copy.io_arg = info->io_arg;
  info->io_arg = p;
  p->copy.b = p->copy.i = p->processed = 0;

  for (i = 0 ; i < sizeof(p->stat) / sizeof(p->stat[0]) ; ++i) {
    p->stat[i].processed = 0;
    p->stat[i].by_encode = p->stat[i].by_char = 0;
  }
}

mb_cs_detector_t *
mb_alloc_cs_detector(mb_info_t *info, size_t init, size_t limit)
{
  mb_cs_detector_t *p = NULL;

  if (!info->io_func.in) {
    if ((p = alt_call_malloc(sizeof(mb_cs_detector_t)))) {
      p->copy.buf = NULL;
      p->realloc_bag = NULL;
      p->free_bag = NULL;
    }
  }
  else if (!limit || init < limit) {
    char *bag = alt_call_malloc_atomic(init);

    if (bag) {
      if ((p = alt_call_malloc(sizeof(mb_cs_detector_t)))) {
	p->copy.buf = bag;
	p->realloc_bag = alt_realloc;
	p->free_bag = alt_free;
      }
      else
	alt_call_free(bag);
    }
  }
  else if ((p = alt_call_malloc(sizeof(mb_cs_detector_t) + init))) {
    p->copy.buf = (char *)p + sizeof(mb_cs_detector_t);
    p->realloc_bag = NULL;
    p->free_bag = NULL;
  }

  if (p) {
    p->limit = limit;
    p->copy.size = init;
    p->copy.i = p->copy.b = p->copy.e = 0;
    p->free_detector = alt_free;
    mb_bind_cs_detector(p, info);
  }

  return p;
}
