/*
 * uconv version 0.3
 * Feb 22, 1999 yoshidam
 * Jul 24, 1998 yoshidam
 * Jun 30, 1998 yoshidam
 *
 */

#include "ruby.h"
#include "rubyio.h"
#include <stdio.h>
#include "uconv.h"
#include "ustring.h"


static VALUE mUconv;

#if 0
static int
_u8tou2(unsigned char* in, UString* out)
{
  int i;
  unsigned short u = 0;
  int rest = 0;

  allocUString(out);
  for (i = 0; in[i] != '\0'; i++) {
    unsigned char c = in[i];
    if (c >= 128 && c < 192) {
      if (rest == 0)
	Raise(eException, "illegal UTF-8 sequence (%02x)", c);
      u = (u << 6) | (c & 63);
      rest--;
      if (rest == 0) {
	addUChar2(out, u & 0xff, u >> 8);
      }
    }
    else if (c < 128) {      /* 0b0nnnnnnn (7) */
      if (c == 0)
	Warn("input may not be UTF-8 text!");
      addUChar2(out, c, 0);
      rest = 0;
    }
    else if (c < 224) {      /* 0b110nnnnn (11) */
      rest = 1;
      u = c & 31;
    }
    else if (c < 240) {      /* 0b1110nnnn (16) */
      rest = 2;
      u = c & 15;
    }
    else {
      Raise(eException, "none-BMP char detected");
    }
  }

  return out->len;
}
#endif

/* Convert UTF-8 to UTF-16-LE (byte ordeer: 21) */
static int
_u8tou16(unsigned char* in, UString* out)
{
  int i;
  unsigned int u = 0;
  int rest = 0;

  allocUString(out);
  for (i = 0; in[i] != '\0'; i++) {
    unsigned char c = in[i];
    if (c >= 128 && c < 192) {
      if (rest == 0)
	Raise(eException, "illegal UTF-8 sequence (%02x)", c);
      u = (u << 6) | (c & 63);
      rest--;
      if (rest == 0) {
	if (u < 0x10000) {
	  addUChar2(out, u & 0xff, u >> 8);
	}
	else if (u < 0x110000) {
	  unsigned int high = ((u - 0x10000) >> 10) | 0xd800;
	  unsigned int low = (u & 1023) | 0xdc00;
	  addUChar4(out, high & 255, high >> 8, low & 255, low >> 8);
	}
	else {
	  Raise(eException, "none-UTF-16 char detected (%04x)", u);
	}
      }
    }
    else if (c < 128) {      /* 0b0nnnnnnn (7bit) */
      if (c == 0)
	Warn("input may not be UTF-8 text!");
      addUChar2(out, c, 0);
      rest = 0;
    }
    else if (c < 224) {      /* 0b110nnnnn (11bit) */
      rest = 1;
      u = c & 31;
    }
    else if (c < 240) {      /* 0b1110nnnn (16bit) */
      rest = 2;
      u = c & 15;
    }
    else if (c < 248) {      /* 0b11110nnn (21bit) */
      rest = 3;
      u = c & 7;
    }
    else {
      Raise(eException, "none-UTF-16 char detected");
    }
  }

  return out->len;
}

/* Convert UTF-8 to UCS-4-LE (byte ordeer: 4321) */
static int
_u8tou4(unsigned char* in, UString* out)
{
  int i;
  unsigned int u = 0;
  int rest = 0;

  allocUString(out);
  for (i = 0; in[i] != '\0'; i++) {
    unsigned char c = in[i];
    if (c >= 128 && c < 192) {
      if (rest == 0)
	Raise(eException, "illegal UTF-8 sequence (%02x)", c);
      u = (u << 6) | (c & 63);
      rest--;
      if (rest == 0) {
	addUChar4(out, u & 0xff, (u >> 8) & 0xff, (u >>16) & 0xff, u >> 24);
      }
    }
    else if (c < 128) {      /* 0b0nnnnnnn (7bit) */
      if (c == 0)
	Warn("input may not be UTF-8 text!");
      addUChar4(out, c, 0, 0, 0);
      rest = 0;
    }
    else if (c < 224) {      /* 0b110nnnnn (11bit) */
      rest = 1;
      u = c & 31;
    }
    else if (c < 240) {      /* 0b1110nnnn (16bit) */
      rest = 2;
      u = c & 15;
    }
    else if (c < 248) {      /* 0b11110nnn (21bit) */
      rest = 3;
      u = c & 7;
    }
    else if (c < 252) {      /* 0b111110nn (26bit) */
      rest = 4;
      u = c & 3;
    }
    else if (c < 254) {      /* 0b1111110n (31bit) */
      rest = 5;
      u = c & 1;
    }
    else {
      Raise(eException, "none-UTF char detected");
    }
  }

  return out->len;
}


#if 0
/* Convert UCS-2-LE (byte ordeer: 21) to UTF-8 */
static int
_u2tou8(unsigned char* in, int len, UString* out)
{
  int i;

  allocUString(out);

  if (len < 2) return 0;
  for (i = 0; i < len; i += 2) {
    unsigned short c = in[i] | (in[i+1] << 8);
    if (c == 0xfeff) {          /* byte order mark */
      continue;
    }
    else if (c < 128) {         /* 0x0000-0x00FF */
      addUChar(out, c);
    }
    else if (c < 2048) {        /* 0x0100-0x07FF */
      unsigned char b2 = c & 63;
      unsigned char b1 = c >> 6;
      addUChar2(out, b1 | 192, b2 | 128);
    }
    else {                      /* 0x0800-0xFFFF */
      unsigned char b3 = c & 63;
      unsigned char b2 = (c >> 6) & 63;
      unsigned char b1 = c >> 12;
      addUChar3(out, b1 | 224, b2 | 128, b3 | 128);
    }
  }

  return out->len;
}
#endif

/* Convert UTF-16-LE (byte ordeer: 21) to UTF-8 */
static int
_u16tou8(unsigned char* in, int len, UString* out)
{
  int i;

  allocUString(out);

  if (len < 2) return 0;
  for (i = 0; i < len; i += 2) {
    unsigned int c = in[i] | (in[i+1] << 8);
    if (c == 0xfeff) {          /* byte order mark */
      continue;
    }
    else if (c < 128) {         /* 0x0000-0x00FF */
      addUChar(out, c);
    }
    else if (c < 2048) {        /* 0x0100-0x07FF */
      unsigned char b2 = c & 63;
      unsigned char b1 = c >> 6;
      addUChar2(out, b1 | 192, b2 | 128);
    }
    else if (c >= 0xd800 && c <= 0xdbff) { /* high surrogate */
      unsigned int surrogate = 0;
      unsigned int low;
      unsigned char b1, b2, b3, b4;
      if (i + 4 > len) /* not enough length */
	Raise(eException, "invalid surrogate detected");
      low = in[i+2] | (in[i+3] << 8);
      if (low < 0xdc00 || low > 0xdfff) /* not low surrogate */
	Raise(eException, "invalid surrogate detected");
      c = (((c & 1023)) << 10 | (low & 1023)) + 0x10000;
      b4 = c & 63;
      b3 = (c >> 6) & 63;
      b2 = (c >> 12) & 63;
      b1 = c >> 18;
      addUChar4(out, b1 | 240, b2 | 128, b3 | 128, b4 | 128);
      i += 2;
    }
    else {                      /* 0x0800-0xFFFF */
      unsigned char b3 = c & 63;
      unsigned char b2 = (c >> 6) & 63;
      unsigned char b1 = c >> 12;
      addUChar3(out, b1 | 224, b2 | 128, b3 | 128);
    }
  }

  return out->len;
}

/* Convert UCS-4-LE (byte ordeer: 4321) to UTF-8 */
static int
_u4tou8(unsigned char* in, int len, UString* out)
{
  int i;

  allocUString(out);

  if (len < 4) return 0;
  for (i = 0; i < len; i += 4) {
    unsigned int c = in[i] | (in[i+1] << 8) |
                             (in[i+2] << 16) | (in[i+3] << 24);
    if (c == 0xfeff) {          /* byte order mark */
      continue;
    }
    else if (c < 128) {         /* 0x0000-0x00FF */
      addUChar(out, c);
    }
    else if (c < 2048) {        /* 0x0100-0x07FF */
      unsigned char b2 = c & 63;
      unsigned char b1 = c >> 6;
      addUChar2(out, b1 | 192, b2 | 128);
    }
    else if (c < 0x10000) {     /* 0x0800-0xFFFF */
      unsigned char b3 = c & 63;
      unsigned char b2 = (c >> 6) & 63;
      unsigned char b1 = c >> 12;
      addUChar3(out, b1 | 224, b2 | 128, b3 | 128);
    }
    else if (c < 0x200000) {     /* 0x00010000-0x001FFFFF */
      unsigned char b4 = c & 63;
      unsigned char b3 = (c >> 6) & 63;
      unsigned char b2 = (c >> 12) & 63;
      unsigned char b1 = c >> 18;
      addUChar4(out, b1 | 240, b2 | 128, b3 | 128, b4 | 128);
    }
    else if (c < 0x4000000) {     /* 0x00200000-0x03FFFFFF */
      unsigned char b5 = c & 63;
      unsigned char b4 = (c >> 6) & 63;
      unsigned char b3 = (c >> 12) & 63;
      unsigned char b2 = (c >> 18) & 63;
      unsigned char b1 = c >> 24;
      addUChar5(out, b1 | 248, b2 | 128, b3 | 128, b4 | 128, b5 | 128);
    }
    else if (c < 0x80000000) {     /* 0x04000000-0x7FFFFFFF */
      unsigned char b6 = c & 63;
      unsigned char b5 = (c >> 6) & 63;
      unsigned char b4 = (c >> 12) & 63;
      unsigned char b3 = (c >> 18) & 63;
      unsigned char b2 = (c >> 24) & 63;
      unsigned char b1 = (c >> 30) & 63;
      addUChar6(out, b1 | 252, b2 | 128, b3 | 128,
		b4 | 128, b5 | 128, b6 | 128);
    }
    else {
      Raise(eException, "none-UCS char detected");
    }
  }

  return out->len;
}

/* Convert UCS-4-LE (byte ordeer: 4321) to UTF-16-LE (byte ordeer: 21) */
/* 10000-10FFFF -> D800-DBFF:DC00-DFFF */
static int
_u4tou16(unsigned char* in, int len, UString* out)
{
  int i;

  allocUString(out);

  if (len < 4) return 0;
  for (i = 0; i < len; i += 4) {
    unsigned int c = in[i] | (in[i+1] << 8) |
                             (in[i+2] << 16) | (in[i+3] << 24);
    if (c < 0x10000) {     /* 0x0000-0xFFFF */
      addUChar2(out, in[i], in[i+1]);
    }
    else if (c < 0x110000) {    /* 0x00010000-0x0010FFFF */
      unsigned int high = ((c - 0x10000) >> 10) | 0xd800;
      unsigned int low = (c & 1023) | 0xdc00;
      addUChar4(out, high & 255, high >> 8, low & 255, low >> 8);
    }
    else {
      Raise(eException, "none-UTF-16 char detected");
    }
  }

  return out->len;
}

/* Convert UTF-16-LE (byte ordeer: 21) to UCS-4-LE (byte ordeer: 4321) */
/* D800-DBFF:DC00-DFFF -> 10000-10FFFF */
static int
_u16tou4(unsigned char* in, int len, UString* out)
{
  int i;

  allocUString(out);

  if (len < 2) return 0;
  for (i = 0; i < len; i += 2) {
    unsigned int c = in[i] | (in[i+1] << 8);
    if (c >= 0xd800 && c <= 0xdbff) { /* high surrogate */
      unsigned int surrogate = 0;
      unsigned int low;
      if (i + 4 > len) /* not enough length */
	Raise(eException, "invalid surrogate detected");
      low = in[i+2] | (in[i+3] << 8);
      if (low < 0xdc00 || low > 0xdfff) /* not low surrogate */
	Raise(eException, "invalid surrogate detected");
      c = (((c & 1023)) << 10 | (low & 1023)) + 0x10000;
      addUChar4(out, c & 255, (c >> 8) & 255, c >> 16, 0);
      i += 2;
    }
    else {
      addUChar4(out, in[i], in[i+1], 0, 0);
    }
  }

  return out->len;
}


static unsigned short
unknown_euc_handler(const unsigned char* seq)
{
  ID mid = rb_intern("unknown_euc_handler");
  VALUE ret;

  if (!rb_method_boundp(CLASS_OF(mUconv), mid, 0))
    return '?';

  ret = rb_funcall((VALUE)mUconv, mid, 1, str_new2((char*)seq));
  Check_Type(ret, T_FIXNUM);
  return FIX2INT(ret);
}

static unsigned char*
unknown_unicode_handler(unsigned short code)
{
  ID mid = rb_intern("unknown_unicode_handler");
  VALUE ret;
  unsigned char* estr;

  if (!rb_method_boundp(CLASS_OF(mUconv), mid, 0)) {
    estr = (unsigned char*)malloc(2);
    estr[0] = '?';
    estr[1] = '\0';
    return estr;
  }
  ret = rb_funcall((VALUE)mUconv, mid, 1, INT2FIX(code));
  Check_Type(ret, T_STRING);
  estr = (unsigned char*)malloc(RSTRING(ret)->len + 1);
  memcpy(estr, RSTRING(ret)->ptr, RSTRING(ret)->len + 1);

  return estr;
}

static VALUE
uconv_u2toeuc(VALUE obj, VALUE wstr)
{
  int len;
  unsigned char* u;
  VALUE ret;
  UString e;

  Check_Type(wstr, T_STRING);

  u = (unsigned char*)(RSTRING(wstr)->ptr);
  len = RSTRING(wstr)->len;

  u2e_conv2(u, len, &e, unknown_unicode_handler);
  ret = str_new(e.str, e.len);
  freeUString(&e);

  return ret;
}

static VALUE
uconv_euctou2(VALUE obj, VALUE estr)
{
  int len;
  unsigned char* e;
  UString u;
  VALUE ret;

  Check_Type(estr, T_STRING);

  e = (unsigned char*)(RSTRING(estr)->ptr);
  len = e2u_conv2(e, &u, unknown_euc_handler);

  ret = str_new(u.str, u.len);
  freeUString(&u);

  return ret;
}

static VALUE
uconv_u2swap(VALUE obj, VALUE wstr)
{
  int len;
  int i;
  unsigned char* u;
  unsigned char* out;
  VALUE ret;

  Check_Type(wstr, T_STRING);

  u = (unsigned char*)(RSTRING(wstr)->ptr);
  len = RSTRING(wstr)->len;
  if (len < 2) return Qnil;
  ret = str_new(NULL, len);
  out = (unsigned char*)(RSTRING(ret)->ptr);

  for (i = 0; i < len; i+=2) {
    out[i] = u[i+1];
    out[i+1] = u[i];
  }
  return ret;
}

static VALUE
uconv_u2swap_b(VALUE obj, VALUE wstr)
{
  int len;
  int i;
  unsigned char* u;
  unsigned char* out;

  Check_Type(wstr, T_STRING);

  u = (unsigned char*)(RSTRING(wstr)->ptr);
  len = RSTRING(wstr)->len;
  if (len < 2) return Qnil;

  for (i = 0; i < len; i+=2) {
    register unsigned char tmp = u[i+1];
    u[i+1] = u[i];
    u[i] = tmp;
  }
  return wstr;
}

static VALUE
uconv_u4swap(VALUE obj, VALUE wstr)
{
  int len;
  int i;
  unsigned char* u;
  unsigned char* out;
  VALUE ret;

  Check_Type(wstr, T_STRING);

  u = (unsigned char*)(RSTRING(wstr)->ptr);
  len = RSTRING(wstr)->len;
  if (len < 4) return Qnil;
  ret = str_new(NULL, len);
  out = (unsigned char*)(RSTRING(ret)->ptr);

  for (i = 0; i < len; i+=4) {
    out[i]   = u[i+3];
    out[i+1] = u[i+2];
    out[i+2] = u[i+1];
    out[i+3] = u[i];
  }
  return ret;
}

static VALUE
uconv_u4swap_b(VALUE obj, VALUE wstr)
{
  int len;
  int i;
  unsigned char* u;
  unsigned char* out;

  Check_Type(wstr, T_STRING);

  u = (unsigned char*)(RSTRING(wstr)->ptr);
  len = RSTRING(wstr)->len;
  if (len < 4) return Qnil;

  for (i = 0; i < len; i+=4) {
    register unsigned char tmp1 = u[i];
    register unsigned char tmp2 = u[i+1];
    u[i] = u[i+3];
    u[i+1] = u[i+2];
    u[i+2] = tmp2;
    u[i+3] = tmp1;
  }
  return wstr;
}


static VALUE
uconv_u8tou16(VALUE obj, VALUE ustr)
{
  int len;
  unsigned char* in;
  UString out;
  VALUE ret;

  Check_Type(ustr, T_STRING);
  in = (unsigned char*)(RSTRING(ustr)->ptr);
  _u8tou16(in, &out);
  ret = str_new(out.str, out.len);
  freeUString(&out);

  return ret;
}

static VALUE
uconv_u16tou8(VALUE obj, VALUE wstr)
{
  int len;
  unsigned char* in;
  UString out;
  VALUE ret;

  Check_Type(wstr, T_STRING);
  in = (unsigned char*)(RSTRING(wstr)->ptr);
  len = RSTRING(wstr)->len;
  _u16tou8(in, len, &out);
  ret = str_new(out.str, out.len);
  freeUString(&out);

  return ret;
}


static VALUE
uconv_u8tou4(VALUE obj, VALUE ustr)
{
  unsigned char* in;
  UString out;
  VALUE ret;

  Check_Type(ustr, T_STRING);
  in = (unsigned char*)(RSTRING(ustr)->ptr);
  _u8tou4(in, &out);
  ret = str_new(out.str, out.len);
  freeUString(&out);

  return ret;
}

static VALUE
uconv_u4tou8(VALUE obj, VALUE wstr)
{
  int len;
  unsigned char* in;
  UString out;
  VALUE ret;

  Check_Type(wstr, T_STRING);
  in = (unsigned char*)(RSTRING(wstr)->ptr);
  len = RSTRING(wstr)->len;
  _u4tou8(in, len, &out);
  ret = str_new(out.str, out.len);
  freeUString(&out);

  return ret;
}


static VALUE
uconv_u16tou4(VALUE obj, VALUE wstr)
{
  int len;
  unsigned char* in;
  UString out;
  VALUE ret;

  Check_Type(wstr, T_STRING);
  in = (unsigned char*)(RSTRING(wstr)->ptr);
  len = RSTRING(wstr)->len;
  _u16tou4(in, len, &out);
  ret = str_new(out.str, out.len);
  freeUString(&out);

  return ret;
}

static VALUE
uconv_u4tou16(VALUE obj, VALUE wstr)
{
  int len;
  unsigned char* in;
  UString out;
  VALUE ret;

  Check_Type(wstr, T_STRING);
  in = (unsigned char*)(RSTRING(wstr)->ptr);
  len = RSTRING(wstr)->len;
  _u4tou16(in, len, &out);
  ret = str_new(out.str, out.len);
  freeUString(&out);

  return ret;
}


static VALUE
uconv_u8toeuc(VALUE obj, VALUE ustr)
{
  int len;
  unsigned char* in;
  UString out;
  UString e;
  VALUE ret;

  Check_Type(ustr, T_STRING);
  in = (unsigned char*)(RSTRING(ustr)->ptr);
  _u8tou16(in, &out);
  u2e_conv2(out.str, out.len, &e, unknown_unicode_handler);
  ret = str_new(e.str, e.len);
  freeUString(&e);
  freeUString(&out);

  return ret;
}

static VALUE
uconv_euctou8(VALUE obj, VALUE estr)
{
  int len;
  unsigned char* e;
  UString in;
  UString out;
  VALUE ret;

  Check_Type(estr, T_STRING);
  e = (unsigned char*)(RSTRING(estr)->ptr);
  len = RSTRING(estr)->len;
  e2u_conv2(e, &in, unknown_euc_handler);
  _u16tou8(in.str, in.len, &out);
  ret = str_new(out.str, out.len);
  freeUString(&out);
  freeUString(&in);

  return ret;
}

void
Init_uconv()
{
  mUconv = rb_define_module("Uconv");

  rb_define_module_function(mUconv, "u16toeuc", uconv_u2toeuc, 1);
  rb_define_module_function(mUconv, "euctou16", uconv_euctou2, 1);
  rb_define_module_function(mUconv, "u2toeuc", uconv_u2toeuc, 1);
  rb_define_module_function(mUconv, "euctou2", uconv_euctou2, 1);

  rb_define_module_function(mUconv, "u16swap", uconv_u2swap, 1);
  rb_define_module_function(mUconv, "u16swap!", uconv_u2swap_b, 1);
  rb_define_module_function(mUconv, "u2swap", uconv_u2swap, 1);
  rb_define_module_function(mUconv, "u2swap!", uconv_u2swap_b, 1);
  rb_define_module_function(mUconv, "u4swap", uconv_u4swap, 1);
  rb_define_module_function(mUconv, "u4swap!", uconv_u4swap_b, 1);

  rb_define_module_function(mUconv, "u8tou16", uconv_u8tou16, 1);
  rb_define_module_function(mUconv, "u16tou8", uconv_u16tou8, 1);
  rb_define_module_function(mUconv, "u8tou2", uconv_u8tou16, 1);
  rb_define_module_function(mUconv, "u2tou8", uconv_u16tou8, 1);
  rb_define_module_function(mUconv, "u8tou4", uconv_u8tou4, 1);
  rb_define_module_function(mUconv, "u4tou8", uconv_u4tou8, 1);
  rb_define_module_function(mUconv, "u16tou4", uconv_u16tou4, 1);
  rb_define_module_function(mUconv, "u4tou16", uconv_u4tou16, 1);

  rb_define_module_function(mUconv, "u8toeuc", uconv_u8toeuc, 1);
  rb_define_module_function(mUconv, "euctou8", uconv_euctou8, 1);
}
