import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;

/**
 * OBJLoader
 * Loads simple OBJ models. Written because I was tired of messing
 * with other peoples' wonky model loader code.
 *
 * Some code is commented out because it is specific to either Java 
 * or Processing, but not both.
 */
public class OBJModel {
    boolean loaded = false;
    
    int vCount = 0;
    int vtCount = 0;
    int vnCount = 0;
    int fCount = 0;
    
    float[]  v;
    float[] vt;
    float[] vn;
    Face[]   f;
    
    ArrayList<Material> materials = new ArrayList<Material>();
    
    BoundingBox boundingBox;
    boolean drawBoundingBox = false;
    
	OBJModel(String path, String filename) {
	    parseModelFile(path, filename);
	}
	
	void parseModelFile(String path, String filename) {
	    //JAVA
	    //File file = new File(path+filename);
        //StringBuffer contents = new StringBuffer();
        //BufferedReader reader = null;
        //PROCESSING
        String[] reader=null;
        
        try {
            //JAVA
            //reader = new BufferedReader(new FileReader(file));
            //PROCESSING
            reader = loadStrings(path+filename);
            int si=0;
            
            String text = null;
            String[] tokens = null;
            boolean parsedMats = false;
            Material currentMaterial=null;
            
            ArrayList<Float>  vAL = new ArrayList<Float>();
            ArrayList<Float> vtAL = new ArrayList<Float>();
            ArrayList<Float> vnAL = new ArrayList<Float>();
            ArrayList<Face>   fAL = new ArrayList<Face>();
            
            float maxX = Float.NEGATIVE_INFINITY;
            float maxY = Float.NEGATIVE_INFINITY;
            float maxZ = Float.NEGATIVE_INFINITY;
            float minX = Float.POSITIVE_INFINITY;
            float minY = Float.POSITIVE_INFINITY;
            float minZ = Float.POSITIVE_INFINITY;
            
            while (si < reader.length && ((text = reader[si++]) != null)) {
                
                tokens = text.split("\\s");
                if (tokens[0].equals("v")) {
                    vCount++;
                    float tempX = Float.parseFloat(tokens[1]);
                    float tempY = Float.parseFloat(tokens[2]);
                    float tempZ = Float.parseFloat(tokens[3]);
                    
                    if (tempX < minX) minX = tempX;
                    else if (tempX > maxX) maxX = tempX;
                    
                    if (tempY < minY) minY = tempY;
                    else if (tempY > maxY) maxY = tempY;
                    
                    if (tempZ < minZ) minZ = tempZ;
                    else if (tempZ > maxZ) maxZ = tempZ;
                    
                    vAL.add(tempX);
                    vAL.add(tempY);
                    vAL.add(tempZ);
                } else if (tokens[0].equals("vt")) {
                    vtCount++;
                    vtAL.add(Float.parseFloat(tokens[1]));
                    vtAL.add(Float.parseFloat(tokens[2]));
                } else if (tokens[0].equals("vn")) {
                    vnCount++;
                    vnAL.add(Float.parseFloat(tokens[1]));
                    vnAL.add(Float.parseFloat(tokens[2]));
                    vnAL.add(Float.parseFloat(tokens[3]));
                } else if (tokens[0].equals("f")) {
                    fCount++;
                    Face face = new Face(tokens);
                    face.setMaterial(currentMaterial);
                    fAL.add(face);
                } else if (tokens[0].equals("mtllib")) {
                    parseMaterialsFile(path, tokens[1]);
                    parsedMats = true;
                } else if (tokens[0].equals("usemtl")) {
                    if (!parsedMats) {
                        //throw new Exception("Have not parsed materials file yet, can not assign mats to faces! BAILING OBJ-LOAD!");
                    	System.err.println("Have not parsed materials file yet, can not assign mats to faces! BAILING OBJ-LOAD!");
                    	return;
                    }
                    Material mat;
                    String name = tokens[1];
                    for (int i=0; i<materials.size(); i++) {
                        if ((mat=materials.get(i)).isNamed(name)) {
                            currentMaterial = mat;
                            break;
                        }
                    }
                } else if (tokens[0].equals("g")) {
                    //group
                } else if (tokens[0].equals("#")) {
                    continue; /* Skip comment line. */
                }
            }
            
            v = new float[vAL.size()];
            for (int i=0; i<vAL.size(); i++) {
                v[i] = vAL.get(i);
            }
            
            vt = new float[vtAL.size()];
            for (int i=0; i<vtAL.size(); i++) {
                vt[i] = vtAL.get(i);
            }
            
            vn = new float[vnAL.size()];
            for (int i=0; i<vnAL.size(); i++) {
                vn[i] = vnAL.get(i);
            }
            
            f = new Face[fAL.size()];
            for (int i=0; i<fAL.size(); i++) {
                f[i] = fAL.get(i);
            }
            
            boundingBox = new BoundingBox(
                0.0, 0.0, 0.0,
                minX, minY, minZ,
                maxX, maxY, maxZ
            );
            
            loaded = true;
        //JAVA
        //} catch (FileNotFoundException e) {
        //    e.printStackTrace();
        //} catch (IOException e) {
        //    e.printStackTrace();
        //} finally {
        
        //PROCESSING
        } catch (Exception e) {
            println("Something wierd happened. :/");
        }
        
        // show file contents here
        //System.out.println(contents.toString());
    }
    
    void parseMaterialsFile(String path, String filename) {
        //JAVA
        //File file = new File(path+filename);
        //StringBuffer contents = new StringBuffer();
        //BufferedReader reader = null;
        //PROCESSING
        String[] reader=null;
        
        try {
            //JAVA
            //reader = new BufferedReader(new FileReader(file));
            //PROCESSING
            reader = loadStrings(path+filename);
            int si=0;
            
            String text = null;
            String[] tokens = null;
            Material currentMaterial=null;
            
            while (si < reader.length && ((text = reader[si++]) != null)) {
                
                tokens = text.split("\\s");
                if (tokens[0].equals("newmtl")) {
                    currentMaterial = new Material(tokens[1]);
                    materials.add(currentMaterial);
                } else if (tokens[0].equals("Ka")) {
                    currentMaterial.ka[0] = Float.parseFloat(tokens[1]);
                    currentMaterial.ka[1] = Float.parseFloat(tokens[2]);
                    currentMaterial.ka[2] = Float.parseFloat(tokens[3]);
                } else if (tokens[0].equals("Kd")) {
                    currentMaterial.kd[0] = Float.parseFloat(tokens[1]);
                    currentMaterial.kd[1] = Float.parseFloat(tokens[2]);
                    currentMaterial.kd[2] = Float.parseFloat(tokens[3]);
                } else if (tokens[0].equals("Ks")) {
                    currentMaterial.ks[0] = Float.parseFloat(tokens[1]);
                    currentMaterial.ks[1] = Float.parseFloat(tokens[2]);
                    currentMaterial.ks[2] = Float.parseFloat(tokens[3]);
                } else if (tokens[0].equals("Ns")) {
                    currentMaterial.ns = Float.parseFloat(tokens[1]);
                } else if (tokens[0].equals("map_Kd")) {
                    currentMaterial.loadTexture(path, tokens[1]);
                } else if (tokens[0].equals("#")) {
                    continue;
                }
            }
        //PROCESSING
        } catch (Exception e) {
            println("Something wierd happened. :/");
        }
	}
    
	/**
	 * A set of points marking a polygon face.
	 */
    class Face {
        int[] points;
        int numPoints=0;
        Material m=null;
        
        Face(String[] s) {
            String[] nums = null;
            ArrayList<Integer> pointsAL = new ArrayList<Integer>();
            
            for (int i=1; i<s.length; i++, numPoints++) {
                nums = s[i].split("/");
                pointsAL.add(Integer.parseInt(nums[0]));
                pointsAL.add(Integer.parseInt(nums[1]));
                pointsAL.add(Integer.parseInt(nums[2]));
            }
            
            points = new int[pointsAL.size()];
            for (int i=0; i<pointsAL.size(); i++) {
                points[i] = pointsAL.get(i);
            }
        }
        
        public void draw() {
            if (m != null) {
                texture(m.tex);
                for (int i=0; i<points.length;) {
                    int vPoint = (points[i++]-1)*3;
                    int vtPoint = (points[i++]-1)*2;
                    int vnPoint = (points[i++]-1)*3;
                    vertex(v[vPoint], v[vPoint+1], v[vPoint+2], vt[vtPoint], vt[vtPoint+1]);
                }
            } else {
                for (int i=0; i<points.length;) {
                    int vPoint = (points[i++]-1)*3;
                    int vtPoint = (points[i++]-1)*2;
                    int vnPoint = (points[i++]-1)*3;
                    vertex(v[vPoint], v[vPoint+1], v[vPoint+2]);
                }
            }
        }
        
        public void setMaterial(Material m) {
            this.m = m;
        }
    }
    
    class Material {
        public String name;
        public float[] ka = new float[3];
        public float[] kd = new float[3];
        public float[] ks = new float[3];
        public float ns = 0.0f;
        
        public PImage tex;
        
        Material(String name) {
            this.name = name;
        }
        
        void loadTexture(String path, String filename) {
            tex = loadImage(path+filename);
        }
        
        boolean isNamed(String name) {
            return ((this.name != null) && (this.name.equals(name)));
        }
    }
    
    void draw() {
        textureMode(NORMALIZED);
        for (int fi=0; fi<f.length; fi++) {
            beginShape();
                f[fi].draw();
            endShape();
        }
        
        if (drawBoundingBox) {
            boundingBox.draw();
        }
    }
}