/*
 *  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.
 */
 /**
  * @author Viskov Nikolay
  *
  */

#include "pngencoder.h"
#include <stdio.h>

/*
 * Class:     org_apache_harmony_awt_gl_image_PngEncoder
 * Method:    initIDs
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_org_apache_harmony_x_imageio_plugins_png_PNGImageWriter_initIDs
(JNIEnv *env, jclass cls, jclass iosClass) {
      //-- ImageOutputStream.write(byte[], int, int)
  img_IOSwriteID = (*env)->GetMethodID(env, iosClass, "write", "([BII)V");
}

void gl_write_data(png_structp png_ptr, png_bytep data, png_size_t length) {
  png_encoder_info_ptr encoderInfo = png_get_io_ptr(png_ptr);
  
  jbyteArray java_buffer;
  unsigned char *native_buffer;

  JNIEnv *env = encoderInfo->env;

  png_size_t bufferLength = IO_BUFFER_SIZE - encoderInfo->freeBytesInIOBuffer;

  //printf("%u", length);

  if (encoderInfo->freeBytesInIOBuffer < length) {
      if (IO_BUFFER_SIZE < length) {          
          java_buffer = (*env)->NewByteArray(env, (jsize) (length + bufferLength));
          native_buffer = (unsigned char *)(*env)->GetPrimitiveArrayCritical(env, java_buffer, NULL);

          memcpy(native_buffer, encoderInfo->ioBuffer, bufferLength);
          memcpy(native_buffer + bufferLength, data, length);

          (*env)->ReleasePrimitiveArrayCritical(env, java_buffer, native_buffer, 0);
          
          (*env)->CallVoidMethod(env, encoderInfo->ios, img_IOSwriteID, java_buffer, 0, length + bufferLength);

          encoderInfo->freeBytesInIOBuffer = IO_BUFFER_SIZE;
      } else {
          gl_flush_data(encoderInfo->png_ptr);
          memcpy(encoderInfo->ioBuffer, data, length);
          encoderInfo->freeBytesInIOBuffer -= length;
      }
  } else {
      memcpy(encoderInfo->ioBuffer + bufferLength, data, length);
      encoderInfo->freeBytesInIOBuffer -= length;
  }
  //printf("passed\n");
}

void gl_flush_data(png_structp png_ptr) {
  png_encoder_info_ptr encoderInfo = png_get_io_ptr(png_ptr);
  
  jbyteArray java_buffer;
  unsigned char *native_buffer;

  png_size_t bufferLength = IO_BUFFER_SIZE - encoderInfo->freeBytesInIOBuffer;

  JNIEnv *env = encoderInfo->env;

  //printf("flush ");

  java_buffer = (*env)->NewByteArray(env, (jsize) bufferLength);
  native_buffer = (unsigned char *)(*env)->GetPrimitiveArrayCritical(env, java_buffer, NULL);
  memcpy(native_buffer, encoderInfo->ioBuffer, bufferLength);
  (*env)->ReleasePrimitiveArrayCritical(env, java_buffer, native_buffer, 0);
  
  (*env)->CallVoidMethod(env, encoderInfo->ios, img_IOSwriteID, java_buffer, 0, bufferLength);

  encoderInfo->freeBytesInIOBuffer = IO_BUFFER_SIZE;

  //printf("passed\n");
}

/*
 * Class:     org_apache_harmony_awt_gl_image_PngEncoder
 * Method:    decode
 * Signature: ([BILjava/lang/Object;IIII[IIZ)I
 */
JNIEXPORT jint JNICALL 
Java_org_apache_harmony_x_imageio_plugins_png_PNGImageWriter_encode
(JNIEnv *env, 
 jobject obj, 
 jbyteArray jInput, 
 jint bytesInBuffer, 
 jint bytePixelSize,
 jobject iosObj, 
 jint imageWidth, 
 jint imageHeight, 
 jint bitDipth, 
 jint colorType, 
 jintArray palette, 
 jint paletteSize, 
 jboolean isInterlace) {



  png_encoder_info_ptr encoderInfo;
  int i;

  if(!(encoderInfo = initPng())) {
    throwNewExceptionByName(env, "java/lang/RuntimeException",
                            "Can't create native PNG encoder");
    return 2; // NULL
  }
  
  if(setjmp(encoderInfo->jmpBuf)) { // Only way to deal with errors in libpng
    destroyPng(&encoderInfo);
    return 2;
  }

  png_set_write_fn(
      encoderInfo->png_ptr,
      encoderInfo, 
      gl_write_data,
      gl_flush_data
      );

  encoderInfo->ios = iosObj;

  // Update JNI-related fields
  encoderInfo->env = env;
  encoderInfo->obj = obj;   

  encoderInfo->jInputData = jInput;
  encoderInfo->jPalette = palette;

  png_set_IHDR(encoderInfo->png_ptr, encoderInfo->info_ptr, imageWidth, imageHeight,
      bitDipth, colorType, isInterlace ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE,
       PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);

  if (colorType == PNG_COLOR_TYPE_PALETTE) {
      jint *tmpPalette = (jint *) (*env)->GetPrimitiveArrayCritical(env, palette, 0);   

      png_colorp pngPalette = (png_colorp) malloc(paletteSize * sizeof(png_color));

      //png_bytep alpha = (png_bytep) malloc(paletteSize * sizeof(png_byte)); 

      for (i = 0;  i < paletteSize;  i ++) {          
          //printf("n%u = %u\n", i, (tmpPalette[i] & 0xff000000));
          (pngPalette + i)->red =
             (png_byte) ((tmpPalette[i] & 0x00ff0000) >> 16);
          (pngPalette + i)->green =
             (png_byte) ((tmpPalette[i] & 0x0000ff00) >> 8);
          (pngPalette + i)->blue = (png_byte) tmpPalette[i] & 0x000000ff;

          //alpha[i] = 256 - (tmpPalette[i] & 0xff000000);
      }

      png_set_PLTE(encoderInfo->png_ptr, encoderInfo->info_ptr, pngPalette, paletteSize);      

      //png_set_tRNS(encoderInfo->png_ptr, encoderInfo->info_ptr, alpha, paletteSize, NULL);

      //free(alpha);
      free(pngPalette);
      (*env)->ReleasePrimitiveArrayCritical(env, palette, tmpPalette, 0); 
  }  

  encoderInfo->inputBuffer = (*env)->GetPrimitiveArrayCritical(env, jInput, 0);    

  encoderInfo->tmpBuffer = (png_bytepp) malloc(imageHeight * sizeof(png_bytep));

  for (i = 0;  i < imageHeight;  i ++) {
      encoderInfo->tmpBuffer[i] = encoderInfo->inputBuffer + (i * (imageWidth * bytePixelSize));
  }

  png_set_rows(encoderInfo->png_ptr, encoderInfo->info_ptr, encoderInfo->tmpBuffer);
  png_write_png(encoderInfo->png_ptr, encoderInfo->info_ptr, PNG_TRANSFORM_IDENTITY, NULL);  

  free(encoderInfo->tmpBuffer);
  encoderInfo->tmpBuffer = NULL;

  (*env)->ReleasePrimitiveArrayCritical(env, jInput, encoderInfo->inputBuffer, 0);    
  encoderInfo->inputBuffer = NULL;
  
  png_write_end(encoderInfo->png_ptr, encoderInfo->info_ptr);

  png_write_flush(encoderInfo->png_ptr);

  destroyPng(&encoderInfo);
  
  return 0;
}

void gl_error_fn(png_structp png_ptr, png_const_charp error_msg) {
  png_encoder_info_ptr encoderInfo = png_get_error_ptr(png_ptr);
  throwNewExceptionByName(encoderInfo->env, "java/lang/RuntimeException",
                          error_msg);
  
  if(encoderInfo) { // Else there's no way to terminate correctly
    longjmp(encoderInfo->jmpBuf, 1);
  }
}

void gl_warning_fn(png_structp png_ptr, png_const_charp warning_msg) {
}

void destroyPng(png_encoder_info_ptr *encoderInfoP) {
  png_encoder_info_ptr encoderInfo = *encoderInfoP;

  JNIEnv *env = encoderInfo->env;

  //printf("now = %u\n",(encoderInfo->inputBuffer));

  // Release arrays
  if(encoderInfo->jInputData && encoderInfo->inputBuffer) {
    (*env)->ReleasePrimitiveArrayCritical(
      env, encoderInfo->jInputData, encoderInfo->inputBuffer, 0
    );
    encoderInfo->inputBuffer = NULL;
  }

  if (encoderInfo->tmpBuffer) {
      free(encoderInfo->tmpBuffer);
  }
  encoderInfo->tmpBuffer = NULL;

  if(encoderInfo->png_ptr && encoderInfo->info_ptr)      
    png_destroy_write_struct(&encoderInfo->png_ptr, &encoderInfo->info_ptr);

  free(encoderInfo);
  encoderInfo = NULL;
}

png_encoder_info_ptr initPng() {
  png_encoder_info_ptr encoderInfo = calloc(sizeof(png_encoder_info), 1);
  if(!encoderInfo) {
    return NULL;
  }  

  encoderInfo->png_ptr = png_create_write_struct(
    PNG_LIBPNG_VER_STRING,
    encoderInfo, 
    gl_error_fn, 
    gl_warning_fn
  );
    
  if(!encoderInfo->png_ptr) {
    return NULL;
  }

  encoderInfo->info_ptr = png_create_info_struct(encoderInfo->png_ptr);

  if (!encoderInfo->info_ptr) {
    png_destroy_write_struct(&encoderInfo->png_ptr, NULL);
    return NULL;
  }

  encoderInfo->freeBytesInIOBuffer = IO_BUFFER_SIZE;

  return encoderInfo;
}

