/* Copyright (C) 2003,2004 MORIOKA Tomohiko
   This file is part of the CHISE Library.

   The CHISE Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   The CHISE Library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with the CHISE Library; if not, write to the Free
   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
   02111-1307 USA.  */

#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif
#include "sysdep.h"
#include "chise.h"
#include "chise-name.h"

const unsigned char chise_db_dir[] = CHISE_DB_DIR;
const unsigned char chise_system_db_dir[] = CHISE_SI_DB_DIR;

CHISE_Feature_Table*
chise_ds_open_feature_table (CHISE_DS *ds, const char *feature);

int chise_ft_close (CHISE_Feature_Table *table);


CHISE_CCS_Table*
chise_ds_open_ccs_table (CHISE_DS *ds, const char *ccs);

int chise_ccst_close (CHISE_CCS_Table *table);


CHISE_Property_Table*
chise_ds_open_property_table (CHISE_DS *ds, const char *property);

int chise_pt_close (CHISE_Property_Table *table);


typedef DB CHISE_Attribute_Table;

CHISE_Attribute_Table*
CHISE_Attribute_Table_open (const unsigned char *db_dir,
			    const char *category,
			    const char *key_type, const char *name,
			    DBTYPE real_subtype,
			    u_int32_t accessmask, int modemask);

int CHISE_Attribute_Table_close (CHISE_Attribute_Table *db);

int chise_attribute_table_get (CHISE_Attribute_Table *db,
			       char *key, CHISE_Value *valdatum);

int chise_attribute_table_put (CHISE_Attribute_Table *db,
			       char *key, unsigned char *value);


#define xzero(lvalue) ((void) memset (&(lvalue), '\0', sizeof (lvalue)))

CHISE_Char_ID
chise_char_id_parse_c_string (unsigned char *str, size_t len);

int
chise_format_char_id (CHISE_Char_ID cid, unsigned char *dest, size_t len);


struct CHISE_DS
{
  CHISE_DS_Type type;
  unsigned char *location;
  CHISE_NAME_TABLE* feature_names;
  CHISE_NAME_TABLE* ccs_names;
  CHISE_NAME_TABLE* property_names;
  DBTYPE subtype;
  int modemask;
};

CHISE_DS*
CHISE_DS_open (CHISE_DS_Type type, const unsigned char *location,
	       int subtype, int modemask)
{
  CHISE_DS *ds = (CHISE_DS*)malloc (sizeof (CHISE_DS));
  size_t len = strlen (location);

  if (ds == NULL)
    return NULL;

  ds->type = type;
  ds->subtype = ( (subtype != 0) ? subtype : DB_HASH );
  ds->modemask = modemask;
  ds->location = (unsigned char*)malloc (len + 1);
  if (ds->location == NULL)
    {
      free (ds);
      return NULL;
    }
  strcpy (ds->location, location);

  ds->feature_names = chise_make_name_table ();
  if (ds->feature_names == NULL)
    {
      free (ds->location);
      free (ds);
    }

  ds->ccs_names = chise_make_name_table ();
  if (ds->ccs_names == NULL)
    {
      free (ds->feature_names);
      free (ds->location);
      free (ds);
    }

  ds->property_names = chise_make_name_table ();
  if (ds->property_names == NULL)
    {
      free (ds->ccs_names);
      free (ds->feature_names);
      free (ds->location);
      free (ds);
    }
  return ds;
}

int
CHISE_DS_close (CHISE_DS *ds)
{
  if (ds->location != NULL)
    free (ds->location);
  if (ds->feature_names != NULL)
    chise_destroy_name_table (ds->feature_names);
  if (ds->ccs_names != NULL)
    chise_destroy_name_table (ds->ccs_names);
  if (ds->property_names != NULL)
    chise_destroy_name_table (ds->property_names);
  free (ds);
  return 0;
}

unsigned char*
chise_ds_location (CHISE_DS *ds)
{
  return ds->location;
}

CHISE_Feature_Table*
chise_ds_get_feature (CHISE_DS *ds, const unsigned char *feature)
{
  CHISE_Feature_Table* ft;

  ft = chise_name_table_get (ds->feature_names, feature);
  if (ft != NULL)
    return ft;

  ft = chise_ds_open_feature_table (ds, feature);
  if (ft == NULL)
    return NULL;

  if (chise_name_table_put (ds->feature_names, feature, ft))
    {
      chise_ft_close (ft);
      return NULL;
    }
  return ft;
}

CHISE_CCS
chise_ds_get_ccs (CHISE_DS *ds, const unsigned char *ccs)
{
  CHISE_CCS_Table* ct;

  ct = chise_name_table_get (ds->ccs_names, ccs);
  if (ct != NULL)
    return ct;

  ct = chise_ds_open_ccs_table (ds, ccs);
  if (ct == NULL)
    return NULL;

  if (chise_name_table_put (ds->ccs_names, ccs, ct))
    {
      chise_ccst_close (ct);
      return NULL;
    }
  return ct;
}

CHISE_Property_Table*
chise_ds_get_property (CHISE_DS *ds, const unsigned char *property)
{
  CHISE_Property_Table* pt;

  pt = chise_name_table_get (ds->property_names, property);
  if (pt != NULL)
    return pt;

  pt = chise_ds_open_property_table (ds, property);
  if (pt == NULL)
    return NULL;

  if (chise_name_table_put (ds->property_names, property, pt))
    {
      chise_pt_close (pt);
      return NULL;
    }
  return pt;
}

int
chise_ds_foreach_char_feature_name (CHISE_DS *ds,
				    int (*func) (CHISE_DS *ds,
						 unsigned char *name))
{
  unsigned char *dname
    = alloca (strlen (ds->location) + sizeof ("/character/feature") + 1);
  DIR *dir;
  struct dirent *de;

  strcpy (dname, ds->location);
  strcat (dname, "/character/feature");

  if ( (dir = opendir (dname)) == NULL)
    return -1;

  while ( (de = readdir (dir)) != NULL )
    {
      if ( (strcmp (de->d_name, ".") != 0) &&
	   (strcmp (de->d_name, "..") != 0) )
	{
	  int i, need_to_decode = 0;
	  unsigned char *cp;
	  unsigned char *name;
	  unsigned char *np;

	  for (cp = de->d_name, i = 0; *cp != '\0'; i++)
	    {
	      if (*cp++ == '%')
		need_to_decode = 1;
	    }
	  if (need_to_decode)
	    {
	      int index = -1;
	      int ch, c[2];
	      int hex[2];

	      name = (unsigned char *) alloca (i);
	      cp = de->d_name;
	      np = name;

	      while ( (ch = *cp++) != '\0')
		{
		  if (ch == '%')
		    {
		      if (index >= 0)
			{
			  *np++ = '%';
			  if (index == 1)
			    *np++ = c[0];
			}
		      index = 0;
		    }
		  else if (index >= 0)
		    {
		      c[index] = ch;

		      if ( ('0' <= ch) && (ch <= '9') )
			hex[index++] = ch - '0';
		      else if ( ('A' <= ch) && (ch <= 'F') )
			hex[index++] = ch - 'A' + 10;
		      else if ( ('a' <= ch) && (ch <= 'f') )
			hex[index++] = ch - 'a' + 10;
		      else
			{
			  *np++ = '%';
			  if (index == 1)
			    *np++ = c[0];
			  *np++ = ch;
			  index = -1;
			  continue;
			}
		      if (index == 2)
			{
			  *np++ = (hex[0] << 4) | hex[1];
			  index = -1;
			  continue;
			}
		    }
		  else
		    *np++ = ch;
		}
	      *np = '\0';
	    }  
	  else
	    name = de->d_name;

	  if (func (ds, name))
	    return closedir (dir);
	}
    }
  return closedir (dir);
}

struct CHISE_Feature_Table
{
  CHISE_DS *ds;
  unsigned char *name;
  CHISE_Attribute_Table *db;
  u_int32_t access;
};

CHISE_Feature_Table*
chise_ds_open_feature_table (CHISE_DS *ds, const char *feature)
{
  CHISE_Feature_Table* table;
  size_t len = strlen (feature);

  if (ds == NULL)
    return NULL;

  table = (CHISE_Feature_Table*)malloc (sizeof (CHISE_Feature_Table));
  if (table == NULL)
    return NULL;

  table->ds = ds;
  table->db = NULL;
  table->access = 0;
  table->name = (unsigned char*)malloc (len + 1);
  if (table->name == NULL)
    {
      free (table);
      return NULL;
    }
  strcpy (table->name, feature);
  return table;
}

int
chise_ft_close (CHISE_Feature_Table *table)
{
  int status;

  if (table == NULL)
    return -1;

  if (table->db == NULL)
    status = -1;
  else
    status = CHISE_Attribute_Table_close (table->db);

  if (table->name == NULL)
    status = -1;
  else
    {
      free (table->name);
      status = 0;
    }
  free (table);
  return status;
}

int
chise_feature_setup_db (CHISE_Feature feature, int writable)
{
  u_int32_t access;

  if (feature == NULL)
    return -1;

  if (writable)
    {
      if ((feature->access & DB_CREATE) == 0)
	{
	  if (feature->db != NULL)
	    {
	      CHISE_Attribute_Table_close (feature->db);
	      feature->db = NULL;
	    }
	  feature->access = 0;
	}
      access = DB_CREATE;
    }
  else
    access = DB_RDONLY;

  if (feature->db == NULL)
    {
      CHISE_DS *ds = feature->ds;

      feature->db
	= CHISE_Attribute_Table_open (ds->location, "character",
				      "feature", feature->name,
				      ds->subtype, access, ds->modemask);
      if (feature->db == NULL)
	return -1;
      feature->access = access;
    }
  return 0;
}

int
chise_feature_sync (CHISE_Feature feature)
{
  int status;

  if (feature->db == NULL)
    status = 0;
  else
    status = CHISE_Attribute_Table_close (feature->db);
  feature->db = NULL;
  feature->access = 0;
  return status;
}

int
chise_char_set_feature_value (CHISE_Char_ID cid,
			      CHISE_Feature feature,
			      unsigned char *value)
{
  unsigned char key_buf[8];

  if (feature == NULL)
    return -1;
  if (chise_feature_setup_db (feature, 1))
    return -1;
  chise_format_char_id (cid, key_buf, 8);
  return chise_attribute_table_put (feature->db, key_buf, value);
}

int
chise_char_load_feature_value (CHISE_Char_ID cid,
			       CHISE_Feature_Table *table,
			       CHISE_Value *valdatum)
{
  unsigned char key_buf[8];

  if (chise_feature_setup_db (table, 0))
    return -1;
  chise_format_char_id (cid, key_buf, 8);
  return chise_attribute_table_get (table->db, key_buf, valdatum);
}

unsigned char*
chise_char_gets_feature_value (CHISE_Char_ID cid,
			       CHISE_Feature_Table *table,
			       unsigned char *buf, size_t size)
{
  CHISE_Value valdatum;
  unsigned char key_buf[8];
  int status;

  if (chise_feature_setup_db (table, 0))
    return NULL;
  chise_format_char_id (cid, key_buf, 8);
  status = chise_attribute_table_get (table->db,
				      key_buf, &valdatum);
  if (status)
    return NULL;
  if (size < valdatum.size)
    return NULL;
  strncpy (buf, valdatum.data, valdatum.size);
  buf[valdatum.size] = '\0';
  return buf;
}

int
chise_feature_foreach_char_with_value (CHISE_Feature feature,
				       int (*func) (CHISE_Char_ID cid,
						    CHISE_Feature feature,
						    CHISE_Value *valdatum))
{
  DBT keydatum, valdatum;
  DBC *dbcp;
  int status;

  if (chise_feature_setup_db (feature, 0))
    return -1;
  xzero (keydatum);
  xzero (valdatum);

  status = feature->db->cursor (feature->db, NULL, &dbcp, 0);
  for (status = dbcp->c_get (dbcp, &keydatum, &valdatum, DB_FIRST);
       status == 0;
       status = dbcp->c_get (dbcp, &keydatum, &valdatum, DB_NEXT))
    {
      unsigned char *key_str = (unsigned char *)keydatum.data;
      int key_len = strnlen (key_str, keydatum.size);
      CHISE_Char_ID key = chise_char_id_parse_c_string (key_str, key_len);
      int ret = func (key, feature, &valdatum);

      if (ret)
	break;
    }
  dbcp->c_close (dbcp);
  return 0;
}


struct CHISE_CCS_Table
{
  CHISE_DS *ds;
  unsigned char *name;
  CHISE_Attribute_Table *db;
  u_int32_t access;
};

CHISE_CCS_Table*
chise_ds_open_ccs_table (CHISE_DS *ds, const char *ccs)
{
  CHISE_CCS_Table* table;
  size_t len = strlen (ccs);

  if (ds == NULL)
    return NULL;

  table = (CHISE_CCS_Table*)malloc (sizeof (CHISE_CCS_Table));
  if (table == NULL)
    return NULL;

  table->ds = ds;
  table->db = NULL;
  table->access = 0;
  table->name = (unsigned char*)malloc (len + 1);
  if (table->name == NULL)
    {
      free (table);
      return NULL;
    }
  strcpy (table->name, ccs);
  return table;
}

int
chise_ccst_close (CHISE_CCS_Table *table)
{
  int status;

  if (table == NULL)
    return -1;

  if (table->db == NULL)
    status = 0;
  else
    status = CHISE_Attribute_Table_close (table->db);

  if (table->name == NULL)
    status = -1;
  else
    {
      free (table->name);
      status = 0;
    }
  free (table);
  return status;
}

int
chise_ccs_setup_db (CHISE_CCS ccs, int writable)
{
  u_int32_t access;

  if (ccs == NULL)
    return -1;

  if (writable)
    {
      if ((ccs->access & DB_CREATE) == 0)
	{
	  if (ccs->db != NULL)
	    {
	      CHISE_Attribute_Table_close (ccs->db);
	      ccs->db = NULL;
	    }
	  ccs->access = 0;
	}
      access = DB_CREATE;
    }
  else
    access = DB_RDONLY;

  if (ccs->db == NULL)
    {
      CHISE_DS *ds = ccs->ds;

      ccs->db
	= CHISE_Attribute_Table_open (ds->location, "character",
				      "by_feature", ccs->name,
				      ds->subtype, access, ds->modemask);
      if (ccs->db == NULL)
	return -1;
      ccs->access = access;
    }
  return 0;
}

int
chise_ccs_sync (CHISE_CCS ccs)
{
  int status;

  if (ccs->db == NULL)
    status = 0;
  else
    status = CHISE_Attribute_Table_close (ccs->db);
  ccs->db = NULL;
  ccs->access = 0;
  return status;
}

CHISE_Char_ID
chise_ccs_decode (CHISE_CCS ccs, int code_point)
{
  CHISE_Value valdatum;
  int status = 0;
  char key_buf[16];

  if (ccs == NULL)
    return -1;

  if (chise_ccs_setup_db (ccs, 0))
    return -1;  

  sprintf(key_buf, "%d", code_point);
  status = chise_attribute_table_get (ccs->db, key_buf, &valdatum);
  if (!status)
    {
      unsigned char *str
	= (unsigned char *)chise_value_data (&valdatum);
      int len = strnlen (str, chise_value_size (&valdatum));

      return chise_char_id_parse_c_string (str, len);
    }
  return -1;
}

int
chise_ccs_set_decoded_char (CHISE_CCS ccs,
			    int code_point, CHISE_Char_ID cid)
{
  char key_buf[16], val_buf[8];

  if (ccs == NULL)
    return -1;

  if (chise_ccs_setup_db (ccs, 1))
    return -1;  

  sprintf(key_buf, "%d", code_point);
  chise_format_char_id (cid, val_buf, 8);
  return chise_attribute_table_put (ccs->db, key_buf, val_buf);
}


struct CHISE_Property_Table
{
  CHISE_DS *ds;
  unsigned char *name;
  CHISE_Attribute_Table *db;
  u_int32_t access;
};

CHISE_Property_Table*
chise_ds_open_property_table (CHISE_DS *ds, const char *property)
{
  CHISE_Property_Table* table;
  size_t len = strlen (property);

  if (ds == NULL)
    return NULL;

  table = (CHISE_Property_Table*)malloc (sizeof (CHISE_Property_Table));
  if (table == NULL)
    return NULL;

  table->ds = ds;
  table->db = NULL;
  table->access = 0;
  table->name = (unsigned char*)malloc (len + 1);
  if (table->name == NULL)
    {
      free (table);
      return NULL;
    }
  strcpy (table->name, property);
  return table;
}

int
chise_pt_close (CHISE_Property_Table *table)
{
  int status;

  if (table == NULL)
    return -1;

  if (table->db == NULL)
    status = -1;
  else
    status = CHISE_Attribute_Table_close (table->db);

  if (table->name == NULL)
    status = -1;
  else
    {
      free (table->name);
      status = 0;
    }
  free (table);
  return status;
}

int
chise_property_setup_db (CHISE_Property property, int writable)
{
  u_int32_t access;

  if (property == NULL)
    return -1;

  if (writable)
    {
      if ((property->access & DB_CREATE) == 0)
	{
	  if (property->db != NULL)
	    {
	      CHISE_Attribute_Table_close (property->db);
	      property->db = NULL;
	    }
	  property->access = 0;
	}
      access = DB_CREATE;
    }
  else
    access = DB_RDONLY;

  if (property->db == NULL)
    {
      CHISE_DS *ds = property->ds;

      property->db
	= CHISE_Attribute_Table_open (ds->location, "feature",
				      "property", property->name,
				      ds->subtype, access, ds->modemask);
      if (property->db == NULL)
	return -1;
      property->access = access;
    }
  return 0;
}

int
chise_property_sync (CHISE_Property property)
{
  int status;

  if (property->db == NULL)
    status = 0;
  else
    status = CHISE_Attribute_Table_close (property->db);
  property->db = NULL;
  property->access = 0;
  return status;
}

int
chise_feature_set_property_value (CHISE_Feature feature,
				  CHISE_Property property,
				  unsigned char *value)
{
  if (property == NULL)
    return -1;
  if (chise_property_setup_db (property, 1))
    return -1;
  return chise_attribute_table_put (property->db, feature->name, value);
}

int
chise_feature_load_property_value (CHISE_Feature feature,
				   CHISE_Property_Table *table,
				   CHISE_Value *valdatum)
{
  if (chise_property_setup_db (table, 0))
    return -1;
  return chise_attribute_table_get (table->db, feature->name, valdatum);
}

unsigned char*
chise_feature_gets_property_value (CHISE_Feature feature,
				   CHISE_Property_Table *table,
				   unsigned char *buf, size_t size)
{
  CHISE_Value valdatum;
  int status;

  if (chise_property_setup_db (table, 0))
    return NULL;
  status = chise_attribute_table_get (table->db,
				      feature->name, &valdatum);
  if (status)
    return NULL;
  if (size < valdatum.size)
    return NULL;
  strncpy (buf, valdatum.data, valdatum.size);
  buf[valdatum.size] = '\0';
  return buf;
}


CHISE_Attribute_Table*
CHISE_Attribute_Table_open (const unsigned char *db_dir,
			    const char *category,
			    const char *key_type, const char *name,
			    DBTYPE real_subtype,
			    u_int32_t accessmask, int modemask)
{
  DB* dbase;
  int status;
  int len, name_len, i;
  int size;
  char *db_file_name, *sp;
  struct stat statbuf;

  status = db_create (&dbase, NULL, 0);
  if (status)
    return NULL;

  if ( (accessmask & DB_CREATE) && stat (db_dir, &statbuf) )
    mkdir (db_dir, modemask);

  len = strlen (db_dir);
  name_len = strlen (name);
  size = len + strlen (category) + strlen (key_type) + name_len * 3 + 5;
  db_file_name = alloca (size);
  strcpy (db_file_name, db_dir);
  if (db_file_name[len - 1] != '/')
    {
      db_file_name[len++] = '/';
      db_file_name[len] = '\0';
    }

  strcat (db_file_name, category);
  if ( (accessmask & DB_CREATE) && stat (db_file_name, &statbuf) )
    mkdir (db_file_name, modemask);
  strcat (db_file_name, "/");

  strcat (db_file_name, key_type);
  if ( (accessmask & DB_CREATE) && stat (db_file_name, &statbuf) )
    mkdir (db_file_name, modemask);
  strcat (db_file_name, "/");

  /* strcat (db_file_name, name); */
  sp = &db_file_name[strlen (db_file_name)];
  for (i = 0; i < name_len; i++)
    {
      int c = name[i];

      if ( (c == '/') || (c == '%') )
	{
	  sprintf (sp, "%%%02X", c);
	  sp += 3;
	}
      else
	*sp++ = c;
    }
  *sp = '\0';
#if DB_VERSION_MAJOR < 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR < 1)
  status = dbase->open (dbase, db_file_name, NULL,
			real_subtype, accessmask, modemask);
#else /* DB_VERSION >= 4.1 */
  status = dbase->open (dbase, NULL, db_file_name, NULL,
			real_subtype,
			accessmask /* | DB_AUTO_COMMIT */, modemask);
#endif /* DB_VERSION < 4.1 */
  if (status)
    {
      dbase->close (dbase, 0);
      return NULL;
    }
  return dbase;
}

int
CHISE_Attribute_Table_close (CHISE_Attribute_Table *db)
{
  if (db)
    {
      db->sync  (db, 0);
      db->close (db, 0);
    }
  return 0;
}

int
chise_attribute_table_get (CHISE_Attribute_Table *db,
			   char *key, CHISE_Value *valdatum)
{
  DBT keydatum;
  int status = 0;

  /* DB Version 2 requires DBT's to be zeroed before use. */
  xzero (keydatum);
  xzero (*valdatum);

  keydatum.data = key;
  keydatum.size = strlen (key);

  status = db->get (db, NULL, &keydatum, valdatum, 0);
  return status;
}

int
chise_attribute_table_put (CHISE_Attribute_Table *db,
			   char *key, unsigned char *value)
{
  DBT keydatum, valdatum;
  int status = 0;

  /* DB Version 2 requires DBT's to be zeroed before use. */
  xzero (keydatum);
  xzero (valdatum);

  keydatum.data = key;
  keydatum.size = strlen (key);

  valdatum.data = value;
  valdatum.size = strlen (value);

  status = db->put (db, NULL, &keydatum, &valdatum, 0);
  return status;
}

CHISE_Char_ID
chise_char_id_parse_c_string (unsigned char *str, size_t len)
{
  int i = 0;

  if ( (len >= 2) && (str[i++] == '?') )
    {
      unsigned char c = str[i++];
      int counter;
      CHISE_Char_ID cid;

      if (c == '\\')
	{
	  if (len < 3)
	    return -1;
	  c = str[i++];
	  if (c == '^')
	    {
	      if (len < 4)
		return -1;
	      c = str[i++];
	      if (c == '?')
		return 0x7F;
	      else
		return c & (0x80 | 0x1F);
	    }
	}
      if ( c < 0xC0 )
	{
	  cid = c;
	  counter = 0;
	}
      else if ( c < 0xE0 )
	{
	  cid = c & 0x1f;
	  counter = 1;
	}
      else if ( c < 0xF0 )
	{
	  cid = c & 0x0f;
	  counter = 2;
	}
      else if ( c < 0xF8 )
	{
	  cid = c & 0x07;
	  counter = 3;
	}
      else if ( c < 0xFC )
	{
	  cid = c & 0x03;
	  counter = 4;
	}
      else
	{
	  cid = c & 0x01;
	  counter = 5;
	}

      if (counter + 2 <= len)
	{
	  int j;

	  for (j = 0; j < counter; j++)
	    cid = (cid << 6) | (str[j + i] & 0x3F);
	  return cid;
	}
    }
  return -1;
}

int
chise_format_char_id (CHISE_Char_ID cid, unsigned char *dest, size_t len)
{
  int i = 0;

  dest[i++] = '?';
  if (cid == '\t')
    {
      dest[i++] = '\\';
      dest[i++] = 't';
      dest[i] = '\0';
      return i;
    }
  else if (cid == '\n')
    {
      dest[i++] = '\\';
      dest[i++] = 'n';
      dest[i] = '\0';
      return i;
    }
  else if (cid == '\r')
    {
      dest[i++] = '\\';
      dest[i++] = 'r';
      dest[i] = '\0';
      return i;
    }
  else if (cid == 0x1C)
    {
      dest[i++] = '\\';
      dest[i++] = '^';
      dest[i++] = '\\';
      dest[i++] = '\\';
      dest[i] = '\0';
      return i;
    }
  else if (cid <= 0x1F)
    {
      dest[i++] = '\\';
      dest[i++] = '^';
      dest[i++] = '@' + cid;
      dest[i] = '\0';
      return i;
    }
  else if ( (cid == ' ') || (cid == '"') ||
	    (cid == '#') || (cid == '\'') ||
	    (cid == '(') || (cid == ')') ||
	    (cid == ',') || (cid == '.') ||
	    (cid == ';') || (cid == '?') ||
	    (cid == '[') || (cid == '\\') ||
	    (cid == ']') || (cid == '`') )
    {
      dest[i++] = '\\';
      dest[i++] = cid;
      dest[i] = '\0';
      return i;
    }
  else if (cid <= 0x7E)
    {
      dest[i++] = cid;
      dest[i] = '\0';
      return i;
    }
  else if (cid == 0x7F)
    {
      dest[i++] = '\\';
      dest[i++] = '^';
      dest[i++] = '?';
      dest[i] = '\0';
      return i;
    }
  else if (cid <= 0x9F)
    {
      dest[i++] = '\\';
      dest[i++] = '^';
      dest[i++] = ((cid + '@') >> 6) | 0xC0;
      dest[i++] = ((cid + '@') & 0x3F) | 0x80;
      dest[i] = '\0';
      return i;
    }
  else if (cid <= 0x7FF)
    {
      dest[i++] = (cid >> 6) | 0xC0;
      dest[i++] = (cid & 0x3F) | 0x80;
      dest[i] = '\0';
      return i;
    }
  else if (cid <= 0xFFFF)
    {
      dest[i++] = (cid >> 12) | 0xE0;
      dest[i++]= ((cid >>  6) & 0x3F) | 0x80;
      dest[i++]=  (cid        & 0x3F) | 0x80;
      dest[i] = '\0';
      return i;
    }
  else if (cid <= 0x1FFFFF)
    {
      dest[i++]=  (cid >> 18) | 0xF0;
      dest[i++]= ((cid >> 12) & 0x3F) | 0x80;
      dest[i++]= ((cid >>  6) & 0x3F) | 0x80;
      dest[i++]=  (cid        & 0x3F) | 0x80;
      dest[i] = '\0';
      return i;
    }
  else if (cid <= 0x3FFFFFF)
    {
      dest[i++]=  (cid >> 24) | 0xF8;
      dest[i++]= ((cid >> 18) & 0x3F) | 0x80;
      dest[i++]= ((cid >> 12) & 0x3F) | 0x80;
      dest[i++]= ((cid >>  6) & 0x3F) | 0x80;
      dest[i++]=  (cid        & 0x3F) | 0x80;
      dest[i] = '\0';
      return i;
    }
  else
    {
      dest[i++]=  (cid >> 30) | 0xFC;
      dest[i++]= ((cid >> 24) & 0x3F) | 0x80;
      dest[i++]= ((cid >> 18) & 0x3F) | 0x80;
      dest[i++]= ((cid >> 12) & 0x3F) | 0x80;
      dest[i++]= ((cid >>  6) & 0x3F) | 0x80;
      dest[i++]=  (cid        & 0x3F) | 0x80;
      dest[i] = '\0';
      return i;
    }
}
