package RwsReader;

import java.io.*;
import java.util.*;

public class RwsReader {

	private static final boolean DEBUG = false;
	
	private boolean useTextures = true;

	private FileReader in;
	private PrintWriter txtWriter;
	private int length;
	private long position;
	private String filename;
	private String rwsName;

	public static void main (String[] args) {
		new RwsReader(args);
	}

	public RwsReader(String[] args) {
		Locale.setDefault(new Locale("en", "US"));
		
		try {
			parseFile(args);
		} catch (EOFException eof) {
			if (txtWriter != null) txtWriter.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void parseFile(String[] args) throws IOException {
		Scanner scan = new Scanner(System.in);
		if (args.length != 1) {
			System.out.print("\nEnter the name of the *.rws file you want to extract: ");
			filename = scan.next();
		} else {
			filename = args[0];
		}
		if (filename.endsWith(".rws")) {
			rwsName = filename.substring(0, filename.length() - 4);
		} else {
			rwsName = filename;
			filename += ".rws";
		}
		
		String textureFolder = rwsName;
		
		File file = new File(filename);
		if (!file.exists()) {
			System.out.println("\nFile " + filename + " doesn't exist!");
			System.exit(1);
		}
		
		// read files from folder "Textures"
		File texFolder = new File(textureFolder);
		if (!texFolder.exists()) {
			textureFolder = "Textures";
			texFolder = new File(textureFolder);
			if (!texFolder.exists()) {
				useTextures = false;
			}
		}
		if (useTextures) {
			texFolder.mkdir();
			String[] textureNames = texFolder.list();
			
			File mtlFile = new File(rwsName + ".mtl");
			PrintWriter mtlWriter = new PrintWriter(mtlFile, "UTF-8");
			
			for (String tex : textureNames) {
				if (!tex.endsWith(".dds")) {
					continue;
				}
				
				mtlWriter.println("newmtl " + tex.substring(0, tex.length() - 4));
				mtlWriter.println("Ka  1.0 1.0 1.0");
				mtlWriter.println("Kd  1.0 1.0 1.0");
				mtlWriter.println("Ks  1.0 1.0 1.0");
				mtlWriter.println("d  1.0");
				mtlWriter.println("Ns  0.0");
				mtlWriter.println("illum 2");
				mtlWriter.println("map_Kd " + textureFolder + "/" + tex);
				mtlWriter.println();
			}
			
			mtlWriter.close();
		}
		
		filename = file.getAbsolutePath();
		
		txtWriter = new PrintWriter(rwsName + ".txt", "UTF-8");
		
		in = new FileReader(file);
		long fileLength = in.length();
		
		length = 0;
		position = 0;
		
		int objectIndex = 1;
		
		String[] texNames = null;
		
		do {
			in.seek(position + length);
			
			position = in.getFilePointer();
			int id = in.readInt();
			length = in.readInt() + 12;
			in.skipBytes(4); // skip version
			
			switch (id) {
			
			case 0x10:
				readObjectChunk(objectIndex++);
				break;
				
			case 0x16FC0:
				in.skipBytes(0x30);
				
				float objectRotation[] = new float[9];
				for (int z = 0; z < 9; z++) {
					objectRotation[z] = in.readFloat();
				}
				
				float objectPos[] = new float[3];
				objectPos[0] = in.readFloat();
				objectPos[1] = in.readFloat();
				objectPos[2] = in.readFloat();
				
				in.skipBytes(4);
				int stringLength = in.readInt();
				String string = in.readString(stringLength);
				
				debugOutput(objectPos[0] + ", " + objectPos[1] + ", " + objectPos[2] + ", " + string);
				
				length = (int) (in.getFilePointer() - position);
				
				break;
				
			case 0x0B:
				// 0x0B container
				// 1E1EBE
				debugOutput("Reading chunk: " + id);
				length = 0x4C + 12;
				break;
				
			case 0x08:
				// texture names
				// 1E1F16
				debugOutput("Reading chunk: " + id);
				texNames = readTextures();
				break;
				
			case 0x09:
				// single map chunk
				debugOutput("Reading chunk: " + id);
				readMapChunk(texNames, 0, length - 12);
				length += 8;
				break;
				
			case 0x0A:
				// map chunk container
				// 2154A6
				debugOutput("Reading chunk: " + id);
				readMapChunks(texNames);
				break;
				
			default:
				System.err.println("Wrong ID: " + id + " at offset " + position);
			}
			
		} while (position + length < fileLength);
		
		txtWriter.close();
		in.close();
	}

	private void readObjectChunk(int fileIndex) throws IOException {
		in.skipBytes(12);
		int objectCount = in.readInt();
		debugOutput("objectCount: " + objectCount);
		in.skipBytes(8);
		if (objectCount == 0) {
			return;
		}
		
		// 0xE
		long pos = in.getFilePointer();
		int id = in.readInt();
		int len = in.readInt();
		
		in.skipBytes(20);
		float objectRotation[][] = new float[3][3];
		
		objectRotation[0][0] = in.readFloat();
		objectRotation[0][2] = in.readFloat();
		objectRotation[0][1] = in.readFloat();
		
		objectRotation[1][0] = in.readFloat() * -1.0f;
		objectRotation[1][2] = in.readFloat() * -1.0f;
		objectRotation[1][1] = in.readFloat() * -1.0f;
		
		objectRotation[2][0] = in.readFloat();
		objectRotation[2][2] = in.readFloat();
		objectRotation[2][1] = in.readFloat();
		
		double eulerRadians[] = rotationMatrixToEulerRadians(objectRotation);
		
		float objectPos[] = new float[3];
		objectPos[0] = in.readFloat();
		objectPos[2] = in.readFloat();
		objectPos[1] = in.readFloat() * -1.0f;
		
		in.seek(pos + len + 12); // skip like 0x80 bytes
		
		id = in.readInt();
		while (id != 0x1A || in.getFilePointer() >= (position + length)) {
			in.skipBytes(in.readInt() + 4);
			id = in.readInt();
		}
		
		if (id != 0x1A) {
			return;
		}
		
		in.skipBytes(20);
		
		int numObjects = in.readInt();
		
		long objectOffset = in.getFilePointer();
		int objectLength = 0;
		
		for (int i = 0; i < numObjects; i++) {
			in.seek(objectOffset + objectLength);
			objectOffset = in.getFilePointer();
			
			if (in.readInt() != 0xF) {
				System.err.println("Error at pos: " + objectOffset);
			}
			objectLength = in.readInt() + 12;
			
			in.skipBytes(16);
			
			int flags = in.readInt();
			int numTriangles = in.readInt();
			int numVertices = in.readInt();
			int numMorphTargets = in.readInt();
			
			debugOutput("Obj " + fileIndex + ": " + flags + ", " + numTriangles + ", " + numVertices + ", " + numMorphTargets + " | " + position + ", " + length);
			
			int numTexCoordSets =  (flags & 0x00FF0000) >> 16;
			boolean TRISTRIP = (flags & 1) == 1;
			boolean POSITIONS = (flags & 2) == 2;
			boolean TEXTURED = (flags & 4) == 4;
			boolean PRELIT = (flags & 8) == 8;
			boolean NORMALS = (flags & 16) == 16;
			boolean LIGHT = (flags & 32) == 32;
			boolean MODULATE_MATERIAL_COLOR = (flags & 64) == 64;
			boolean TEXTURED2 = (flags & 128) == 128;
			boolean NATIVE  = (flags & 0x01000000) == 0x01000000;
			boolean NATIVE_INSTANCE = (flags & 0x02000000 ) == 0x02000000 ;
			
			int out_index = 1;
			String out_file;
			File f;
			do { // increase out_index if file already exists
				out_file = filename + "." + fileIndex + "." + out_index + ".obj";
				f = new File(out_file);
				out_index++;
			} while (f.exists());
			
			txtWriter.println(String.format("%s, %f, %f, %f, %f, %f, %f",
					out_file, objectPos[0], objectPos[1], objectPos[2],
					eulerRadians[0], eulerRadians[1], eulerRadians[2]
			));
			
			PrintWriter writer = new PrintWriter(out_file, "UTF-8");
			
			if (useTextures) {
				writer.println("mtllib ./" + rwsName + ".mtl" + "\r\n");
			}
			
			if (PRELIT) {
				in.skipBytes(numVertices * 4); // RGBA
			}
			
			long vt_offset = in.getFilePointer();
			long f_offset = vt_offset + numVertices * 8 * numTexCoordSets;
			if (!TEXTURED && !TEXTURED2) {
				f_offset = vt_offset;
			}
			long v_offset = f_offset + numTriangles * 8;
			long vn_offset = v_offset + numVertices * 12 + 24;
			long tex_offset = vn_offset + numVertices * 12;
			if (!NORMALS) {
				tex_offset = vn_offset;
			}
			
			// vertices
			in.seek(v_offset);
			float[] boundingSpherePos = { in.readFloat(), in.readFloat(), in.readFloat() };
			float boundingSphereRadius = in.readFloat();
			int hasPosition = in.readInt();
			int hasNormals = in.readInt();
			
			// for (numMorphTargets)
			for (int j = 0; j < numVertices; j++) {
				float x = in.readFloat();
				float y = in.readFloat();
				float z = in.readFloat();
				writer.println(String.format("v %.6f %.6f %.6f", x, y, z));
			}
			writer.println();
			
			// texture coordinates
			if (TEXTURED2) {
				in.seek(vt_offset);
				for (int j = 0; j < numVertices; j++) {
					float u = in.readFloat();
					float v = 1.0f - in.readFloat();
					writer.println(String.format("vt %.6f %.6f", u, v));
				}
				writer.println();
			}
			
			if (useTextures) {
				if (TEXTURED) {
					in.skipBytes(numVertices * 8);
				}
			}
			
			// normals
			if (NORMALS) {
				in.seek(vn_offset);
				
				for (int j = 0; j < numVertices; j++) {
					float x = in.readFloat();
					float y = in.readFloat();
					float z = in.readFloat();
					writer.println(String.format("vn %.6f %.6f %.6f", x, y, z));
				}
				writer.println();
			}
			
			String[] texNames = null;
			
			if (useTextures) {
				texNames = readTextures(tex_offset);
			} else {
				TEXTURED2 = false;
			}
			
			// faces
			in.seek(f_offset);
			int currentMaterial = -1;
			
			for (int j = 0; j < numTriangles; j++) {
				int v2 = in.readShort() + 1;
				int v1 = in.readShort() + 1;
				int mat = in.readShort();
				int v3 = in.readShort() + 1;
				
				if (TEXTURED2) {
					if (mat != currentMaterial) {
						currentMaterial = mat;
						writer.println("usemtl " + texNames[mat]);
					}
				}
				
				String s;
				if (NORMALS && TEXTURED2) {
					s = String.format("f %d/%d/%d %d/%d/%d %d/%d/%d", v1, v1, v1, v2, v2, v2, v3, v3, v3);
				} else if (NORMALS) {
					s = String.format("f %d//%d %d//%d %d//%d", v1, v1, v2, v2, v3, v3);
				} else if (TEXTURED2) {
					s = String.format("f %d/%d %d/%d %d/%d", v1, v1, v2, v2, v3, v3);
				} else {
					s = String.format("f %d %d %d", v1, v2, v3);
				}
				writer.println(s);
				
			}
			writer.println();
			
			writer.close();
		}
		
		fileIndex++;
	}

	private double[] rotationMatrixToEulerRadians(float[][] R) {
		
		double x, y, z;
		
		double sy = Math.sqrt(R[0][0] * R[0][0] +  R[0][1] * R[0][1]);
		
		boolean singular = sy < 1e-6;
		
		if (!singular) {
			x = Math.atan2(R[1][2] , R[2][2]);
			y = Math.atan2(-R[0][2], sy);
			z = Math.atan2(R[0][1], R[0][0]);
		} else {
			x = Math.atan2(-R[2][1], R[1][1]);
			y = Math.atan2(-R[0][2], sy);
			z = 0;
		}
		
		return new double[] { -x, y, -z };
	}

	public void readMapChunks(String[] texNames) throws IOException {
		
		int chunkCounter = 0;
		
		in.skipBytes(0x24);
		
		while (true) {
			int id = in.readInt();
			
			while (id == 0xA) {
				in.skipBytes(0x2C);
				id = in.readInt();
			}
			if (id == 3) { // EOF
				break;
			}
			
			// id == 9
			int chunkSize = in.readInt();
			in.skipBytes(4); // version
			
			readMapChunk(texNames, chunkCounter, chunkSize);
			
			chunkCounter++;
		}
	}

	private void readMapChunk(String[] texNames, int chunkCounter, int chunkSize) throws IOException {
		long startPos = in.getFilePointer() - 4;
		
		debugOutput(chunkCounter + "\t" + chunkSize + "\t" + startPos);
		
		in.skipBytes(4 * 4);
		
		String outFilename = filename + "_" + chunkCounter +  ".obj";
		PrintWriter writer = new PrintWriter(outFilename, "UTF-8");
		
		writer.println("mtllib ./" + rwsName + ".mtl" + "\r\n");
		
		int numFaces = in.readInt();
		int numVertices = in.readInt();
		
		in.skipBytes(4 * 8);
		
		// vertices
		for (int j = 0; j < numVertices; j++) {
			float x = in.readFloat();
			float y = in.readFloat();
			float z = in.readFloat();
			writer.println(String.format("v %.6f %.6f %.6f", x, y, z));
		}
		writer.println();
		
		in.skipBytes(numVertices * 4);
		
		// texture coordinates
		for (int j = 0; j < numVertices; j++) {
			float u = in.readFloat();
			float v = 1.0f - in.readFloat();
			writer.println(String.format("vt %.6f %.6f", u, v));
		}
		writer.println();
		
		in.skipBytes(numVertices * 8); // skip second set of texture coordinates
		
		/*
		// normals
		for (int j = 0; j < numVertices; j++) {
			float x = in.readFloat();
			float y = in.readFloat();
			float z = in.readFloat();
			writer.println(String.format("vn %.6f %.6f %.6f", x, y, z));
		}
		writer.println();
		*/
		
		// faces
		int currentMaterial = 0;
		
		for (int j = 0; j < numFaces; j++) {
			int v1 = in.readShort() + 1;
			int v2 = in.readShort() + 1;
			int v3 = in.readShort() + 1;
			int mat = in.readShort();
			
			if (mat != currentMaterial) {
				currentMaterial = mat;
				writer.println("usemtl " + texNames[mat]);
			}
			
			String s = String.format("f %d/%d %d/%d %d/%d", v1, v1, v2, v2, v3, v3);
			writer.println(s);
		}
		writer.println();
		
		writer.close();
		
		txtWriter.println(outFilename + ", 0.000000, 0.000000, 0.000000, 1.570796, 0.000000, 0.000000");
		
		long endPos = in.getFilePointer();
		
		in.skipBytes((int) (chunkSize - (endPos - startPos)));
	}

	private String[] readTextures() throws IOException {
		return readTextures(-1);
	}

	private String[] readTextures(long tex_offset) throws IOException {
		if (tex_offset == -1) {
			in.skipBytes(0xC);
		} else {
			in.seek(tex_offset);
			in.skipBytes(24);
		}
		int numMaterials = in.readInt();
		
		String[] texNames = new String[numMaterials];
		
		in.skipBytes(numMaterials * 4);
		
		for (int m = 0; m < numMaterials; m++) {
			in.skipBytes(4);
			int chunkSize = in.readInt();
			in.skipBytes(0x4C);
			int stringLength = in.readInt();
			
			String texName = "empty";
			if (stringLength == 0) {
				in.skipBytes(20);
			} else {
				in.skipBytes(4);
				texName = in.readString(stringLength);
				in.skipBytes(chunkSize - 0x50 - stringLength);
			}
			debugOutput("\tTexture: " + texName);
			
			texNames[m] = texName;
		}
		
		return texNames;
	}

	private void debugOutput(String msg) {
		if (DEBUG) {
			System.out.println(msg);
		}
	}
}