/*
 *
 *
 * Copyright  1990-2006 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation. 
 * 
 * This program 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
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt). 
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA 
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions. 
 */

#include <stdlib.h>
#include <utf.h>

pcsl_string_status utf8_convert_to_utf16(const jbyte * str, jsize str_length,
			      jchar * buffer, jsize buffer_length,
			      jsize * converted_length) {
  const jbyte * const str_end = str + str_length;

  jbyte byte1 = 0;
  jbyte byte2 = 0;
  jbyte byte3 = 0;
  jbyte byte4 = 0;
  jchar output_char[2] = { 0 };
  jsize output_size = 0;
  jsize output_off = 0;
  jboolean buffer_overflow = PCSL_FALSE;

  if (str == NULL) {
    return PCSL_STRING_EINVAL;
  }

  while (str < str_end) {
    byte1 = *str++;

    if ((byte1 & 0x80) == 0){
      output_char[0] = (jchar)byte1;
      output_size = 1;
    } else if ((byte1 & 0xe0) == 0xc0) {
      if (str >= str_end) {
	return PCSL_STRING_EILSEQ;
      }
      byte2 = *str++;
      if ((byte2 & 0xc0) != 0x80) {
	return PCSL_STRING_EILSEQ;
      }
      output_char[0] = (jchar)(((byte1 & 0x1f) << 6) | (byte2 & 0x3f));
      output_size = 1;
    } else if ((byte1 & 0xf0) == 0xe0){
      if (str + 1 >= str_end) {
	return PCSL_STRING_EILSEQ;
      }
      byte2 = *str++;
      byte3 = *str++;
      if ((byte2 & 0xc0) != 0x80 || (byte3 & 0xc0) != 0x80) {
	return PCSL_STRING_EILSEQ;
      }
      output_char[0] = (jchar)(((byte1 & 0x0f) << 12)
			     | ((byte2 & 0x3f) << 6)
			     | (byte3 & 0x3f));
      output_size = 1;
    } else if ((byte1 & 0xf8) == 0xf0) {
      if (str + 2 >= str_end) {
	return PCSL_STRING_EILSEQ;
      }
      byte2 = *str++;
      byte3 = *str++;
      byte4 = *str++;
      if ((byte2 & 0xc0) != 0x80 ||
	  (byte3 & 0xc0) != 0x80 ||
	  (byte4 & 0xc0) != 0x80) {
	return PCSL_STRING_EILSEQ;
      }
      {
	// this byte sequence is UTF16 character
	jint ucs4 = (jint)(0x07 & byte1) << 18 |
	  (jint)(0x3f & byte2) << 12 |
	  (jint)(0x3f & byte3) <<  6 |
	  (jint)(0x3f & byte4);
	output_char[0] = (jchar)((ucs4 - 0x10000) / 0x400 + 0xd800);
	output_char[1] = (jchar)((ucs4 - 0x10000) % 0x400 + 0xdc00);
	output_size = 2;
      }
    } else {
      return PCSL_STRING_EILSEQ;
    }

    if (buffer_overflow == PCSL_FALSE && buffer != NULL) {
      if (output_off + output_size > buffer_length) {
	buffer_overflow = PCSL_TRUE;
      } else {
	int i;
	for (i = 0; i < output_size; i++) {
	  buffer[output_off + i] = output_char[i];
	}
      }
    }

    output_off += output_size;
  }

  if (converted_length != NULL) {
    *converted_length = output_off;
  }

  return buffer_overflow == PCSL_TRUE ? PCSL_STRING_BUFFER_OVERFLOW : PCSL_STRING_OK;
}

pcsl_string_status utf16_convert_to_utf8(const jchar * str, jsize str_length,
			      jbyte * buffer, jsize buffer_length,
			      jsize * converted_length) {
  const jchar * const str_end = str + str_length;
  const jbyte * const buffer_end = buffer + buffer_length;

  jchar input_char = 0;
  jbyte output_byte[6] = { 0 };
  jsize output_size = 0;
  jsize output_off = 0;
  jboolean buffer_overflow = PCSL_FALSE;

  if (str == NULL) {
    return PCSL_STRING_EINVAL;
  }

  while(str < str_end) {
    input_char = *str++;
    if (input_char < 0x80) {
      output_byte[0] = (jbyte)input_char;
      output_size = 1;
    } else if (input_char < 0x800) {
      output_byte[0] = (jbyte)(0xc0 | ((input_char >> 6) & 0x1f));
      output_byte[1] = (jbyte)(0x80 | (input_char & 0x3f));
      output_size = 2;
    } else if (input_char >= 0xd800 && input_char <= 0xdbff) {
      // this is <high-half zone code> in UTF-16
      if (str >= str_end) {
	return PCSL_STRING_EILSEQ;
      } else {
	// check next char is valid <low-half zone code>
	jchar low_char = *str++;

	if (low_char < 0xdc00 || low_char > 0xdfff) {
	  return PCSL_STRING_EILSEQ;
	} else {
	  int ucs4 =
	    (input_char - 0xd800) * 0x400 + (low_char - 0xdc00) + 0x10000;
	  output_byte[0] = (jbyte)(0xf0 | ((ucs4 >> 18)) & 0x07);
	  output_byte[1] = (jbyte)(0x80 | ((ucs4 >> 12) & 0x3f));
	  output_byte[2] = (jbyte)(0x80 | ((ucs4 >> 6) & 0x3f));
	  output_byte[3] = (jbyte)(0x80 | (ucs4 & 0x3f));
	  output_size = 4;
	}
      }
    } else {
      output_byte[0] = (jbyte)(0xe0 | ((input_char >> 12)) & 0x0f);
      output_byte[1] = (jbyte)(0x80 | ((input_char >> 6) & 0x3f));
      output_byte[2] = (jbyte)(0x80 | (input_char & 0x3f));
      output_size = 3;
    }

    if (buffer_overflow == PCSL_FALSE && buffer != NULL) {
      if (output_off + output_size > buffer_length) {
	buffer_overflow = PCSL_TRUE;
      } else {
	int i;
	for (i = 0; i < output_size; i++) {
	  buffer[output_off + i] = output_byte[i];
	}
      }
    }


    output_off += output_size;
  }

  if (converted_length != NULL) {
    *converted_length = output_off;
  }

  return buffer_overflow == PCSL_TRUE ? PCSL_STRING_BUFFER_OVERFLOW : PCSL_STRING_OK;
}

/**
 * Converts the Unicode code point to UTF-16 code unit.
 * See Unicode Glossary at http://www.unicode.org/glossary/.
 * High surrogate is stored in code_unit[0],
 * low surrogate is stored in code_unit[1].
 *
 * @param code_point  Unicode code point
 * @param code_unit   Storage for UTF-16 code unit
 * @param unit_length Storage for the number of 16-bit units
 *                    in the UTF-16 code unit
 * @return status of the conversion
 */
pcsl_string_status code_point_to_utf16_code_unit(jint code_point,
				      jchar code_unit[2],
				      jsize * unit_length) {

  if ((code_point & 0xffff0000) == 0) {
    // handle most cases here (ch is a BMP code point)
    * unit_length = 1;
    code_unit[0] = (jchar)(code_point & 0xffff);
    return PCSL_STRING_OK;
  } else if (code_point < 0 || code_point > 0x10ffff) {
    return PCSL_STRING_EINVAL;
  } else {
    const jint offset = code_point - 0x10000;
    code_unit[0] = (jchar)((offset >> 10) + 0xd800);
    code_unit[1] = (jchar)((offset & 0x3ff) + 0xdc00);
    * unit_length = 2;
    return PCSL_STRING_OK;
  }
}

/**
 * Returns the number of abstract characters in the string specified
 * by the UTF-16 code unit sequence.
 * Returns -1 if str is NULL or is not a valid UTF-16 code unit sequence.
 * See Unicode Glossary at http://www.unicode.org/glossary/.
 *
 * @param str           UTF-16 code unit sequence
 * @param str_length    number of UTF-16 code units in the sequence
 * @return number of abstract characters in the string
 */
jsize utf16_string_length(jchar * str, jsize str_length) {
  const jchar * const str_end = str + str_length;
  jsize char_count = 0;
  jchar input_char = 0;

  if (str == NULL) {
    return -1;
  }

  while(str < str_end) {
    input_char = *str++;
    char_count++;
    if (input_char >= 0xd800 && input_char <= 0xdbff) {
      // this is <high-half zone code> in UTF-16
      if (str >= str_end) {
	return -1;
      } else {
	// check next char is valid <low-half zone code>
	jchar low_char = *str++;

	if (low_char < 0xdc00 || low_char > 0xdfff) {
	  return -1;
	}
      }
    }
  }

  return char_count;
}

/**
 * Convert a Unicode string into a form that can be safely stored on
 * an ANSI-compatible file system. All characters that are not
 * [A-Za-z0-9] are converted into %uuuu, where uuuu is the hex
 * representation of the character's unicode value. Note even
 * though "_" is allowed it is converted because we use it for
 * for internal purposes. Potential file separators are converted
 * so the storage layer does not have deal with sub-directory hierarchies.
 *
 * @param str_data buffer with a string that may contain any unicode character
 * @param str_len length of the string pointed to by str_data
 * @param pBuffer a buffer that is at least 5 times the size of str after
 *        the offset, must not be the memory as str
 * @param offset where to start putting the characters
 *
 * @return number of characters put in pBuffer
 */
int unicode_to_escaped_ascii(const jchar* str_data, const int str_len,
                             jchar* pBuffer, int offset) {
    static jchar NUMS[] = {
        '0', '1', '2', '3', '4', '5', '6', '7',
        '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
    };

    int i;
    int j;

    for (i = 0, j = offset; i < str_len; i++) {
        jchar c = str_data[i];

        if ((c >= 'a' && c <= 'z') ||
            (c >= '0' && c <= '9')) {
            pBuffer[j++] = c;
        } else if (c >= 'A' && c <= 'Z') {
            /* Some file systems do not treat capital letters differently. */
            pBuffer[j++] = '#';
            pBuffer[j++] = c;
        } else {
            pBuffer[j++] = '%';
            pBuffer[j++] = NUMS[(c >> 12) & 0x000f];
            pBuffer[j++] = NUMS[(c >>  8) & 0x000f];
            pBuffer[j++] = NUMS[(c >>  4) & 0x000f];
            pBuffer[j++] = NUMS[c & 0x000f];
        }
    }

    return j - offset;
}
