package com.android.draw3d;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.StringTokenizer;

import javax.microedition.khronos.opengles.GL10;

import org.opencv.core.Point3;

import android.content.Context;
import android.util.Log;

public class ObjModel {
	private Context mContext;

	private ArrayList<Point3> mVPoints = new ArrayList<Point3>();
	private ArrayList<Point3> mTPoints = new ArrayList<Point3>();
	private ArrayList<Point3> mNPoints = new ArrayList<Point3>();
	private ArrayList<Face> faces = new ArrayList<Face>();

	public FloatBuffer vertices;
	public FloatBuffer textures;
	public FloatBuffer normals;
	public int v_size;

	public ObjModel(Context context) {
		mContext = context;
	}

	public void ObjLoader() throws IOException {
		InputStream in = mContext.getResources().openRawResource(R.raw.axis_die);
		BufferedReader reader = new BufferedReader(new InputStreamReader(in));

		// Log.e("line", "g");
		boolean pending = false;
		while (reader.ready()) {
			String line = reader.readLine();

			StringTokenizer tok = new StringTokenizer(line);
			String cmd = tok.nextToken();

			// Log.e("line", line);
			if (cmd.equals("O")) {
				if (pending)
					break;
				else
					pending = true;
			}
			if (cmd.equals("v")) {
				Point3 mVPoint = read_point(tok);
				mVPoints.add(mVPoint);
			} else if (cmd.equals("vn")) {
				Point3 mNPoint = read_point(tok);
				mNPoints.add(mNPoint);
			} else if (cmd.equals("vt")) {
				Point3 mTPoint = read_point(tok);
				mTPoints.add(mTPoint);
			} else if (cmd.equals("f")) {
				if (tok.countTokens() != 3)
					throw new UnsupportedOperationException("Only triangles supported");

				Face face = new Face(3);
				while (tok.hasMoreTokens()) {
					StringTokenizer face_tok = new StringTokenizer(tok.nextToken(), "/");

					int mV_idx = -1;
					int mT_idx = -1;
					int mN_idx = -1;

					mV_idx = Integer.parseInt(face_tok.nextToken());
					if (face_tok.hasMoreTokens())
						mT_idx = Integer.parseInt(face_tok.nextToken());
					if (face_tok.hasMoreTokens())
						mN_idx = Integer.parseInt(face_tok.nextToken());

					Point3 v = mVPoints.get(mV_idx - 1);
					Point3 t = new Point3();
					Point3 n = new Point3();
					if (mT_idx == -1)
						t = null;
					else
						t = mTPoints.get(mT_idx - 1);
					if (mN_idx == -1)
						n = null;
					else
						n = mNPoints.get(mN_idx - 1);
					face.addVertex(v, t, n);

					/*
					 * String s = "fsize: " + face.fsize + " fcout: " +
					 * face.fcount + " fv: " + face.fv[face.fcount-1] + " ft: "
					 * + face.ft[face.fcount-1] + " fn: " +
					 * face.fn[face.fcount-1]; Log.e("line", s);
					 */
				}
				faces.add(face);
			}
		}

		boolean has_texture = false;
		boolean has_normals = false;

		if (mTPoints.size() > 0)
			has_texture = true;
		if (mNPoints.size() > 0)
			has_normals = true;

		fill(faces, has_texture, has_normals);

		// Log.e("line", "loading... ...done"+vertices.limit());
	}

	private Point3 read_point(StringTokenizer tok) {
		Point3 ret = new Point3();
		if (tok.hasMoreTokens()) {
			ret.x = Float.parseFloat(tok.nextToken());
			if (tok.hasMoreTokens()) {
				ret.y = Float.parseFloat(tok.nextToken());
				if (tok.hasMoreTokens()) {
					ret.z = Float.parseFloat(tok.nextToken());
				}
			}
		}
		return ret;
	}

	private class Face {
		Point3 fv[];
		Point3 ft[];
		Point3 fn[];
		int fsize;
		int fcount;

		public Face(int size) {
			fsize = size;
			fcount = 0;
			fv = new Point3[size];
			ft = new Point3[size];
			fn = new Point3[size];
		}

		public boolean addVertex(Point3 v, Point3 t, Point3 n) {
			if (fcount >= fsize)
				return false;
			fv[fcount] = v;
			ft[fcount] = t;
			fn[fcount] = n;
			fcount++;
			return true;
		}

		public void pushOnto(FloatBuffer v_buffer, FloatBuffer t_buffer, FloatBuffer n_buffer) {
			int i;
			for (i = 0; i < fsize; i++) {
				v_buffer.put((float) fv[i].x);
				v_buffer.put((float) fv[i].y);
				v_buffer.put((float) fv[i].z);

				if (t_buffer != null && ft[i] != null) {
					t_buffer.put((float) ft[i].x);
					t_buffer.put((float) ft[i].y);
				}

				if (n_buffer != null && fn[i] != null) {
					n_buffer.put((float) fn[i].x);
					n_buffer.put((float) fn[i].y);
					n_buffer.put((float) fn[i].z);
				}
			}
		}
	}

	public void fill(ArrayList<Face> faces, boolean has_tex, boolean has_normals) {
		int f_len = faces.size();

		v_size = f_len * 3;
		vertices = FloatBuffer.allocate(v_size * 3);

		if (has_tex)
			textures = FloatBuffer.allocate(this.v_size * 2);
		if (has_normals)
			normals = FloatBuffer.allocate(this.v_size * 3);

		int i;
		for (i = 0; i < f_len; i++) {
			Face face = faces.get(i);
			face.pushOnto(vertices, textures, normals);
		}

		vertices.rewind();
		if (textures != null)
			textures.rewind();
		if (normals != null)
			normals.rewind();

		//vertices.position(0);
	}

	public void draw(GL10 gl) {
		gl.glFrontFace(GL10.GL_CCW); // Front face in counter-clockwise
		// orientation
		gl.glEnable(GL10.GL_CULL_FACE); // Enable cull face
		gl.glCullFace(GL10.GL_BACK); // Cull the back face (don't display)

		gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
		gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertices);

		// Render all the faces
		/*
		 * for (int face = 0; face < numFaces; face++) { // Set the color for
		 * each of the faces gl.glColor4f(colors[face][0], colors[face][1],
		 * colors[face][2], colors[face][3]); // Draw the primitive from the
		 * vertex-array directly gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, face *
		 * 4, 4); }
		 */
		gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, v_size);
		gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
		gl.glDisable(GL10.GL_CULL_FACE);
	}
}
