/* 
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "AndroidSystemNatives.h"
#include <JNIHelp.h>
#include "ErrorCode.h"
#include "unicode/ubidi.h"
#include <stdlib.h>
#include <string.h>

struct BiDiData {
    BiDiData(UBiDi* biDi) : mBiDi(biDi), embeddingLevels(NULL) {
    }
    ~BiDiData() {
        ubidi_close(mBiDi);
        delete[] embeddingLevels;
    }
    UBiDi* mBiDi;
    jbyte* embeddingLevels;
};

static BiDiData* biDiData(jlong ptr) {
    return reinterpret_cast<BiDiData*>(static_cast<uintptr_t>(ptr));
}

static jlong BidiWrapper_ubidi_open(JNIEnv* env, jclass) {
    return reinterpret_cast<uintptr_t>(new BiDiData(ubidi_open()));
}

static void BidiWrapper_ubidi_close(JNIEnv* env, jclass, jlong ptr) {
    delete biDiData(ptr);
}

static void BidiWrapper_ubidi_setPara(JNIEnv* env, jclass, jlong ptr, jcharArray text, jint length, jbyte paraLevel, jbyteArray newEmbeddingLevels) {
    BiDiData* data = biDiData(ptr);
    jbyte* oldEmbeddingLevels = data->embeddingLevels;
    // Copy the new embedding levels from the Java heap to the native heap.
    if (newEmbeddingLevels != NULL) {
        data->embeddingLevels = new jbyte[length];
        env->GetByteArrayRegion(newEmbeddingLevels, 0, length, data->embeddingLevels);
    } else {
        data->embeddingLevels = NULL;
    }
    UErrorCode err = U_ZERO_ERROR;
    jchar* _text = env->GetCharArrayElements(text, NULL);
    ubidi_setPara(data->mBiDi, _text, length, paraLevel, (UBiDiLevel*) data->embeddingLevels, &err);
    env->ReleaseCharArrayElements(text, _text, 0);
    delete[] oldEmbeddingLevels;
    icu4jni_error(env, err);
}

static jlong BidiWrapper_ubidi_setLine(JNIEnv* env, jclass, jlong ptr, jint start, jint limit) {
    UErrorCode err = U_ZERO_ERROR;
    UBiDi* sized = ubidi_openSized(limit - start, 0, &err);
    if (icu4jni_error(env, err)) {
        return 0;
    }
    BiDiData* lineData = new BiDiData(sized);
    BiDiData* data = biDiData(ptr);
    ubidi_setLine(data->mBiDi, start, limit, lineData->mBiDi, &err);
    icu4jni_error(env, err);
    return reinterpret_cast<uintptr_t>(lineData);
}

static jint BidiWrapper_ubidi_getDirection(JNIEnv * env, jclass clazz, jlong ptr) {
    return ubidi_getDirection(biDiData(ptr)->mBiDi);
}

static jint BidiWrapper_ubidi_getLength(JNIEnv* env, jclass, jlong ptr) {
    return ubidi_getLength(biDiData(ptr)->mBiDi);
}

static jbyte BidiWrapper_ubidi_getParaLevel(JNIEnv* env, jclass, jlong ptr) {
    return ubidi_getParaLevel(biDiData(ptr)->mBiDi);
}

static jbyteArray BidiWrapper_ubidi_getLevels(JNIEnv* env, jclass, jlong ptr) {
    BiDiData* data = biDiData(ptr);
    UErrorCode err = U_ZERO_ERROR;
    const UBiDiLevel* levels = ubidi_getLevels(data->mBiDi, &err);
    if (icu4jni_error(env, err)) {
        return NULL;
    }
    int len = ubidi_getLength(data->mBiDi);
    jbyteArray result = env->NewByteArray(len);
    env->SetByteArrayRegion(result, 0, len, reinterpret_cast<const jbyte*>(levels));
    return result;
}

static jint BidiWrapper_ubidi_countRuns(JNIEnv* env, jclass, jlong ptr) {
    BiDiData* data = biDiData(ptr);
    UErrorCode err = U_ZERO_ERROR;
    int count = ubidi_countRuns(data->mBiDi, &err);
    icu4jni_error(env, err);
    return count;
}

static jobjectArray BidiWrapper_ubidi_getRuns(JNIEnv* env, jclass, jlong ptr) {
    BiDiData* data = biDiData(ptr);
    UErrorCode err = U_ZERO_ERROR;
    int runCount = ubidi_countRuns(data->mBiDi, &err);
    if (icu4jni_error(env, err)) {
        return NULL;
    }
    jclass bidiRunClass = env->FindClass("org/apache/harmony/text/BidiRun");
    jmethodID bidiRunConstructor = env->GetMethodID(bidiRunClass, "<init>", "(III)V");
    jobjectArray runs = env->NewObjectArray(runCount, bidiRunClass, NULL);
    UBiDiLevel level = 0;
    int start = 0;
    int limit = 0;
    for (int i = 0; i < runCount; ++i) {
        ubidi_getLogicalRun(data->mBiDi, start, &limit, &level);
        jobject run = env->NewObject(bidiRunClass, bidiRunConstructor, start, limit, level);
        env->SetObjectArrayElement(runs, i, run);
        start = limit;
    }
    return runs;
}

static jintArray BidiWrapper_ubidi_reorderVisual(JNIEnv* env, jclass, jbyteArray levels, jint length) {
    int* local_indexMap = new int[length];
    jbyte* local_levelBytes = env->GetByteArrayElements(levels, NULL);
    UBiDiLevel* local_levels = reinterpret_cast<UBiDiLevel*>(local_levelBytes);
    ubidi_reorderVisual(local_levels, length, local_indexMap);
    jintArray result = env->NewIntArray(length);
    env->SetIntArrayRegion(result, 0, length, local_indexMap);
    delete[] local_indexMap;
    env->ReleaseByteArrayElements(levels, local_levelBytes, 0);
    return result;
}

static JNINativeMethod gMethods[] = {
    { "ubidi_close", "(J)V", (void*) BidiWrapper_ubidi_close },
    { "ubidi_countRuns", "(J)I", (void*) BidiWrapper_ubidi_countRuns },
    { "ubidi_getDirection", "(J)I", (void*) BidiWrapper_ubidi_getDirection },
    { "ubidi_getLength", "(J)I", (void*) BidiWrapper_ubidi_getLength },
    { "ubidi_getLevels", "(J)[B", (void*) BidiWrapper_ubidi_getLevels },
    { "ubidi_getParaLevel", "(J)B", (void*) BidiWrapper_ubidi_getParaLevel },
    { "ubidi_getRuns", "(J)[Lorg/apache/harmony/text/BidiRun;", (void*) BidiWrapper_ubidi_getRuns },
    { "ubidi_open", "()J", (void*) BidiWrapper_ubidi_open },
    { "ubidi_reorderVisual", "([BI)[I", (void*) BidiWrapper_ubidi_reorderVisual },
    { "ubidi_setLine", "(JII)J", (void*) BidiWrapper_ubidi_setLine },
    { "ubidi_setPara", "(J[CIB[B)V", (void*) BidiWrapper_ubidi_setPara },
};
int register_org_apache_harmony_text_BidiWrapper(JNIEnv* env) {
    return jniRegisterNativeMethods(env, "org/apache/harmony/text/BidiWrapper",
            gMethods, NELEM(gMethods));
}