#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void parseface(char *facedata,int *varray,int *narray,int *tarray,int position, int hasuv, int hasnormals) {
	char workstr[10];
	int readpos = 0;
	int i;
	for(i = 0; i < 3; i++) {
		int pos = 0;
		while(readpos < strlen(facedata) && facedata[readpos] != '/') {
			workstr[pos] = facedata[readpos];
			pos++;
			readpos++;
		}
		workstr[pos] = 0;
		pos = 0;
		readpos++;

		// -1 to compensate for wavefront file starting on 1 and not 0
		if(i == 0) {
			varray[position] = atoi(workstr) - 1;
		} else if((i == 1) && (hasuv > 0)) {
			tarray[position] = atoi(workstr) - 1;
		} else if((i == 2) && (hasnormals > 0)) {
			narray[position] = atoi(workstr) - 1;
		}
	}
}

void loadobj(char *objfilename, char *openobj, char *outputfile) {
	FILE * pFile;
	pFile = fopen(objfilename, "r");
	char *buf;
	buf = (char*)malloc(1024);
	char *result;

	// foundobj is true if an object with a certain name is found
	// Set foundobj to true if we are loading all objects
	int foundobj = 0;
	if(strcmp(openobj, "ALL") == 0) {
		foundobj = 1;
	}
	int vertcount = 0;
	int normalcount = 0;
	int texturecount = 0;
	int facecount = 0;
	int polycount = 0;
	int quadcount = 0;
	int trianglecount = 0;

	// Global Variables for Object
	float *vertexlist;
	float *uvlist;
	float *normallist;
	int *trianglelist;
	int *quadlist;
	char *resultp1, *resultp2, *resultp3, *resultp4, *resultp5;

	if(pFile) {
		printf("Msg: Success opening: %s!\n", objfilename);
		while(fgets(buf, 1024, pFile) != NULL) {
			if(buf[0] == '#') {
				// Comment - Do Nothing
			} else {
				result = strtok(buf, " \n\t");
				if(result != NULL) {
					if(strcmp(result, "v") == 0) {
						if(foundobj) {
							//Increase vertex counter
							vertcount++;
						}
					} else if(strcmp(result, "vt") == 0) {
						if(foundobj) {
							// Increase texture coordinate counter
							texturecount++;
						}
					} else if(strcmp(result, "vn") == 0) {
						if(foundobj) {
							// Increase normal counter
							normalcount++;
						}
					} else if(strcmp(result, "f") == 0) {
						if(foundobj) {
							// Increase face counter
							facecount++;
							resultp1 = strtok(NULL, " \n\t");
							resultp2 = strtok(NULL, " \n\t");
							resultp3 = strtok(NULL, " \n\t");
							resultp4 = strtok(NULL, " \n\t");
							resultp5 = strtok(NULL, " \n\t");
							if((resultp4 != NULL)&&(resultp5 == NULL)) {
								quadcount++;
							} else if((resultp3 != NULL)&&(resultp4 == NULL)) {
								trianglecount++;
							} else {
								polycount++;
							}
						}
					} else if(strcmp(result, "g") == 0) {
						if(result=strtok(NULL, " \n\t")) {
							if(!strcmp(openobj, result)) {
								foundobj = 1;
							}
						} else {
							printf("Error: Failed to read object name\n");
						}
					} else {
						//printf("O: %s \n",buf);
					}
				}
			}
		}

		if(polycount > 0) {
			printf("Warning: Object %s in file %s contains faces that will be ignored, with more than 4 vertices.!\n", objfilename, openobj);
		}

		if(((trianglecount + quadcount) > 0)) {
			// Set foundobj to true if we are loading all objects
			if(strcmp(openobj, "ALL")) {
				foundobj = 1;
			}

			// Seek to beginning again
			fseek(pFile, 0, SEEK_SET);

			// Allocate local buffers
			float *localvertexlist = (float *) malloc (sizeof(float) * vertcount * 3);
			float *localnormallist = (float *) malloc (sizeof(float) * normalcount * 3);
			float *localuvlist = (float *) malloc (sizeof(float) * texturecount * 2);
			int *localquadlist = (int *) malloc (sizeof(int) * quadcount * 4);
			int *localquadlistuv = (int *) malloc (sizeof(int) * quadcount * 4);
			int *localquadlistnormal = (int *) malloc (sizeof(int) * quadcount * 4);
			int *localtrianglelist = (int *) malloc (sizeof(int) * trianglecount * 3);
			int *localtrianglelistuv = (int *) malloc (sizeof(int) * trianglecount * 3);
			int *localtrianglelistnormal = (int *) malloc (sizeof(int) * trianglecount * 3);

			// Reset counters for second pass
			trianglecount = 0;
			quadcount = 0;
			normalcount = 0;
			vertcount = 0;
			texturecount = 0;

			while(fgets(buf, 1024, pFile) != NULL) {
				if(buf[0] == '#') {
					// Comment - Do Nothing
				} else {
					result = strtok(buf, " \n\t");
					if(result != NULL) {
						if(strcmp(result, "v") == 0) {
							if(foundobj) {
								result = strtok(NULL, " \n\t");
								localvertexlist[(vertcount * 3)] = atof(result);
								result = strtok(NULL, " \n\t");
								localvertexlist[(vertcount * 3) + 1] = atof(result);
								result = strtok(NULL, " \n\t");
								localvertexlist[(vertcount * 3) + 2] = atof(result);
								vertcount++;
							}
						} else if(strcmp(result, "vt") == 0) {
							if(foundobj) {
								result = strtok(NULL, " \n\t");
								localuvlist[(texturecount * 2)] = atof(result);
								result = strtok(NULL, " \n\t");
								localuvlist[(texturecount * 2) + 1] = atof(result);
								texturecount++;
							}
						} else if(strcmp(result, "vn") == 0) {
							if(foundobj) {
								result = strtok(NULL, " \n\t");
								localnormallist[(normalcount * 3)] = atof(result);
								result = strtok(NULL, " \n\t");
								localnormallist[(normalcount * 3) + 1] = atof(result);
								result = strtok(NULL, " \n\t");
								localnormallist[(normalcount * 3) + 2] = atof(result);
								normalcount++;
							}
						} else if(strcmp(result, "f") == 0) {
							if(foundobj) {
								resultp1 = strtok(NULL, " \n\t");
								resultp2 = strtok(NULL, " \n\t");
								resultp3 = strtok(NULL, " \n\t");
								resultp4 = strtok(NULL, " \n\t");
								resultp5 = strtok(NULL, " \n\t");
								if((resultp4 != NULL)&&(resultp5 == NULL)) {
									// Parse all 4 points in quad
									parseface(resultp1, localquadlist, localquadlistnormal, localquadlistuv, quadcount * 4, texturecount, normalcount);
									parseface(resultp2, localquadlist, localquadlistnormal, localquadlistuv, (quadcount * 4) + 1, texturecount, normalcount);
									parseface(resultp3, localquadlist, localquadlistnormal, localquadlistuv, (quadcount * 4) + 2, texturecount, normalcount);
									parseface(resultp4, localquadlist, localquadlistnormal, localquadlistuv, (quadcount * 4) + 3, texturecount, normalcount);
									quadcount++;
								} else if((resultp3 != NULL)&&(resultp4 == NULL)) {
									// Parse all 3 points in triangle
									parseface(resultp1, localtrianglelist, localtrianglelistnormal, localtrianglelistuv, trianglecount * 3, texturecount, normalcount);
									parseface(resultp2, localtrianglelist, localtrianglelistnormal, localtrianglelistuv, (trianglecount * 3) + 1, texturecount, normalcount);
									parseface(resultp3, localtrianglelist, localtrianglelistnormal, localtrianglelistuv, (trianglecount * 3) + 2, texturecount, normalcount);
									trianglecount++;
								}
							}
						} else if(strcmp(result, "g") == 0) {
							if(result = strtok(NULL, " \n\t")) {
								if(!strcmp(openobj, result)) {
									foundobj = 1;
								}
							}
						}
					}
				}
			}
			int i = 0;
			// Allocate buffers
			vertexlist = (float *) malloc (sizeof(float) * vertcount * 3);
			normallist = (float *) malloc (sizeof(float) * vertcount * 3);
			uvlist = (float *) malloc (sizeof(float) * vertcount * 2);
			trianglelist = (int *) malloc (sizeof(int) * trianglecount * 3);
			quadlist = (int *) malloc (sizeof(int) * quadcount * 4);

			// Copy data from Per Face model to Per Vertex model first for triangles and then for quads
			// If there are triangle normals, copy triangle normal data 
			if(normalcount > 0) {
				printf("Msg: Copying Triangle Normals: %i / %i !\n", normalcount, trianglecount);
				for(i = 0; i < trianglecount * 3; i++) {
					// Normal X
					normallist[localtrianglelist[i] * 3] = localnormallist[localtrianglelistnormal[i] * 3];
					// Normal Y
					normallist[(localtrianglelist[i] * 3 ) + 1 ] = localnormallist[(localtrianglelistnormal[i] * 3) + 1 ];
					// Normal Z
					normallist[(localtrianglelist[i] * 3) + 2] = localnormallist[(localtrianglelistnormal[i] * 3) + 2];
				}
				for(i = 0; i < quadcount * 4; i++) {
					// Normal X
					normallist[localquadlist[i] * 3] = localnormallist[localquadlistnormal[i] * 3];
					// Normal Y
					normallist[(localquadlist[i] * 3) + 1] = localnormallist[(localquadlistnormal[i] * 3) + 1];
					// Normal Z
					normallist[(localquadlist[i] * 3) + 2] = localnormallist[(localquadlistnormal[i] * 3) + 2];
				}
			} else {
				printf("Msg: No Triangle Normals to Copy\n");
			}

			// If there are triangle uv coordinates, copy uv coordinate data
			if(texturecount > 0) {
				printf("Msg: Copying Triangle UV Coordinates: %i / %i !\n", texturecount, trianglecount);
				for(i = 0; i < trianglecount * 3; i++) {
					// U Coordinate
					uvlist[localtrianglelist[i] * 3] = localuvlist[localtrianglelistuv[i] * 3];
					// V Coordinate
					uvlist[(localtrianglelist[i] * 3) + 1] = localuvlist[(localtrianglelistuv[i] * 3) + 1];
				}
				for(i = 0;i < quadcount * 4; i++) {
					// U Coordinate
					uvlist[localquadlist[i] * 3] = localuvlist[localquadlistuv[i] * 3];
					// V Coordinate
					uvlist[(localquadlist[i] * 3) + 1] = localuvlist[(localquadlistuv[i] * 3) + 1];
				}
			} else {
				printf("Msg: No Triangle UV Coordinates to Copy\n");
			}

			// Same for Quads
			printf("--------------------------------\n");
			printf("Saving JSON Object file: %s\n", outputfile);
			printf("--------------------------------\n");

			// Process data to JSON
			printf("{\n");
			printf("\"vertexPositions\" : [");
			for(i = 0; i < (vertcount * 3); i++) {
				if(i < ((vertcount * 3) - 1)) {
					printf("%f,", localvertexlist[i]);
				} else {
					printf("%f", localvertexlist[i]);
				}
			}
			printf("],\n");
			printf("\"vertexNormals\" : [");
			if(normalcount > 0) {
				for(i = 0; i < (vertcount * 3); i++) {
					if(i < ((vertcount * 3) - 1)) {
						printf("%f,", normallist[i]);
					} else {
						printf("%f", normallist[i]);
					}
				}
			}
			printf("],\n");
			printf("\"vertexTextureCoords\" : [");
			if(texturecount > 0) {
				for(i = 0; i < (vertcount * 2); i++){
					if(i < ((vertcount * 2) - 1)) {
						printf("%f,", uvlist[i]);
					} else {
						printf("%f", uvlist[i]);
					}
				}
			}
			printf("],\n");
			printf("\"indices\" : [");

			// Triangle indices
			for(i = 0; i < (trianglecount * 3); i++) {
				if(i < ((trianglecount * 3) - 1)) {
					printf("%i,", localtrianglelist[i]);
				} else {
					printf("%i", localtrianglelist[i]);
				}
			}

			// Quad indices
			for(i = 0; i < quadcount; i++) {
				if(i == (quadcount - 1)) {
					printf("%i,%i,%i,%i,%i,%i,", localquadlist[i], localquadlist[i + 1], localquadlist[i + 2], localquadlist[i + 2], localquadlist[i + 3], localquadlist[i]);
				} else {
					printf("%i,%i,%i,%i,%i,%i ", localquadlist[i], localquadlist[i + 1], localquadlist[i + 2], localquadlist[i + 2], localquadlist[i + 3], localquadlist[i]);
				}
			}

			printf("]\n");
			printf("}\n");

			// Free buffers
			free(localvertexlist);
			free(localnormallist);
			free(localuvlist);
			free(localtrianglelist);
			free(localtrianglelistuv);
			free(localtrianglelistnormal);
			free(localquadlist);
			free(localquadlistuv);
			free(localquadlistnormal);
		} else {
			// Multigon
			printf("Error: Object did not contain any triangles or quads!\n");
		}

		fclose(pFile);
		if(!foundobj) {
			printf("Error: Failed to find object %s!\n", openobj);
		} else {
			printf("Msg: Sucessful Parsing of Object %s in File %s\nMsg: %i vertices %i normals %i texture coordinates %i faces (%i triangles %i quads %i polys)\n", openobj, objfilename, vertcount, normalcount, texturecount, facecount, trianglecount, quadcount, polycount);
		}

	} else {
		printf("Error: Failed to open %s!\n", objfilename);
	}
	free(buf);
}

int main(int argc, char** argv) {
	if(argc < 3) {
		printf("ERROR: not enough arguments. must have LOADFILE and SAVEFILE and if you want to process only a specific object, the name of object to process\n");
		return 1;
	}

	printf("--------------------------------\n");
	printf("Loading Wavefront Object file: %s\n", argv[1]);
	printf("--------------------------------\n");

	if(argc > 3) {
		printf("Msg: Loading Object: %s\n", argv[3]);
		loadobj(argv[1], argv[3], argv[2]);
	} else {
		printf("Msg: Loading all Objects\n");
		loadobj(argv[1], "ALL", argv[2]);
	}
	return 0;
}
