/* -*- Mode: C; c-file-style: "gnu"; indent-tabs-mode:nil -*-
   jstrings.c -- java string functions.
   Created: Chris Toshok <toshok@hungry.com>, 28-Apr-1999
 */
/*
  This file is part of Japhar, the GNU Virtual Machine for Java Bytecodes.
  Japhar is a project of The Hungry Programmers, GNU, and OryxSoft.

  Copyright (C) 1997, 1998, 1999 The Hungry Programmers

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

  This 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
  Library General Public License for more details.

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

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "runtimeint.h"

#define UTF8_1BYTE_TRESHOLD     0x0080
#define UTF8_2BYTE_TRESHOLD     0x07ff

static ClazzFile* string_cf;
static MethodStruct *string_ctor;
static FieldStruct *length_field, *value_field, *offset_field;
static ClazzFile* char_array_type;

static void
init_ids(HungryEnv *henv)
{
  if (string_cf) return;

  string_cf = HVM_ClassFind(henv, java_lang_String);
  string_ctor = HVM_MethodFind(henv, string_cf, "<init>", "([C)V");
  length_field = HVM_FieldFind(henv, string_cf, "count", "I");
  offset_field = HVM_FieldFind(henv, string_cf, "offset", "I");
  value_field = HVM_FieldFind(henv, string_cf, "value", "[C");
  char_array_type = HVM_ClassFind(henv, "[C");
}

PR_IMPLEMENT(japhar_object*)
HVM_StringFromCString(HungryEnv *henv,
                      const char *str)
{
  japhar_object* new_string;
  japhar_object* char_array;
  PRUint16* array_elements; 
  PRUint32 java_at;
  PRUint8 atchar;
  PRUint16* tmparray;

  init_ids(henv);

  tmparray = PR_MALLOC(PL_strlen(str) * sizeof(PRUint16));
  java_at =0;
  while ((atchar = (PRUint8) *str++))
    {
      if ( !(atchar & 0x80) )
        {
          tmparray[java_at] = atchar;
        }
      else if ( !(atchar & 0x20) )
        {
          tmparray[java_at] = ((atchar & 0x1f)<<6) + (*str++ & 0x3f);
        }
      else
        {
          tmparray[java_at] = ((atchar & 0x0f)<<12);
          tmparray[java_at] |= ((*str++ & 0x3f)<<6);
          tmparray[java_at] |= ((*str++ & 0x3f));
        }
      java_at++;
    }  

  char_array = HVM_ArrayNew(henv, java_at, char_array_type);
  if (!char_array)
    {
      PR_DELETE(tmparray);
      return NULL;
    }

  array_elements = HVM_ArrayGetBody(char_array);
  
  memcpy(array_elements, tmparray, java_at * sizeof(PRUint16));

  new_string = HVM_ObjectNew(henv, string_cf);
  HVM_MethodCall(henv, string_ctor, new_string, char_array);

  PR_DELETE(tmparray);

  return new_string;
}


/* 
  Idea for speedup - length should be not calculated for relatively short
  strings. Instead 3*string length space should be alloc()ated, buffer would
  be PR_MALLOC()ated at end and data memcopied.
 */
PR_IMPLEMENT(char*)
HVM_StringToCString(HungryEnv *henv,
                    japhar_object* jstr)
{
  PRSize utflength = HVM_StringGetUTFLength(henv, jstr);
  /* XXX +1 to get zero termination.  Is this needed */
  char *bytes = PR_Calloc(utflength+1, sizeof(*bytes));
  PRUint32 offset = HVM_StringGetOffset(henv, jstr);
  japhar_object* char_array = HVM_StringGetValue(henv, jstr);
  PRUint16* chars = (PRUint16*)HVM_ArrayGetBody(char_array);
  PRUint32 java_at, utf_at;
  PRInt32 length = HVM_StringGetLength(henv, jstr);
  
  utf_at =0;

  for ( java_at=offset; java_at < (length+offset); java_at++ )
    {
      PRUint16 atchar = chars[java_at];
      if ( atchar == 0 )
        {
          /* Zero is encoded by 2 bytes in java utf8 */
          bytes[utf_at++] = (PRInt8)0xC0;
          bytes[utf_at++] = (PRInt8)0x80;
        }
      else if ( atchar <= UTF8_1BYTE_TRESHOLD )
        {
          bytes[utf_at++] = (PRInt8) atchar;
        }
      else if ( atchar <= UTF8_2BYTE_TRESHOLD )
        {
          bytes[utf_at++] = 0xC0 | ((atchar & 0x07C0)>>6);
          bytes[utf_at++] = 0x80 | ((atchar & 0x003f));
        }
      else
        {
          bytes[utf_at++] = 0xE0| ((atchar & 0xF000)>>12);
          bytes[utf_at++] = 0x80| ((atchar & 0x0FC0)>>6);
          bytes[utf_at++] = 0x80| ((atchar & 0x003f));
        }       
    }

  /* XXX Zero terminate C-string.  Is this needed? */
  bytes[utf_at] = 0;

  return bytes;
}

PR_IMPLEMENT(PRSize)
HVM_StringGetUTFLength(HungryEnv *henv,
                       japhar_object* jstr)
{
  PRSize length;
  PRSize utflength;
  PRUint32 i;
  PRUint16 atchar;
  PRInt32 offset = HVM_StringGetOffset(henv, jstr);
  japhar_object* char_array;
  PRUint16* chars;

  char_array = HVM_StringGetValue(henv, jstr);
  chars = (PRUint16*)HVM_ArrayGetBody(char_array);

  length = HVM_StringGetLength(henv, jstr);
  utflength = 0;
  for ( i=0; i < length; i++ )
    {
      atchar = chars[i+offset];
      if ( atchar == 0 )
        /* Zero is encoded by 2 bytes in java utf8 */
        utflength += 2;
      else if ( atchar <= UTF8_1BYTE_TRESHOLD )
        utflength += 1;
      else if ( atchar <= UTF8_2BYTE_TRESHOLD )
        utflength += 2;
      else
        utflength += 3;
    }
  
  return utflength;
}

PR_IMPLEMENT(PRUint32)
HVM_StringGetOffset(HungryEnv *henv,
                    japhar_object* jstr)
{
  init_ids(henv);

  if (offset_field)
    {
      InterpValue off_value;
      
      HVM_FieldGet(jstr, offset_field, &off_value);
      
      return off_value.i;
    }
  else
    {
      return 0;
    }
}

PR_IMPLEMENT(PRUint32)
HVM_StringGetLength(HungryEnv *henv,
                    japhar_object* jstr)
{
  InterpValue len_value;

  init_ids(henv);

  HVM_FieldGet(jstr, length_field, &len_value);
  
  return len_value.i;
}

PR_IMPLEMENT(japhar_object*)
HVM_StringGetValue(HungryEnv *henv,
                   japhar_object* jstr)
{
  InterpValue value_value;

  HVM_FieldGet(jstr, value_field, &value_value);
  
  return value_value.l;
}
