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

void
mb_setup_by_ces(mb_ces_t *p, mb_info_t *info)
{
  switch (p->flag_op) {
  case mb_flag_set:
    info->flag = p->flag;
    break;
  case mb_flag_minus:
    info->flag &= ~(p->flag);
    break;
  default:
    info->flag |= p->flag;
  }

  info->G = info->Gsave = p->G;
  info->GRB4 = mb_GN;
  info->ces = p;
  mb_update_encoder(info->G.l, info->G.r, info);
}

static btri_string_tab_t default_ces_tab[] = {
#include "mbcestab.h"
};

static btri_string_tab_t *ces_tab = default_ces_tab;

btri_string_tab_t *
mb_set_ces_tab(btri_string_tab_t *new)
{
  btri_string_tab_t *old = ces_tab;

  if (new)
    ces_tab = new;

  return old;
}

void
mb_ces_by_name(const char *name, mb_info_t *info)
{
  mb_ces_t *p = NULL;

  info->flag |= MB_FLAG_UNKNOWNCS;

  if (name && *name) {
    void *res;

    if (btri_fast_ci_search_str(name, ces_tab, &res) != bt_failure) {
      p = res;
      info->flag &= ~MB_FLAG_UNKNOWNCS;
    }
  }

  if (!p)
    p = &mb_ces_ASCII;

  mb_setup_by_ces(p, info);
}

void
mb_vsetsetup(mb_setup_t *setup, const char *op, va_list ap)
{
  while (*op)
    switch ((unsigned char)*op++) {
    case '=':
      setup->flag_op = mb_flag_set;
      setup->flag = va_arg(ap, int);
      break;
    case '|':
      setup->flag_op = mb_flag_plus;
      setup->flag = va_arg(ap, int);
      break;
    case '-':
      setup->flag_op = mb_flag_minus;
      setup->flag = va_arg(ap, int);
      break;
    case '@':
      setup->cs = va_arg(ap, const char *);
      break;
    case '!':
      *setup = *(va_arg(ap, mb_setup_t *));
    default:
      break;
    }
}

void
mb_setsetup(mb_setup_t *setup, const char *op, ...)
{
  va_list ap;

  va_start(ap, op);
  mb_vsetsetup(setup, op, ap);
  va_end(ap);
}

void
mb_vsetup(mb_info_t *info, mb_setup_t *dflt, const char *op, va_list ap)
{
  mb_setup_t setup = {};

  if (dflt)
    setup = *dflt;

  mb_vsetsetup(&setup, op, ap);

  if (setup.cs)
    mb_ces_by_name(setup.cs, info);

  switch (setup.flag_op) {
  case mb_flag_set:
    info->flag = setup.flag;
    break;
  case mb_flag_plus:
    info->flag |= setup.flag;
    break;
  case mb_flag_minus:
    info->flag &= setup.flag;
  default:
    break;
  }
}

void
mb_setup(mb_info_t *info, mb_setup_t *dflt, const char *op, ...)
{
  va_list ap;

  va_start(ap, op);
  mb_vsetup(info, dflt, op, ap);
  va_end(ap);
}

void
mb_vinit(mb_info_t *info, void *arg, mb_setup_t *dflt, const char *op, va_list ap)
{
  info->flag = 0;
  mb_ces_by_name(NULL, info);
  info->io_arg = arg;
  info->aux_i = info->aux_n = info->b = info->i = info->e = 0;
  mb_vsetup(info, dflt, op, ap);
}

void
mb_init(mb_info_t *info, void *arg, mb_setup_t *dflt, const char *op, ...)
{
  va_list ap;

  va_start(ap, op);
  mb_vinit(info, arg, dflt, op, ap);
  va_end(ap);
}

void
mb_vinit_r(mb_info_t *info, void *arg, size_t (*func)(char *s, size_t, void *), mb_setup_t *dflt, const char *op, va_list ap)
{
  mb_vinit(info, arg, dflt, op, ap);
  info->io_func.in = func;
}

void
mb_init_r(mb_info_t *info, void *arg, size_t (*func)(char *s, size_t, void *), mb_setup_t *dflt, const char *op, ...)
{
  va_list ap;

  va_start(ap, op);
  mb_vinit_r(info, arg, func, dflt, op, ap);
  va_end(ap);
}

void
mb_vinit_w(mb_info_t *info, void *arg, size_t (*func)(const char *, size_t, void *), mb_setup_t *dflt, const char *op, va_list ap)
{
  mb_vinit(info, arg, dflt, op, ap);
  info->io_func.out = func;
}

void
mb_init_w(mb_info_t *info, void *arg, size_t (*func)(const char *, size_t, void *), mb_setup_t *dflt, const char *op, ...)
{
  va_list ap;

  va_start(ap, op);
  mb_vinit_w(info, arg, func, dflt, op, ap);
  va_end(ap);
}

static btri_string_tab_t default_conv_tab[] = {
#include "mbconvtab.h"
};

static btri_string_tab_t *conv_tab = default_conv_tab;

size_t
mb_namev_to_converterv(const char *s, mb_ws_conv_t *v, size_t max, void (*ehook)(const char *, size_t))
{
  void *p;
  size_t i, j;

  for (i = 0 ; i + 1 < max && *s ; s += j + 1) {
    j = strcspn(s, ",");

    if (btri_fast_ci_search_mem(s, j, conv_tab, &p) != bt_failure)
      v[i++] = p;
    else if (ehook)
      ehook(s, j);

    if (!s[j])
      break;
  }

  v[i] = NULL;
  return i;
}

static mb_flag_tab_t
mb_flag_28FOR94X94G0 = {MB_FLAG_28FOR94X94G0, ~0},
mb_flag_ASCIIATCTL = {MB_FLAG_ASCIIATCTL, ~0},
mb_flag_NOSSL = {MB_FLAG_NOSSL, ~0},
mb_flag_DISCARD_NOTPREFERED_CHAR = {MB_FLAG_DISCARD_NOTPREFERED_CHAR, ~0};

static btri_string_tab_t default_flag_tab[] = {
#include "mbflagtab.h"
};

static btri_string_tab_t *flag_tab = default_flag_tab;

int
mb_namev_to_flag(const char *s, int flag, void (*ehook)(const char *, size_t))
{
  void *p;
  size_t j;

  for (; *s ; s += j + 1) {
    j = strcspn(s, ",");

    if (btri_fast_ci_search_mem(s, j, flag_tab, &p) != bt_failure) {
      flag &= ((mb_flag_tab_t *)p)->mask;
      flag |= ((mb_flag_tab_t *)p)->value;
    }
    else if (ehook)
      ehook(s, j);

    if (!s[j])
      break;
  }

  return flag;
}


static mb_ces_t
#ifdef USE_CN
*mb_cesv_CN[] = {
&mb_ces_GBK2K,
&mb_ces_BIG5,
&mb_ces_UTF8,
&mb_ces_EUC_TW,
NULL,
},
#endif
#ifdef USE_JA
*mb_cesv_JA[] = {
&mb_ces_EUC_JP,
&mb_ces_SJIS,
&mb_ces_UTF8,
NULL,
},
#endif
#ifdef USE_KR
*mb_cesv_KR[] = {
&mb_ces_EUC_KR,
&mb_ces_UTF8,
&mb_ces_JOHAB,
&mb_ces_UHANG,
NULL,
},
#endif
*mb_cesv_CJK[] = {
&mb_ces_ISO8859_1,
&mb_ces_UTF8,
#ifdef USE_CN
&mb_ces_GBK2K,
&mb_ces_EUC_TW,
&mb_ces_BIG5,
#endif
#ifdef USE_JA
&mb_ces_EUC_JP,
&mb_ces_SJIS,
#endif
#ifdef USE_KR
&mb_ces_EUC_KR,
&mb_ces_JOHAB,
&mb_ces_UHANG,
#endif
NULL,
};

btri_string_tab_t default_detector_tab[] = {
#include "mbdetectortab.h"
};

static btri_string_tab_t *detector_tab = default_detector_tab;

int
mb_lang_to_detector(const char *s, mb_cs_detector_stat_t *statv, size_t *p_nstats)
{
  void *p;

  if (btri_fast_ci_search_str(s, detector_tab, &p) != bt_failure) {
    mb_ces_t **v;

    for (v = p ; *v ;)
      (statv++)->ces = *v++;

    *p_nstats = v - (mb_ces_t **)p;
    return 1;
  }
  else
    return 0;
}
