package testing.testing;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.os.SystemClock;
import android.view.Window;
import android.view.WindowManager;
import android.view.ViewGroup.LayoutParams;
import android.widget.LinearLayout;

public class MainActivity
  extends Activity implements GLSurfaceView.Renderer
{
  private GLSurfaceView view;
  private Block block;
  
  @Override
  public void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    
    block = new Block(4f, 4f, 4f);
    
    use_full_screen();
    
    view = new GLSurfaceView(this);
    
    view.setLayoutParams(new LinearLayout.LayoutParams
        (LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));

    view.setRenderer(this);
    setContentView(view);
  }

  private void use_full_screen()
  {
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    
    getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
        WindowManager.LayoutParams.FLAG_FULLSCREEN);
  }

  @Override
  public void onSurfaceCreated(GL10 gl, EGLConfig config)
  {}

  public void onSurfaceChanged(GL10 gl, int width, int height)
  {
    float aspectRatio = (float) width / (float) height;

    gl.glViewport(0, 0, width, height);

    gl.glMatrixMode(GL10.GL_PROJECTION);
    gl.glLoadIdentity();
    
    gl.glFrustumf(-1f, 1f , -1 / aspectRatio, 1 / aspectRatio, 3f, 40f);
  
    gl.glMatrixMode(GL10.GL_MODELVIEW);
    
    gl.glClearColor(0.5f, 0.6f, 0.7f, 1f);

    gl.glEnable(GL10.GL_LIGHTING);
    gl.glEnable(GL10.GL_LIGHT0);
    
    gl.glEnable(GL10.GL_COLOR_MATERIAL);
    
    gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, new float[]{0.3f, 0.3f, 0.3f, 1f}, 0);
    gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, new float[]{0.8f, 0.8f, 0.8f, 1f}, 0);
    
    enable_blending(gl);
    
    gl.glEnable(GL10.GL_DEPTH_TEST);

    gl.glMaterialfv
      (GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, FloatBuffer.wrap
        (new float []{ 0.7f, 0.7f, 0.7f, 1.0f}));

    gl.glMaterialfv
      (GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, FloatBuffer.wrap(new float[] {7f}));
  }
  
  @Override
  public void onDrawFrame(GL10 gl)
  {
    gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
    
    gl.glLoadIdentity();
    
    gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, new float[]{10f, 10f, 0f}, 1);
    
    gl.glTranslatef(0f, 0f, -20f);    
    
    long tick = SystemClock.uptimeMillis();
    
    float angle1 = tick * 0.032f;
    float angle2 = (float)(30 * Math.sin(tick * 0.011));
    
    gl.glRotatef(angle1, 1, 0, 0);
    gl.glRotatef(angle2, 0, 1, 0);

    gl.glColor4f(1f, 0f, 0f, 1f);
    draw_mesh(gl, block);
    
    gl.glFlush();    
  }
  
  private void draw_mesh(GL10 gl, IUnindexedUncoloredMesh mesh)
  {
    gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
    gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);
    gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
    
    gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mesh.get_vertex_composants());
    gl.glNormalPointer(GL10.GL_FLOAT, 0, mesh.get_normal_composants());

    gl.glDrawArrays(GL10.GL_TRIANGLES, 0, mesh.get_num_vertices());        
  }
  
  private void enable_blending(GL10 gl)
  {
    gl.glEnable(GL10.GL_BLEND);
    gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
  }
}

class Block implements IUnindexedUncoloredMesh
{
  private FloatBuffer vertex_composants;
  private FloatBuffer normal_composants;
  int num_vertices;

  public Block(float width, float height, float depth)
  {
    OpenGLArraysBuilder builder = new OpenGLArraysBuilder();

    float w_2 = width / 2;
    float h_2 = height / 2;
    float d_2 = depth / 2;
    
    FloatVertex top_left_hither = new FloatVertex(-w_2, h_2, d_2);
    FloatVertex top_left_yon = new FloatVertex(-w_2, h_2, -d_2);

    FloatVertex btm_left_hither = new FloatVertex(-w_2, -h_2, d_2);
    FloatVertex btm_left_yon = new FloatVertex(-w_2, -h_2, -d_2);

    FloatVertex top_right_hither = new FloatVertex(w_2, h_2, d_2);
    FloatVertex top_right_yon = new FloatVertex(w_2, h_2, -d_2);

    FloatVertex btm_right_hither = new FloatVertex(w_2, -h_2, d_2);
    FloatVertex btm_right_yon = new FloatVertex(w_2, -h_2, -d_2);
    
    FloatNormal top_normal = new FloatNormal(0, 1, 0);
    FloatNormal btm_normal = new FloatNormal(0, -1, 0);
    FloatNormal left_normal = new FloatNormal(-1, 0, 0);
    FloatNormal right_normal = new FloatNormal(1, 0, 0);
    FloatNormal hither_normal = new FloatNormal(0, 0, 1);
    FloatNormal yon_normal = new FloatNormal(1, 0, -1);
    
    /* top */
    builder.add_point(new NormalPoint(top_left_hither, top_normal));
    builder.add_point(new NormalPoint(top_right_hither, top_normal));
    builder.add_point(new NormalPoint(top_left_yon, top_normal));
    
    builder.add_point(new NormalPoint(top_right_yon, top_normal));
    builder.add_point(new NormalPoint(top_left_yon, top_normal));
    builder.add_point(new NormalPoint(top_right_hither, top_normal));
    
    /* hither */
    builder.add_point(new NormalPoint(top_left_hither, hither_normal));
    builder.add_point(new NormalPoint(top_right_hither, hither_normal));
    builder.add_point(new NormalPoint(btm_left_hither, hither_normal));
    
    builder.add_point(new NormalPoint(top_right_hither, hither_normal));
    builder.add_point(new NormalPoint(btm_right_hither, hither_normal));
    builder.add_point(new NormalPoint(btm_left_hither, hither_normal));

    /* right */
    builder.add_point(new NormalPoint(top_right_hither, right_normal));
    builder.add_point(new NormalPoint(btm_right_hither, right_normal));
    builder.add_point(new NormalPoint(top_right_yon, right_normal));
    
    builder.add_point(new NormalPoint(btm_right_hither, right_normal));
    builder.add_point(new NormalPoint(btm_right_yon, right_normal));
    builder.add_point(new NormalPoint(top_right_yon, right_normal));

    /* bottom */
    builder.add_point(new NormalPoint(btm_left_hither, btm_normal));
    builder.add_point(new NormalPoint(btm_left_yon, btm_normal));
    builder.add_point(new NormalPoint(btm_right_hither, btm_normal));
    
    builder.add_point(new NormalPoint(btm_right_yon, btm_normal));
    builder.add_point(new NormalPoint(btm_right_hither, btm_normal));
    builder.add_point(new NormalPoint(btm_left_yon, btm_normal));
    
    /* yon */
    builder.add_point(new NormalPoint(top_left_yon, yon_normal));
    builder.add_point(new NormalPoint(btm_left_yon, yon_normal));
    builder.add_point(new NormalPoint(top_right_yon, yon_normal));
    
    builder.add_point(new NormalPoint(top_right_yon, yon_normal));
    builder.add_point(new NormalPoint(btm_left_yon, yon_normal));
    builder.add_point(new NormalPoint(btm_right_yon, yon_normal));
    
    /* left */
    builder.add_point(new NormalPoint(top_left_hither, left_normal));
    builder.add_point(new NormalPoint(top_left_yon, left_normal));
    builder.add_point(new NormalPoint(btm_left_hither, left_normal));
    
    builder.add_point(new NormalPoint(btm_left_hither, left_normal));
    builder.add_point(new NormalPoint(top_left_yon, left_normal));
    builder.add_point(new NormalPoint(btm_left_yon, left_normal));    
    
    
    vertex_composants = builder.get_vertex_composants();
    normal_composants = builder.get_normal_composants();
    num_vertices = builder.get_num_elements();
  }

  @Override
  public FloatBuffer get_vertex_composants()
  { return vertex_composants; }

  @Override
  public FloatBuffer get_normal_composants()
  { return normal_composants; }

  @Override
  public int get_num_vertices()
  { return num_vertices; }
}

class BufferUtil
{
  public static ShortBuffer create_short_buffer(short[] input)
  {
    ByteBuffer buffer0 = ByteBuffer.allocateDirect((input.length) * 2);

    buffer0.order(ByteOrder.nativeOrder());
    
    ShortBuffer buffer = buffer0.asShortBuffer();

    buffer.put(input);
    buffer.position(0);

    return buffer;
  }
  
  public static FloatBuffer create_float_buffer(float[] input)
  {
    ByteBuffer buffer0 = ByteBuffer.allocateDirect((input.length) * 4);
    
    buffer0.order(ByteOrder.nativeOrder());
    
    FloatBuffer buffer = buffer0.asFloatBuffer();

    buffer.put(input);
    buffer.position(0);

    return buffer;
  }
}

class OpenGLArraysBuilder
{
  private ArrayList<FloatVertex> vertices = new ArrayList<FloatVertex>();
  private ArrayList<FloatNormal> normals = new ArrayList<FloatNormal>();
  
  public void add_point(NormalPoint point)
  {
    vertices.add(point.vertex);
    normals.add(point.normal);
  }
  
  public FloatBuffer get_normal_composants()
  {
    float[] buffer = new float[get_num_elements() * 3];

    int i = 0;
    for (FloatNormal normal: normals)
    {
      buffer[i++] = normal.dx;
      buffer[i++] = normal.dy;
      buffer[i++] = normal.dz;
    }
    
    return BufferUtil.create_float_buffer(buffer);
  }

  public FloatBuffer get_vertex_composants()
  {
    float[] buffer = new float[get_num_elements() * 3];

    int i = 0;
    for (FloatVertex vertex: vertices)
    {
      buffer[i++] = vertex.x;
      buffer[i++] = vertex.y;
      buffer[i++] = vertex.z;
    }
    
    return BufferUtil.create_float_buffer(buffer);
  }

  public int get_num_elements()
  { return vertices.size(); }
}

class NormalPoint
{
  public FloatVertex vertex;
  public FloatNormal normal;
  
  public NormalPoint(FloatVertex vertex, FloatNormal normal)
  {
    this.vertex = vertex;
    this.normal = normal;
  }
}

class FloatVertex
{
  public float x, y, z;
  
  public FloatVertex(float x, float y, float z)
  {
    this.x = x;
    this.y = y;
    this.z = z;
  }
  
  public FloatVertex(double x, double y, double z)
  {
    this.x = (float)x;
    this.y = (float)y;
    this.z = (float)z;
  }  
}

class FloatNormal
{
  public float dx, dy, dz;
  
  public FloatNormal(double dx, double dy, double dz)
  {
    double length = Math.sqrt(dx * dx + dy * dy + dz * dz); 
    this.dx = (float)(dx / length);
    this.dy = (float)(dy / length);
    this.dz = (float)(dz / length);
  }  
}

interface IUnindexedUncoloredMesh
{
  FloatBuffer get_vertex_composants();
  FloatBuffer get_normal_composants();

  int get_num_vertices();
}
