#include <jni.h>
#include <stdlib.h>
#include "yi_graph_polygon_gpc_GPC.h"
#include "gpc.h"

#ifndef TRUE
#define FALSE              0
#define TRUE               1
#endif

#define MALLOC(p, b, s)    {if ((b) > 0) { \
                            p= malloc(b); if (!(p)) { \
                            fprintf(stderr, "gpc malloc failure: %s\n", s); \
		                    exit(0);}} else p= NULL;}
#define FREE(p)            {if (p) {free(p); (p)= NULL;}}

// function definitions
jobject construct_polygon_c2j(JNIEnv *env, gpc_polygon *p);
gpc_polygon* construct_polygon_j2c(JNIEnv *env, jobject polygon, gpc_polygon *p);


JNIEXPORT jobject JNICALL Java_yi_graph_polygon_gpc_GPC_gpcPolygonClip
  (JNIEnv *env, jclass gpc, jint set_operation, jobject subject_polygon, jobject clip_polygon)
{
	int the_set_operation;
	gpc_polygon the_subject;
	gpc_polygon the_clip;
	gpc_polygon the_clipped;
	gpc_polygon* result;
	jobject clipped_polygon;

	// the set_operation
	the_set_operation = set_operation;

	// construct the corresponding gpc_polygon subject polygon (in C)
	result = construct_polygon_j2c(env, subject_polygon, &the_subject);
	if (result == NULL) {
		fprintf(stderr, "error when construct subject polygon\n");
		return NULL;
	}

	// construct the corresponding gpc_polygon clip polygon (in C)
	result = construct_polygon_j2c(env, clip_polygon, &the_clip);
	if (result == NULL) {
		fprintf(stderr, "error when construct clip polygon\n");
		return NULL;
	}

	// do the real work: clip
	gpc_polygon_clip(the_set_operation, &the_subject, &the_clip, &the_clipped);

    // output the operation for debug
	//fprintf(stdout, "subject polygon\n");
	//gpc_write_polygon(stdout, TRUE, &the_subject);
	//fprintf(stdout, "clip polygon\n");
    //gpc_write_polygon(stdout, TRUE, &the_clip);
	//fprintf(stdout, "result polygon\n");
    //gpc_write_polygon(stdout, TRUE, &the_clipped);

	// convert the result to GPCPolygon
	clipped_polygon = construct_polygon_c2j(env, &the_clipped);

	// relese memory
	gpc_free_polygon(&the_subject);
	gpc_free_polygon(&the_clip);
	gpc_free_polygon(&the_clipped);

	return clipped_polygon;
}

jobject construct_polygon_c2j(JNIEnv *env, gpc_polygon *p) {

	int c, v;

	jdouble* the_x;							// c peer of GpcContour.x
	jdouble* the_y;							// c peer of GpcContour.y
	jint* the_holes;						// c peer of GpcPolygon.holes

	jdoubleArray x, y;						// GpcContour.x, GpcContour.y
	jobjectArray contours;					// GpcPolygon.contours
	jintArray holes;						// GpcPolygon.holes

	jclass contourClass;					// class reference to GpcContour
	static jmethodID contour_init_id = NULL;// method id of GpcContour constructor
	jobject contour;						// a GpcContour

	jclass polygonClass;					// class reference to GPCPolygon
	static jmethodID polygon_init_id = NULL;// method id of GpcPolygon constructor				
	jobject polygon;						// created object

	int num_contours = p->num_contours;

	// Get a reference to GpcContour
	contourClass =(*env)->FindClass(env,"yi/graph/polygon/gpc/GpcContour");
	if (contourClass ==NULL){
		fprintf(stderr, "failed to find the class reference of GpcContour\n");
		return NULL;
	}

	// Get the method ID for the yi.graph.polygon.gpc.GpcContour(double[],double[]) constructor
	if (contour_init_id == NULL) {
		// fprintf(stdout, "Look for the method ID for the yi.graph.polygon.gpc.GpcContour(double[],double[]) constructor\n");
		contour_init_id =(*env)->GetMethodID(env, contourClass, "<init>","([D[D)V");
		if (contour_init_id ==NULL){
			fprintf(stderr, "failed to find the method id of GpcContour contructor\n");
			return NULL;
		}
	}

	// construct an array of GpcContour (for GpcPolygon.contours)
	contours =(*env)->NewObjectArray(env,num_contours,contourClass,NULL);
	if (contours ==NULL){
		fprintf(stderr, "failed to construct an array of GpcContour (for GpcPolygon.contours)\n");
		return NULL; // out of memory error thrown
	}

	// c peers of GpcPolygon.holes
	MALLOC(the_holes, num_contours * sizeof(jint), "the_holes array creation");

	for (c= 0; c < p->num_contours; c++)
	{
		int num_vertices = p->contour[c].num_vertices;

		the_holes[c] = p->hole[c];

		// create x, y array
		MALLOC(the_x, num_vertices * sizeof(jdouble), "x array creation");
		MALLOC(the_y, num_vertices * sizeof(jdouble), "y array creation");

		for (v= 0; v < num_vertices; v++) {
		  the_x[v] = p->contour[c].vertex[v].x;
		  the_y[v] = p->contour[c].vertex[v].y;
		}

		// construct a GpcContour.x and GpcContour.y
		x =(*env)->NewDoubleArray(env, num_vertices);
		if (x ==NULL){
			fprintf(stderr, "failed to construct construct a GpcContour.x\n");
			return NULL;
		}
		y =(*env)->NewDoubleArray(env, num_vertices);
		if (y ==NULL){
			fprintf(stderr, "failed to construct construct a GpcContour.y\n");
			return NULL;
		}
		// set x and y
		(*env)->SetDoubleArrayRegion(env,x,0,num_vertices,the_x);
		(*env)->SetDoubleArrayRegion(env,y,0,num_vertices,the_y);

		// construct a yi.graph.polygon.gpc.GpcContour object
		contour =(*env)->NewObject(env,contourClass,contour_init_id,x,y);
		if (contour ==NULL){
			fprintf(stderr, "failed to construct a GpcContour object\n");
			return NULL;
		}
		// add it to coutours
		(*env)->SetObjectArrayElement(env,contours,c,contour);

		// Free local references
		(*env)->DeleteLocalRef(env,x);
		(*env)->DeleteLocalRef(env,y);
		(*env)->DeleteLocalRef(env,contour);

		// free memory
		FREE(the_x);
		FREE(the_y);
	}

	// construct holes
	holes =(*env)->NewIntArray(env, num_contours);
	if (holes ==NULL){
		fprintf(stderr, "failed to construct a GpcPolygon.holes object\n");
		return NULL; // out of memory error thrown
	}
	// set holes
	(*env)->SetIntArrayRegion(env,holes,0,num_contours,the_holes);
	FREE(the_holes);

	// GpcPolygon class reference
	polygonClass =(*env)->FindClass(env,"yi/graph/polygon/gpc/GpcPolygon");
	if (polygonClass ==NULL){
		fprintf(stderr, "failed to find the class reference of GpcPolygon\n");
		return NULL;/*exception thrown */
	}

	// Get the method ID for the yi.graph.polygon.gpc.GpcPolygon constructor
	if (polygon_init_id == NULL) {
		//fprintf(stdout, "Look for the method ID for the yi.graph.polygon.gpc.GpcPolygon constructor\n");
		polygon_init_id =(*env)->GetMethodID(env,polygonClass, "<init>","([I[Lyi/graph/polygon/gpc/GpcContour;)V");
		if (contour_init_id ==NULL){
			fprintf(stderr, "failed to get the method ID for the yi.graph.polygon.gpc.GpcPolygon constructor\n");
			return NULL;
		}
	}
	polygon =(*env)->NewObject(env,polygonClass,polygon_init_id,holes,contours);
	

	return polygon;
}


gpc_polygon* construct_polygon_j2c(JNIEnv *env, jobject polygon, gpc_polygon *p)
{
	int i, j;

	jclass clc_gpc_polygon;                 // class reference to GPCPolygon

	static jfieldID fid_holes = NULL;		// field id of GpcPolygon.holes (int[])
	jintArray holes;						// GpcPolygon.holes
	jint holes_len;							// length of GpcPolygon.holes (it is an array)
	jint *the_holes;						// c peer of GpcPolygon.holes

	static jfieldID fid_contours = NULL;	// field id of GpcPolygon.contours (GpcContour[])
	jobjectArray contours;					// GpcPolygon.contours
	jint contours_len;						// length of GpcPolygon.contours (it is an array)
	jobject a_contour;						// a GpcContour object

	jclass cls_gpc_contour;					// class reference to GpcContour

	static jfieldID fid_x = NULL;			// field id of GpcContour.x (double[])
	jdoubleArray x;				            // GpcContour.x
	jint x_len;								// length of GpcContour.x
	jdouble *the_x;							// c peer of GpcContour.x

	static jfieldID fid_y = NULL;           // field id of GpcContour.y (double[])
	jdoubleArray y;							// GpcContour.y
	jint y_len;								// length of GpcContour.y
	jdouble *the_y;							// c peer of GpcContour.y

	// Get a reference to GPCPolygon class
    clc_gpc_polygon =(*env)->GetObjectClass(env, polygon);

    // Look for the instance field id of GpcPolygon.holes (int[])
	if (fid_holes == NULL) {
		//fprintf(stdout, "Look for the instance field id of GpcPolygon.holes\n");
		fid_holes =(*env)->GetFieldID(env, clc_gpc_polygon, "holes", "[I");
		if (fid_holes == NULL){
			fprintf(stderr, "failed to find the field GpcPolygon.holes\n");
			return NULL;
		}
	}

	// Read the instance field  GpcPolygon.holes (int[])
	holes = (*env)->GetObjectField(env, polygon, fid_holes);
	holes_len = (*env)->GetArrayLength(env, holes);
	
	// get the c peer of GpcPolygon.holes
	the_holes =(*env)->GetIntArrayElements(env, holes, NULL);
	if (the_holes ==NULL){
		fprintf(stderr, "failed to get the c peer of GpcPolygon.holes\n");
		return NULL; // exception occurred
	}

    // Look for the instance field id of GpcPolygon.contours (GpcContour[])
	if (fid_contours == NULL) {
		//fprintf(stdout, "Look for the instance field id of GpcPolygon.contours\n");
		fid_contours =(*env)->GetFieldID(env, clc_gpc_polygon, "contours", "[Lyi/graph/polygon/gpc/GpcContour;");
		if (fid_contours == NULL){
			fprintf(stderr, "failed to find the field GpcPolygon.contours\n");
			return NULL;
		}
	}

	// Read the instance field GpcPolygon.contours
	contours =(*env)->GetObjectField(env, polygon, fid_contours);
	contours_len = (*env)->GetArrayLength(env, contours);
	
	if (holes_len != contours_len) {
		fprintf(stderr, "GpcPolygon.holes.length != GpcPolygon.contours.length\n");
		return NULL;
	}

	// construct the c peer of GpcPolygon.contours and GpcPolygon.holes
	p->num_contours = contours_len;
	MALLOC(p->hole, p->num_contours * sizeof(int), "hole flag array creation");
	MALLOC(p->contour, p->num_contours * sizeof(gpc_vertex_list), "contour creation");

	// read each contour
	for (i=0; i<contours_len; i++){

		// get an element from GpcPolygon.contours (a GpcContour object)
		a_contour =(*env)->GetObjectArrayElement(env, contours, i);
		if (a_contour ==NULL){
			fprintf(stderr, "failed to get an element from GpcPolygon.contours\n");
			return NULL; // exception occurred
		}

		// Get a reference to contour's class
		cls_gpc_contour =(*env)->GetObjectClass(env, a_contour);

		// Look for the instance field id of GpcContour.x (double[])
		if (fid_x == NULL) {
			//fprintf(stdout, "Look for the instance field id of  GpcContour.x\n");
			fid_x =(*env)->GetFieldID(env, cls_gpc_contour, "x", "[D");
			if (fid_x == NULL){
				fprintf(stderr, "failed to find the field GpcContour.x\n");
				return NULL;
			}
		}

		// Read the instance field GpcContour.x 
		x = (*env)->GetObjectField(env, a_contour, fid_x);
		x_len = (*env)->GetArrayLength(env, x);
		
		// get the c peer of field GpcContour.x
		the_x =(*env)->GetDoubleArrayElements(env, x, NULL);
		if (the_x ==NULL){
			fprintf(stderr, "failed to get the c peer of field GpcContour.x\n");
			return NULL;
		}

		// Look for the instance field id of GpcContour.y (double[])
		if (fid_y == NULL) {
			//fprintf(stdout, "Look for the instance field id of  GpcContour.y\n");
			fid_y =(*env)->GetFieldID(env, cls_gpc_contour, "y", "[D");
			if (fid_y == NULL){
				fprintf(stderr, "failed to find the field GpcContour.y\n");
				return NULL;
			}
		}

		// Read the instance field GpcContour.y
		y =(*env)->GetObjectField(env, a_contour, fid_y);
		y_len = (*env)->GetArrayLength(env, y);
		
		// get the c peer of field GpcContour.y
		the_y =(*env)->GetDoubleArrayElements(env, y, NULL);
		if (the_y ==NULL){
			fprintf(stderr, "failed to get the c peer of field GpcContour.y\n");
			return NULL;
		}

		if (x_len != y_len) {
			fprintf(stderr, "GpcContour.x.length != GpcContour.y.length\n");
			return NULL;
		}
		
		p->hole[i]= the_holes[i];

		p->contour[i].num_vertices = x_len;
		MALLOC(p->contour[i].vertex, p->contour[i].num_vertices
           * sizeof(gpc_vertex), "vertex creation");

		for (j= 0; j < p->contour[i].num_vertices; j++) {
			p->contour[i].vertex[j].x = the_x[j];
            p->contour[i].vertex[j].y = the_y[j];
		}

		// freeing local references
		(*env)->DeleteLocalRef(env, a_contour);
		(*env)->DeleteLocalRef(env, cls_gpc_contour);
		(*env)->ReleaseDoubleArrayElements(env, x, the_x, JNI_ABORT);
		(*env)->DeleteLocalRef(env, x);
		(*env)->ReleaseDoubleArrayElements(env, y, the_y, JNI_ABORT);
		(*env)->DeleteLocalRef(env, y);
	}

	// freeing local references
	(*env)->DeleteLocalRef(env, clc_gpc_polygon);
	(*env)->ReleaseIntArrayElements(env, holes, the_holes, JNI_ABORT);
	(*env)->DeleteLocalRef(env, holes);
	(*env)->DeleteLocalRef(env, contours);

	return p;
}