/**
*******************************************************************************
* Copyright (C) 1996-2005, International Business Machines Corporation and    *
* others. All Rights Reserved.                                                *
*******************************************************************************
*
*******************************************************************************
*/

#include "JNIHelp.h"
#include "AndroidSystemNatives.h"
#include "ErrorCode.h"
#include "unicode/ucol.h"
#include "unicode/ucoleitr.h"
#include "ucol_imp.h"


/**
* Closing a C UCollator with the argument locale rules.
* Note determining if a collator currently exist for the caller is to be handled
* by the caller. Hence if the caller has a existing collator, it is his 
* responsibility to delete first before calling this method.
* @param env JNI environment
* @param obj RuleBasedCollatorJNI object
* @param address of the C UCollator
*/
static void closeCollator(JNIEnv *env, jclass obj,
        jint address) { 

  UCollator *collator = (UCollator *)(int)address;
  ucol_close(collator);
}


/**
* Close a C collation element iterator.
* @param env JNI environment
* @param obj RuleBasedCollatorJNI object
* @param address of C collation element iterator to close.
*/
static void closeElements(JNIEnv *env, jclass obj,
        jint address) {

  UCollationElements *iterator = (UCollationElements *)(int)address;
  ucol_closeElements(iterator);
}

/**
* Compare two strings.
* The strings will be compared using the normalization mode and options
* specified in openCollator or openCollatorFromRules
* @param env JNI environment
* @param obj RuleBasedCollatorJNI object
* @param address address of the c collator
* @param source The source string.
* @param target The target string.
* @return result of the comparison, UCOL_EQUAL, UCOL_GREATER or UCOL_LESS
*/
static jint compare(JNIEnv *env, jclass obj, jint address,
        jstring source, jstring target) {

    const UCollator *collator  = (const UCollator *)(int)address;
    jint result = -2;
    if(collator){
        jsize       srcLength = env->GetStringLength(source);
        const UChar *chars   = (const UChar *) env->GetStringCritical(source,0);
        if(chars){
            jsize       tgtlength = env->GetStringLength(target);
            const UChar *tgtstr    = (const UChar *) env->GetStringCritical(target,0);
            if(tgtstr){ 
                  result = ucol_strcoll(collator, chars, srcLength, tgtstr, tgtlength);
                  env->ReleaseStringCritical(source, chars);
                  env->ReleaseStringCritical(target, tgtstr);
                  return result;
            }else{
                icu4jni_error(env,U_ILLEGAL_ARGUMENT_ERROR);
            }
        }else{
            icu4jni_error(env,U_ILLEGAL_ARGUMENT_ERROR);
        }
    }else{
        icu4jni_error(env,U_ILLEGAL_ARGUMENT_ERROR);
    }
    return result;
}

/**
* Universal attribute getter
* @param env JNI environment
* @param obj RuleBasedCollatorJNI object
* @param address address of the C collator
* @param type type of attribute to be set
* @return attribute value
* @exception thrown when error occurs while getting attribute value
*/
static jint getAttribute(JNIEnv *env, jclass obj, jint address,
        jint type) {

    const UCollator *collator = (const UCollator *)(int)address;
    UErrorCode status = U_ZERO_ERROR;
    if(collator){
        jint result = (jint)ucol_getAttribute(collator, (UColAttribute)type, 
                                            &status);
        if (icu4jni_error(env, status) != FALSE){
            return (jint)UCOL_DEFAULT;
        }
        return result;
    }else{
        icu4jni_error(env,U_ILLEGAL_ARGUMENT_ERROR);
    }
    return (jint)UCOL_DEFAULT;
}

/** 
* Create a CollationElementIterator object that will iterator over the elements 
* in a string, using the collation rules defined in this RuleBasedCollatorJNI
* @param env JNI environment
* @param obj RuleBasedCollatorJNI object
* @param address address of C collator
* @param source string to iterate over
* @return address of C collationelement
*/
static jint getCollationElementIterator(JNIEnv *env,
        jclass obj, jint address, jstring source) {

    UErrorCode status    = U_ZERO_ERROR;
    UCollator *collator  = (UCollator *)(int)address;
    jint       result=0;
    if(collator){
        jsize srcLength     = env->GetStringLength(source);
        const UChar *chars = (const UChar *) env->GetStringCritical(source,0);
        if(chars){
            result = (jint)(ucol_openElements(collator, chars, srcLength, &status));

            env->ReleaseStringCritical(source, chars);
            icu4jni_error(env, status);
        }else{
            icu4jni_error(env, U_ILLEGAL_ARGUMENT_ERROR);
        }
    }else{
        icu4jni_error(env, U_ILLEGAL_ARGUMENT_ERROR);
    }
    return result;
}

/**
* Get the maximum length of any expansion sequences that end with the specified 
* comparison order.
* @param env JNI environment
* @param obj RuleBasedCollatorJNI object
* @param address of the C collation element iterator containing the text.
* @param order collation order returned by previous or next.
* @return maximum length of any expansion sequences ending with the specified 
*         order or 1 if collation order does not occur at the end of any 
*         expansion sequence.
*/
static jint getMaxExpansion(JNIEnv *env, jclass obj,
        jint address, jint order) {

  UCollationElements *iterator = (UCollationElements *)(int)address;
  return ucol_getMaxExpansion(iterator, order);
}

/**
* Get the normalization mode for this object.
* The normalization mode influences how strings are compared.
* @param env JNI environment
* @param obj RuleBasedCollatorJNI object
* @param address of C collator
* @return normalization mode; one of the values from NormalizerEnum
*/
static jint getNormalization(JNIEnv *env, jclass obj,
        jint address) {

    const UCollator* collator = (const UCollator*) address;
    UErrorCode status = U_ZERO_ERROR;
    jint result = ucol_getAttribute(collator, UCOL_NORMALIZATION_MODE, &status);
    icu4jni_error(env, status);
    return result;
}

/**
* Set the normalization mode for this object.
* The normalization mode influences how strings are compared.
* @param env JNI environment
* @param obj RuleBasedCollatorJNI object
* @param address of C collator
* @param mode the normalization mode
*/
static void setNormalization(JNIEnv *env, jclass, jint address, jint mode) {
    UCollator* collator = reinterpret_cast<UCollator*>(static_cast<uintptr_t>(address));
    UErrorCode status = U_ZERO_ERROR;
    ucol_setAttribute(collator, UCOL_NORMALIZATION_MODE, UColAttributeValue(mode), &status);
    icu4jni_error(env, status);
}


/**
* Get the offset of the current source character.
* This is an offset into the text of the character containing the current
* collation elements.
* @param env JNI environment
* @param obj RuleBasedCollatorJNI object
* @param addresss of the C collation elements iterator to query.
* @return offset of the current source character.
*/
static jint getOffset(JNIEnv *env, jclass obj, jint address) {

  UCollationElements *iterator = (UCollationElements *)(int)address;
  return ucol_getOffset(iterator);
}

/**
* Get the collation rules from a UCollator.
* The rules will follow the rule syntax.
* @param env JNI environment
* @param obj RuleBasedCollatorJNI object
* @param address the address of the C collator
* @return collation rules.
*/
static jstring getRules(JNIEnv *env, jclass obj,
        jint address) {

  const UCollator *collator = (const UCollator *)(int)address;
  int32_t length=0;
  const UChar *rules = ucol_getRules(collator, &length);
  return env->NewString(rules, length);
}

/**
* Get a sort key for the argument string
* Sort keys may be compared using java.util.Arrays.equals
* @param env JNI environment
* @param obj RuleBasedCollatorJNI object
* @param address address of the C collator
* @param source string for key to be generated
* @return sort key
*/
static jbyteArray getSortKey(JNIEnv *env, jclass obj,
        jint address, jstring source) {

  const UCollator *collator  = (const UCollator *)(int)address;
  jbyteArray result = NULL;
  if(collator && source){
      // BEGIN android-added
      if(!source) {
          return NULL;
      }
      // END android-added
      jsize srcLength = env->GetStringLength(source);
      const UChar *chars = (const UChar *) env->GetStringCritical(source, 0);
      if (chars){
// BEGIN android-changed
          uint8_t byteArray[UCOL_MAX_BUFFER * 2];
          uint8_t *largerByteArray = NULL;
          uint8_t *usedByteArray = byteArray;
  
          size_t byteArraySize = ucol_getSortKey(collator, chars, srcLength, byteArray,
                  sizeof(byteArray) - 1);
 
          if (byteArraySize > sizeof(byteArray) - 1) {
              // didn't fit, try again with a larger buffer.
              largerByteArray = new uint8_t[byteArraySize + 1];
              usedByteArray = largerByteArray;
              byteArraySize = ucol_getSortKey(collator, chars, srcLength, largerByteArray,
                      byteArraySize);
          }
 
          env->ReleaseStringCritical(source, chars);

          if (byteArraySize == 0) {
              delete[] largerByteArray;
              return NULL;
          }
  
          /* no problem converting uint8_t to int8_t, gives back the correct value
           * tried and tested
           */
          result = env->NewByteArray(byteArraySize);
          env->SetByteArrayRegion(result, 0, byteArraySize, reinterpret_cast<jbyte*>(usedByteArray));
          delete[] largerByteArray;
// END android-changed
      }else{
          icu4jni_error(env,U_ILLEGAL_ARGUMENT_ERROR);
      }
  }else{
    icu4jni_error(env,U_ILLEGAL_ARGUMENT_ERROR);
  }
  return result;
}

/**
* Returns a hash of this collation object
* Note this method is not complete, it only returns 0 at the moment.
* @param env JNI environment
* @param obj RuleBasedCollatorJNI object
* @param address address of C collator
* @return hash of this collation object
*/
static jint hashCode(JNIEnv *env, jclass obj, jint address) {
	
  UCollator *collator = (UCollator *)(int)address;
  int32_t length=0;
  const UChar *rules = ucol_getRules(collator, &length);
  /* temporary commented out
   * return uhash_hashUCharsN(rules, length);
   */
  return 0;
}

/**
* Get the ordering priority of the next collation element in the text.
* A single character may contain more than one collation element.
* @param env JNI environment
* @param obj RuleBasedCollatorJNI object
* @param address if C collation elements containing the text.
* @return next collation elements ordering, otherwise returns NULLORDER if an 
*         error has occured or if the end of string has been reached
*/
static jint next(JNIEnv *env, jclass obj, jint address) {
    UCollationElements *iterator = (UCollationElements *) address;
    UErrorCode status = U_ZERO_ERROR;
    jint result = ucol_next(iterator, &status);
    icu4jni_error(env, status);
    return result;
}

/**
* Opening a new C UCollator with the default locale.
* Note determining if a collator currently exist for the caller is to be handled
* by the caller. Hence if the caller has a existing collator, it is his 
* responsibility to delete first before calling this method.
* @param env JNI environment
* @param obj RuleBasedCollatorJNI object
* @return address of the new C UCollator
* @exception thrown if creation of the UCollator fails
*/
static jint openCollator__(JNIEnv *env, jclass obj) {
    UErrorCode status = U_ZERO_ERROR;
    UCollator* result = ucol_open(NULL, &status);
    icu4jni_error(env, status);
    return reinterpret_cast<uintptr_t>(result);
}


/**
* Opening a new C UCollator with the argument locale rules.
* Note determining if a collator currently exist for the caller is to be handled
* by the caller. Hence if the caller has a existing collator, it is his 
* responsibility to delete first before calling this method.
* @param env JNI environment
* @param obj RuleBasedCollatorJNI object
* @param locale name
* @return address of the new C UCollator
* @exception thrown if creation of the UCollator fails
*/
static jint openCollator__Ljava_lang_String_2(JNIEnv *env,
        jclass obj, jstring locale) {

    /* this will be null terminated */
    const char* localeStr = env->GetStringUTFChars(locale, NULL);
    if (localeStr == NULL) {
        icu4jni_error(env, U_ILLEGAL_ARGUMENT_ERROR);
        return 0;
    }

    UErrorCode status = U_ZERO_ERROR;
    UCollator* result = ucol_open(localeStr, &status);
    env->ReleaseStringUTFChars(locale, localeStr);
    icu4jni_error(env, status);
    return reinterpret_cast<uintptr_t>(result);
}

/**
* Opening a new C UCollator with the argument locale rules.
* Note determining if a collator currently exist for the caller is to be 
* handled by the caller. Hence if the caller has a existing collator, it is his 
* responsibility to delete first before calling this method.
* @param env JNI environment
* @param obj RuleBasedCollatorJNI object
* @param rules set of collation rules
* @param normalizationmode normalization mode
* @param strength collation strength
* @return address of the new C UCollator
* @exception thrown if creation of the UCollator fails
*/
static jint openCollatorFromRules(JNIEnv *env, jclass obj,
        jstring rules, jint normalizationmode, jint strength) {

  jsize  ruleslength    = env->GetStringLength(rules);
  const UChar *rulestr  = (const UChar *) env->GetStringCritical(rules, 0);
  UErrorCode status     = U_ZERO_ERROR;
  jint   result        = 0;
  if(rulestr){
      result = (jint)ucol_openRules(rulestr, ruleslength, 
                                   (UColAttributeValue)normalizationmode,
                                   (UCollationStrength)strength, NULL, &status);

      env->ReleaseStringCritical(rules, rulestr);
      icu4jni_error(env, status);
  }else{
      icu4jni_error(env,U_ILLEGAL_ARGUMENT_ERROR);
  }

  return result;
}

/**
* Get the ordering priority of the previous collation element in the text.
* A single character may contain more than one collation element.
* @param env JNI environment
* @param obj RuleBasedCollatorJNI object
* @param address of the C collation element iterator containing the text.
* @return previous collation element ordering, otherwise returns NULLORDER if 
*         an error has occured or if the start of string has been reached
* @exception thrown when retrieval of previous collation element fails.
*/
static jint previous(JNIEnv *env, jclass obj, jint address) {

  UCollationElements *iterator = (UCollationElements *)(int)address;
  UErrorCode status = U_ZERO_ERROR;
  jint result = ucol_previous(iterator, &status);

   icu4jni_error(env, status);
  return result;
}


/**
* Reset the collation elements to their initial state.
* This will move the 'cursor' to the beginning of the text.
* @param env JNI environment
* @param obj RuleBasedCollatorJNI object
* @param address of C collation element iterator to reset.
*/
static void reset(JNIEnv *env, jclass obj, jint address) {

  UCollationElements *iterator = (UCollationElements *)(int)address;
  ucol_reset(iterator);
}

/**
* Thread safe cloning operation
* @param env JNI environment
* @param obj RuleBasedCollatorJNI object
* @param address address of C collator to be cloned
* @return address of the new clone
* @exception thrown when error occurs while cloning
*/
static jint safeClone(JNIEnv *env, jclass obj, jint address) {

  const UCollator *collator = (const UCollator *)(int)address;
  UErrorCode status = U_ZERO_ERROR;
  jint result;
  jint buffersize = U_COL_SAFECLONE_BUFFERSIZE;

  result = (jint)ucol_safeClone(collator, NULL, &buffersize, &status);

  if ( icu4jni_error(env, status) != FALSE) {
    return 0;
  }
 
  return result;
}

/**
* Universal attribute setter.
* @param env JNI environment
* @param obj RuleBasedCollatorJNI object
* @param address address of the C collator
* @param type type of attribute to be set
* @param value attribute value
* @exception thrown when error occurs while setting attribute value
*/
static void setAttribute(JNIEnv *env, jclass obj, jint address,
        jint type, jint value) {

  UCollator *collator = (UCollator *)(int)address;
  UErrorCode status = U_ZERO_ERROR;
  ucol_setAttribute(collator, (UColAttribute)type, (UColAttributeValue)value, 
                    &status);
   icu4jni_error(env, status);
}

/**
* Set the offset of the current source character.
* This is an offset into the text of the character to be processed.
* @param env JNI environment
* @param obj RuleBasedCollatorJNI object
* @param address of the C collation element iterator to set.
* @param offset The desired character offset.
* @exception thrown when offset setting fails
*/
static void setOffset(JNIEnv *env, jclass obj, jint address,
        jint offset) {

  UCollationElements *iterator = (UCollationElements *)(int)address;
  UErrorCode status = U_ZERO_ERROR;

  ucol_setOffset(iterator, offset, &status);
   icu4jni_error(env, status);
}

/**
* Set the text containing the collation elements.
* @param env JNI environment
* @param obj RuleBasedCollatorJNI object
* @param address of the C collation element iterator to be set
* @param source text containing the collation elements.
* @exception thrown when error occurs while setting offset
*/
static void setText(JNIEnv *env, jclass obj, jint address,
        jstring source) {

  UCollationElements *iterator = (UCollationElements *)(int)address;
  UErrorCode status = U_ZERO_ERROR;
  int strlength = env->GetStringLength(source);
  const UChar *str = (const UChar *) env->GetStringCritical(source, 0);
  
  ucol_setText(iterator, str, strlength, &status);
  env->ReleaseStringCritical(source, str);

   icu4jni_error(env, status);
}

static jobjectArray getAvailableLocalesImpl(JNIEnv *env, jclass clazz) {
    jclass stringClass = env->FindClass("java/lang/String");
    if (stringClass == NULL) {
        return NULL;
    }
    size_t count = ucol_countAvailable();
    jobjectArray result = env->NewObjectArray(count, stringClass, NULL);
    for (size_t i = 0; i < count; ++i) {
        jstring s = env->NewStringUTF(ucol_getAvailable(i));
        env->SetObjectArrayElement(result, i, s);
        env->DeleteLocalRef(s);
    }
    return result;
}

static JNINativeMethod gMethods[] = {
    /* name, signature, funcPtr */
    { "getAvailableLocalesImpl", "()[Ljava/lang/String;", (void*) getAvailableLocalesImpl },
    { "openCollator", "()I", (void*) openCollator__ },
    { "openCollator", "(Ljava/lang/String;)I", (void*) openCollator__Ljava_lang_String_2 },
    { "openCollatorFromRules", "(Ljava/lang/String;II)I", (void*) openCollatorFromRules },
    { "closeCollator", "(I)V", (void*) closeCollator },
    { "compare", "(ILjava/lang/String;Ljava/lang/String;)I", (void*) compare },
    { "getNormalization", "(I)I", (void*) getNormalization },
    { "setNormalization", "(II)V", (void*) setNormalization },
    { "getRules", "(I)Ljava/lang/String;", (void*) getRules },
    { "getSortKey", "(ILjava/lang/String;)[B", (void*) getSortKey },
    { "setAttribute", "(III)V", (void*) setAttribute },
    { "getAttribute", "(II)I", (void*) getAttribute },
    { "safeClone", "(I)I", (void*) safeClone },
    { "getCollationElementIterator", "(ILjava/lang/String;)I", (void*) getCollationElementIterator },
    { "hashCode", "(I)I", (void*) hashCode },
    { "closeElements", "(I)V", (void*) closeElements },
    { "reset", "(I)V", (void*) reset },
    { "next", "(I)I", (void*) next },
    { "previous", "(I)I", (void*) previous },
    { "getMaxExpansion", "(II)I", (void*) getMaxExpansion },
    { "setText", "(ILjava/lang/String;)V", (void*) setText },
    { "getOffset", "(I)I", (void*) getOffset },
    { "setOffset", "(II)V", (void*) setOffset }
};

int register_com_ibm_icu4jni_text_NativeCollator(JNIEnv *_env) { 
    return jniRegisterNativeMethods(_env, "com/ibm/icu4jni/text/NativeCollation",
                gMethods, NELEM(gMethods));
}