#include <stddef.h>
#include <errno.h>
#include "mb.h"

mb_wchar_t
mb_mem_to_wchar(const char *s, size_t *p_b, size_t *p_e)
{
  size_t b;
  size_t e;

  b = *p_b;
  e = *p_e;

  if (b < e) {
    mb_wchar_t c;
    int clen;

    if (((unsigned char)s[b] & 0xC0) == 0x80) {
      size_t i;

      for (i = 1 ; i < MB_MBC_LEN_MAX ; ++i) {
	if (b < i)
	  break;

	switch ((unsigned char)s[b - i] & 0xC0) {
	case 0x80:
	  break;
	case 0xC0:
	  b -= i;
	  goto first_octet;
	default:
	  goto sbc;
	}
      }

      goto sbc;
    }
  first_octet:
    if ((clen = mb_mem_to_wchar_internal(&s[b], e - b, c)) > 0) {
      *p_b = b;
      *p_e = b + clen;
      return c;
    }

    b = *p_b;
  sbc:
    *p_e = b + 1;
    return MB_CTL_ENC((unsigned char)(s)[b]);
  }

  return mb_notchar_eof;
}

mb_wchar_t
mb_str_to_wchar(const char **p_s)
{
  mb_wchar_t wc;

  *p_s += mb_str_to_wchar_internal(*p_s, wc); 
  return wc;
}

const char *
mb_mem_to_wstr(const char *from, const char *from_end, mb_wchar_t **p_to, mb_wchar_t *to_end)
{
  int cn;
  mb_wchar_t *to;

  for (to = *p_to ; from < from_end && to < to_end ; ++to)
    if ((cn = mb_mem_to_wchar_internal(from, from_end - from, *to)) > 0)
      from += cn;
    else
      ++from;

  *p_to = to;
  return from;
}

const char *
mb_str_to_wstr(const char *from, mb_wchar_t **p_to, mb_wchar_t *to_end)
{
  int cn;
  mb_wchar_t *to;

  for (to = *p_to ; *from && to < to_end ; ++to)
    if ((cn = mb_str_to_wchar_internal(from, *to)) > 0)
      from += cn;
    else
      ++from;

  *p_to = to;
  return from;
}

/* made from EastAsianWidth.txt (Ambiguous mapped to Narrow)*/
static int eaw_a2n_ucswidth[] = {
#include "eaw_a2n_ucswidth.h"
};

/* made from EastAsianWidth.txt (Ambiguous mapped to Wide)*/
static int eaw_a2w_ucswidth[] = {
#include "eaw_a2w_ucswidth.h"
};

/* taken from xterm(-146)/wcwidth.c::my_wcwidth() */
static int xterm_ucswidth[] = {
#include "xterm_ucswidth.h"
};

static int *ucswidth = NULL;

static btri_string_tab_t default_term_tab[] = {
#include "mbtermtab.h"
};

static btri_string_tab_t *term_tab = default_term_tab;

void
mb_set_widthtable(char *term)
{
  void *p = NULL;

  if (!term) term = getenv("TERM");
  if (term) btri_fast_ci_search_str(term, term_tab, &p);
  ucswidth = p ? p : xterm_ucswidth;
}

size_t
mb_ucs_width(mb_wchar_t wc)
{
  unsigned int width;

  if (!ucswidth)
    mb_set_widthtable(NULL);

  return bt_search(wc, ucswidth, &width) != bt_failure ? width : 1;
}

size_t
mb_mem_width(const char *s, size_t n)
{
  size_t i, b, e, len;
  mb_wchar_t wc;

  for (len = i = 0 ; i < n ;) {
    b = 0;
    e = n - i;
    wc = mb_mem_to_wchar(&s[i], &b, &e);
    len += MB_WCHAR_WIDTH(wc);
    i += e;
  }

  return len;
}

size_t
mb_str_width(const char *s)
{
  size_t len;
  int cn;
  mb_wchar_t wc;

  for (len = 0 ; *s ;) {
    cn = mb_str_to_wchar_internal(s, wc);
    s += cn > 0 ? cn : 1;
    len += MB_WCHAR_WIDTH(wc);
  }

  return len;
}

size_t
mb_str_width_n(const char *s, size_t n)
{
  size_t len;
  int cn;
  mb_wchar_t wc;

  for (len = 0 ; n > 0 && *s ;) {
    cn = mb_str_to_wchar_internal(s, wc);
    if (!(cn > 0)) cn = 1;
    if (n < cn) break;
    n -= cn;
    s += cn;
    len += MB_WCHAR_WIDTH(wc);
  }

  return len;
}

size_t
mb_wmem_width(const mb_wchar_t *s, size_t n)
{
  size_t len, i;
  mb_wchar_t wc;

  for (len = i = 0 ; i < n ;) {
    wc = s[i++];
    len += MB_WCHAR_WIDTH(wc);
  }

  return len;
}

size_t
mb_wstr_width(const mb_wchar_t *s)
{
  size_t len;
  mb_wchar_t wc;

  for (len = 0 ; *s ;) {
    wc = *s++;
    len += MB_WCHAR_WIDTH(wc);
  }

  return len;
}

size_t
mb_wstr_width_n(const mb_wchar_t *s, size_t n)
{
  size_t len;
  mb_wchar_t wc;

  for (len = 0 ; n > 0 && *s ;) {
    --n;
    wc = *s++;
    len += MB_WCHAR_WIDTH(wc);
  }

  return len;
}

static unsigned int default_prop_tab[] = {
#include "prop.h"
};

static unsigned int *prop_tab = default_prop_tab;

unsigned int *
mb_set_prop_tab(unsigned int *new)
{
  if (new) {
    unsigned int *old = prop_tab;

    prop_tab = new;
    return old;
  }
  else
    return prop_tab;
}

int
mb_wchar_prop(mb_wchar_t wc)
{
  unsigned int res;

  if (bt_search(wc, prop_tab, &res) == bt_failure)
    res = 0;

  return res;
}

int
mb_mem_to_prop(const char *s, size_t e)
{
  size_t b = 0;

  return mb_wchar_prop(mb_mem_to_wchar(s, &b, &e));
}

int
mb_str_to_prop(const char *s)
{
  mb_wchar_t wc;

  mb_str_to_wchar_internal(s, wc);
  return mb_wchar_prop(wc);
}
