#include <string.h>
#include <jni.h>
#include <stdint.h>

#include <android/log.h>

#include "hqxcommon.h"
#include "hqx.h"

#define MAX(a,b) (((a) > (b)) ? (a) : (b))
#define MIN(a,b) (((a) < (b)) ? (a) : (b))

#define DEBUG(args...) \
    __android_log_print(ANDROID_LOG_DEBUG, "EBookDroid.ByteBufferBitmap", args)

#define ERROR(args...) \
    __android_log_print(ANDROID_LOG_ERROR, "EBookDroid.ByteBufferBitmap", args)

#define INFO(args...) \
    __android_log_print(ANDROID_LOG_INFO, "EBookDroid.ByteBufferBitmap", args)

JNIEXPORT jobject JNICALL
Java_org_ebookdroid_common_bitmaps_ByteBufferBitmap_create(JNIEnv* env, jclass classObject, jint size)
{
    void* buf = malloc(size);
    jobject obj = (*env)->NewDirectByteBuffer(env, buf, size);
    return (*env)->NewGlobalRef(env, obj);
}

JNIEXPORT void JNICALL
Java_org_ebookdroid_common_bitmaps_ByteBufferBitmap_free(JNIEnv* env, jclass classObject, jobject obj)
{
    if (!obj)
    {
        return;
    }
    void* buf = (*env)->GetDirectBufferAddress(env, obj);
    if (!buf)
    {
        return;
    }
    free(buf);
}

JNIEXPORT void JNICALL
Java_org_ebookdroid_common_bitmaps_ByteBufferBitmap_nativeHq4x(JNIEnv* env, jclass classObject, jobject srcBuffer,
                                                          jobject dstBuffer, jint width, jint height)
{

    uint32_t* src;
    uint32_t* dst;

    src = (uint32_t*)((*env)->GetDirectBufferAddress(env, srcBuffer));
    if (!src) {
    	ERROR("Can not get direct buffer");
    	return;
    }
    dst = (uint32_t*)((*env)->GetDirectBufferAddress(env, dstBuffer));
    if (!dst) {
    	ERROR("Can not get direct buffer");
    	return;
    }

    hq4x_32(src, dst, width, height);
}

JNIEXPORT void JNICALL
 Java_org_ebookdroid_common_bitmaps_ByteBufferBitmap_nativeHq3x(JNIEnv* env, jclass classObject, jobject srcBuffer,
         jobject dstBuffer, jint width, jint height)
{

    uint32_t* src;
    uint32_t* dst;

    src = (uint32_t*)((*env)->GetDirectBufferAddress(env, srcBuffer));
    if (!src) {
    	ERROR("Can not get direct buffer");
    	return;
    }
    dst = (uint32_t*)((*env)->GetDirectBufferAddress(env, dstBuffer));
    if (!dst) {
    	ERROR("Can not get direct buffer");
    	return;
    }

    hq3x_32(src, dst, width, height);
}

JNIEXPORT void JNICALL
 Java_org_ebookdroid_common_bitmaps_ByteBufferBitmap_nativeHq2x(JNIEnv* env, jclass classObject, jobject srcBuffer,
         jobject dstBuffer, jint width, jint height)
{
    uint32_t* src;
    uint32_t* dst;

    src = (uint32_t*)((*env)->GetDirectBufferAddress(env, srcBuffer));
    if (!src) {
    	ERROR("Can not get direct buffer");
    	return;
    }
    dst = (uint32_t*)((*env)->GetDirectBufferAddress(env, dstBuffer));
    if (!dst) {
    	ERROR("Can not get direct buffer");
    	return;
    }

    hq2x_32(src, dst, width, height);
}


JNIEXPORT void JNICALL
 Java_org_ebookdroid_common_bitmaps_ByteBufferBitmap_nativeInvert(JNIEnv* env, jclass classObject, jobject srcBuffer,
                                                          jint width, jint height)
{
	int i;

	uint8_t* src;
    src = (uint8_t*)((*env)->GetDirectBufferAddress(env, srcBuffer));
    if (!src) {
    	ERROR("Can not get direct buffer");
    	return;
    }

    for (i = 0; i < width * height * 4; i+=4) {
    	int bright = (((src[i+2] * 77 + src[i + 1] * 150 + src[i] * 29) >> 8) ^ 0xFF) & 0xFF;
    	src[i] = bright;
    	src[i + 1] = bright;
    	src[i + 2] = bright;
    }
}

JNIEXPORT void JNICALL
 Java_org_ebookdroid_common_bitmaps_ByteBufferBitmap_nativeFillAlpha(JNIEnv* env, jclass classObject, jobject srcBuffer,
                                                          jint width, jint height, jint value)
{
	int i;

	uint8_t* src;
    src = (uint8_t*)((*env)->GetDirectBufferAddress(env, srcBuffer));
    if (!src) {
    	ERROR("Can not get direct buffer");
    	return;
    }

    for (i = 0; i < width * height * 4; i+=4) {
    	src[i + 3] = value;
    }
}

JNIEXPORT void JNICALL
 Java_org_ebookdroid_common_bitmaps_ByteBufferBitmap_nativeEraseColor(JNIEnv* env, jclass classObject, jobject srcBuffer,
                                                          jint width, jint height, jint c)
{
	int i;

	uint8_t* src;
    src = (uint8_t*)((*env)->GetDirectBufferAddress(env, srcBuffer));
    if (!src) {
    	ERROR("Can not get direct buffer");
    	return;
    }
    uint8_t a = (uint8_t)((c >> 24) & 0xFF);
    uint8_t r = (uint8_t)((c >> 16) & 0xFF);
    uint8_t g = (uint8_t)((c >> 8) & 0xFF);
    uint8_t b = (uint8_t)(c & 0xFF);

    for (i = 0; i < width * height * 4; i+=4) {
    	src[i + 0] = r;
    	src[i + 1] = g;
    	src[i + 2] = b;
    	src[i + 3] = a;
    }
}

JNIEXPORT void JNICALL
 Java_org_ebookdroid_common_bitmaps_ByteBufferBitmap_nativeContrast(JNIEnv* env, jclass classObject, jobject srcBuffer,
                                                          jint width, jint height, jint contrast)
{
    int i, a;
    uint8_t buf[256];
    int midBright = 0;
    uint8_t* src;
    src = (uint8_t*)((*env)->GetDirectBufferAddress(env, srcBuffer));
    if (!src) {
    	ERROR("Can not get direct buffer");
    	return;
    }

    for (i = 0; i < width * height * 4; i += 4) {
        midBright += src[i+2] * 77 + src[i + 1] * 150 + src[i] * 29;
    }
    midBright /= (256 * width * height);

    for (i = 0; i < 256; i++) {
        a = (((i - midBright) * contrast) / 256) + midBright;
        if (a < 0) {
            buf[i] = 0;
        } else if (a > 255) {
            buf[i] = 255;
        } else {
            buf[i] = a;
        }
    }

    for (i = 0; i < width * height * 4; i+=4) {
        src[i] = buf[src[i]];
        src[i+1] = buf[src[i+1]];
        src[i+2] = buf[src[i+2]];
    }
}

JNIEXPORT jint JNICALL
 Java_org_ebookdroid_common_bitmaps_ByteBufferBitmap_nativeAvgLum(JNIEnv* env, jclass classObject, jobject srcBuffer,
                                                          jint width, jint height, jint contrast)
{
    int i, a;
    int midBright = 0;
    uint8_t* src;
    src = (uint8_t*)((*env)->GetDirectBufferAddress(env, srcBuffer));
    if (!src) {
    	ERROR("Can not get direct buffer");
    	return;
    }

    for (i = 0; i < width * height * 4; i += 4) {
        midBright += (MIN(src[i+2],MIN(src[i + 1],src[i])) + MAX(src[i+2],MAX(src[i + 1],src[i]))) / 2;
    }
    midBright /= (width * height);

	return (jint) midBright;
}

JNIEXPORT void JNICALL
 Java_org_ebookdroid_common_bitmaps_ByteBufferBitmap_nativeExposure(JNIEnv* env, jclass classObject, jobject srcBuffer,
                                                          jint width, jint height, jint exp)
{
    int i, a;
    uint8_t* src;
    src = (uint8_t*)((*env)->GetDirectBufferAddress(env, srcBuffer));
    if (!src) {
    	ERROR("Can not get direct buffer");
    	return;
    }

    for (i = 0; i < width * height * 4; i += 4) {
        src[i] = MIN(MAX(src[i] + exp * 11 / 100, 0), 255);
        src[i+1] = MIN(MAX(src[i+1] + exp * 59 / 100, 0), 255);
        src[i+2] = MIN(MAX(src[i+2] + exp * 30 / 100, 0), 255);
    }
}

JNIEXPORT void JNICALL
 Java_org_ebookdroid_common_bitmaps_ByteBufferBitmap_nativeAutoLevels(JNIEnv* env, jclass classObject, jobject srcBuffer,
                                                          jint width, jint height)
{
    int i;
    int histoR[256];
    int cumulativeFreqR[256];
    int histoG[256];
    int cumulativeFreqG[256];
    int histoB[256];
    int cumulativeFreqB[256];

    int numpixels = width * height;

    uint8_t* src;
    src = (uint8_t*)((*env)->GetDirectBufferAddress(env, srcBuffer));
    if (!src) {
    	ERROR("Can not get direct buffer");
    	return;
    }

    for (i = 0; i < 256; i++) {
        histoR[i] = 0;
        histoG[i] = 0;
        histoB[i] = 0;
    }
    for (i = 0; i < numpixels * 4; i += 4) {
        histoR[src[i]]++;
        histoG[src[i+1]]++;
        histoB[src[i+2]]++;
    }

    for (i = 0; i < 256; i++) {
        cumulativeFreqR[i] = histoR[i] + (i > 0 ? cumulativeFreqR[i-1] : 0);
        cumulativeFreqG[i] = histoG[i] + (i > 0 ? cumulativeFreqG[i-1] : 0);
        cumulativeFreqB[i] = histoB[i] + (i > 0 ? cumulativeFreqB[i-1] : 0);
    }

    for (i = 0; i < numpixels * 4; i += 4) {
        src[i] = MIN(MAX(cumulativeFreqR[src[i]] * 255 / numpixels, 0), 255);
        src[i+1] = MIN(MAX(cumulativeFreqG[src[i+1]] * 255 / numpixels, 0), 255);
        src[i+2] = MIN(MAX(cumulativeFreqB[src[i+2]] * 255 / numpixels, 0), 255);
    }
}

JNIEXPORT void JNICALL
 Java_org_ebookdroid_common_bitmaps_ByteBufferBitmap_nativeAutoLevels2(JNIEnv* env, jclass classObject, jobject srcBuffer,
                                                          jint width, jint height)
{
    int i;
    int histoR[256];
    int cumulativeFreqR[256];
    int histoG[256];
    int cumulativeFreqG[256];
    int histoB[256];
    int cumulativeFreqB[256];

    int numpixels = width * height;
    int minR = 0, minG = 0, minB = 0;
    int maxR = 0, maxG = 0, maxB = 0;

    DEBUG("nativeAutoLevels");

    uint8_t* src;
    src = (uint8_t*)((*env)->GetDirectBufferAddress(env, srcBuffer));
    if (!src) {
    	ERROR("Can not get direct buffer");
    	return;
    }
    for (i = 0; i < 256; i++) {
        histoR[i] = 0;
        histoG[i] = 0;
        histoB[i] = 0;
    }
    for (i = 0; i < numpixels * 4; i += 4) {
        histoR[src[i]]++;
        histoG[src[i+1]]++;
        histoB[src[i+2]]++;
    }

    for (i = 0; i < 256; i++) {
        cumulativeFreqR[i] = histoR[i] + (i > 0 ? cumulativeFreqR[i-1] : 0);
        cumulativeFreqG[i] = histoG[i] + (i > 0 ? cumulativeFreqG[i-1] : 0);
        cumulativeFreqB[i] = histoB[i] + (i > 0 ? cumulativeFreqB[i-1] : 0);
    }

    for (i = 0; i < 256; i++) {
        if (cumulativeFreqR[i] > 5 * numpixels / 100 && minR == 0) {
            minR = MAX(0, i - 1);
        }
        if (cumulativeFreqG[i] > 5 * numpixels / 100 && minG == 0) {
            minG = MAX(0, i - 1);
        }
        if (cumulativeFreqB[i] > 5 * numpixels / 100 && minB == 0) {
            minB = MAX(0, i - 1);
        }
    }

    for (i = 255; i >= 0; i--) {
        if (cumulativeFreqR[i] < 95 * numpixels / 100 && maxR == 0) {
            maxR = MIN(255, i + 1);
        }
        if (cumulativeFreqG[i] < 95 * numpixels / 100 && maxG == 0) {
            maxG = MIN(255, i + 1);
        }
        if (cumulativeFreqB[i] < 95 * numpixels / 100 && maxB == 0) {
            maxB = MIN(255, i + 1);
        }
    }

    if (maxR - minR < 10) {
      minR = MAX(0, minR - 5);
      maxR = MIN(255, maxR + 5);
    }

    if (maxG - minG < 10) {
      minG = MAX(0, minG - 5);
      maxG = MIN(255, maxG + 5);
    }

    if (maxB - minB < 10) {
      minB = MAX(0, minB - 5);
      maxB = MIN(255, maxB + 5);
    }

    int min = MIN(MIN(minR, minG), minB);
    int max = MAX(MAX(maxR, maxG), maxB);

    for (i = 0; i < numpixels * 4; i += 4) {
        src[i] = MIN(MAX((src[i] - minR) * 255 / (maxR - minR), 0), 255);
        src[i+1] = MIN(MAX((src[i+1] - minG) * 255 / (maxG - minG), 0), 255);
        src[i+2] = MIN(MAX((src[i+2] - minB) * 255 / (maxB - minB), 0), 255);
    }
}

JNIEXPORT void JNICALL
Java_org_ebookdroid_common_bitmaps_ByteBufferBitmap_nativeFillRect(JNIEnv* env, jclass classObject, jobject srcBuffer,
                                                                   jint srcWidth, jobject dstBuffer, jint dstWidth,
                                                                   jint sub_x, jint sub_y, jint width, jint height)
{
    uint8_t* src;
    uint8_t* dst;
    int y;

    src = (uint8_t*)((*env)->GetDirectBufferAddress(env, srcBuffer));
    if (!src) {
        ERROR("Can not get direct buffer");
        return;
    }
    dst = (uint8_t*)((*env)->GetDirectBufferAddress(env, dstBuffer));
    if (!dst) {
        ERROR("Can not get direct buffer");
        return;
    }

    for(y = 0; y < height; y++ )
    {
        uint8_t* row = src + ((y + sub_y)*srcWidth + sub_x) * 4;
        memcpy(dst + y * dstWidth * 4, row, width * 4);
    }
}
