package opengl.glslsphere1;

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

import android.content.Context;
import java.io.IOException;
import android.util.Log;
import android.opengl.GLES20;

public class Sphere 
{
  private static String LOG_APP_TAG = "io_tag";	
  private Context context;
	  
  private String vsCode = null;
  private String fsCode = null;		
  private int program;
  private int vertexShader;
  private int fragmentShader;
  private FloatBuffer vertexBuffer;
  private FloatBuffer colorBuffer[];
  private int nTriangles;
  ShortBuffer sphereIndices[];
  // number of coordinates per vertex in this array
  static final int COORDS_PER_VERTEX = 3;
  static final int N_STACKS = 16;
  static final int N_SLICES = 24;
  ArrayList<XYZ> vertices = new ArrayList<XYZ>();
  
  // Set color with red, green, blue and alpha (opacity) values
  float color[] = { 0.9f, 0.1f, 0.9f, 1.0f };
  short drawOrders[][];   //draw order lists for solid sphere
  // color for each face
  static float colors[][] = {
        {1.0f, 0.0f, 0.0f, 1.0f},   // 0,1, 2 red
    {0.0f, 1.0f, 0.0f, 1.0f},   // 0, 2, 3 green
    {0.0f, 0.0f, 1.0f, 1.0f},   // 0, 3, 1 blue
    {1.0f, 1.0f, 0.0f, 1.0f}    // 3, 2, 1 yellow
  };

  // Constructor   
  Sphere( Context context0){
    context = context0;
    // get shader codes from res/raw/vshader and res/raw/fshader
    vsCode = getShaderCode( GLES20.GL_VERTEX_SHADER );
    fsCode = getShaderCode( GLES20.GL_FRAGMENT_SHADER );
    program = GLES20.glCreateProgram(); // create empty OpenGL ES Program	  
    vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vsCode );
    fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fsCode );
    GLES20.glAttachShader ( program, vertexShader ); // add the vertex shader to program    
    GLES20.glAttachShader(program, fragmentShader); // add the fragment shader to program
    GLES20.glLinkProgram(program);                  // creates OpenGL ES program executables
    GLES20.glUseProgram( program);

   	createSphere (1.4f, N_SLICES, N_STACKS );
    int nVertices = vertices.size();
    int k = 0;
    int m = N_STACKS;
    int n = N_SLICES;
    // 2n(m-1) slices + 2(m-2)n stacks
    nTriangles = 2 * n * (m - 1);    //number of triangles
    drawOrders = new short[nTriangles][3];
    for ( int j = 0; j < n; j++ )
    {
       for ( int i = 0; i < m-1; i++ ) {
    	 short j1 = (short)(j + 1);
         if ( j == n - 1 ) j1 = 0;   //wrap around
         short ia = (short)( j * m + i ) ;
         short ib = (short)( j * m + i + 1);
         short ic = (short) (j1 * m + i );
         short id = (short)( j1 * m + i + 1 );
         //XYZ a, b, c, d;
         drawOrders[k] = new short[3];
         drawOrders[k][0] = ia;
         drawOrders[k][1] = ib;
         drawOrders[k][2] = ic;
         k++;
         drawOrders[k] = new short[3];
         drawOrders[k][0] = ic;
         drawOrders[k][1] = ib;
         drawOrders[k][2] = id;
         k++;
       }
     }
    System.out.printf ("k=%d, nTriangles=%d\n", k, nTriangles );

    float sphereCoords[] = new float[3*nVertices];
    k = 0;
    for ( int i = 0; i < nVertices; i++ ) {
      XYZ v = vertices.get ( i );
      sphereCoords[k++] = v.x;
      sphereCoords[k++] = v.y;
      sphereCoords[k++] = v.z;
    }
	   
    ByteBuffer bb = ByteBuffer.allocateDirect(sphereCoords.length * 4);
    bb.order(ByteOrder.nativeOrder());
    vertexBuffer = bb.asFloatBuffer();
    vertexBuffer.put(sphereCoords);
    vertexBuffer.position(0);
    // do the same for colors
    colorBuffer = new FloatBuffer[nTriangles];   // a color for each face
    sphereIndices = new ShortBuffer[nTriangles];
   // indexArray = new ShortBuffer[N_FACES];    // N_FACES triangles
    for ( int i = 0; i < nTriangles; i++) {
      int j = i % 4;
      ByteBuffer bbc = ByteBuffer.allocateDirect( colors[j].length * 4 );
      bbc.order(ByteOrder.nativeOrder());
      colorBuffer[i] = bbc.asFloatBuffer();
      colorBuffer[i].put( colors[j] );
      colorBuffer[i].position(0);
      ByteBuffer bbIndices = ByteBuffer.allocateDirect(
              // (# of coordinate values * 2 bytes per short)
             drawOrders[i].length * 2);
      bbIndices.order(ByteOrder.nativeOrder());
      sphereIndices[i] = bbIndices.asShortBuffer();
      sphereIndices[i].put( drawOrders[i] );
      sphereIndices[i].position(0);
    }
  } // Sphere Constructor
	    
  /*
    r = The radius of the sphere. 
    nSlices = The number of divisions around the Z axis, similar to lines of longitude. 
    nStacks = The number of divisions along the Z axis, similar to lines of latitude. 
  */
  private void createSphere ( float r, int nSlices, int nStacks )
  {
     double phi,  theta;	  
     XYZ p = new XYZ();
     final double PI = 3.1415926;
     final double TWOPI = 2 * PI;
		   
     for ( int j = 0; j < nSlices; j++ ) {
       phi = j * TWOPI / nSlices;      
       for ( int i = 0; i < nStacks; i++ ) {
         theta = i * PI / (nStacks-1);  //0 to pi
  	     p.x = r * (float) (Math.sin ( theta ) * Math.cos ( phi ));
       	 p.y = r * (float) (Math.sin ( theta ) * Math.sin ( phi ));
         p.z = r * (float) Math.cos ( theta );
	     vertices.add ( new XYZ ( p ) );
       }
     } 	
  }
		  
  public void draw( float[] mvpMatrix ) {
    // Add program to OpenGL ES environment
    GLES20.glUseProgram(program);
    // get handle to shape's transformation matrix
    int mvpMatrixHandle = GLES20.glGetUniformLocation( program, "mvpMatrix");

     // Pass the projection and view transformation to the shader
     GLES20.glUniformMatrix4fv(mvpMatrixHandle, 1, false, mvpMatrix, 0);

     // Prepare the  coordinate data
     int vertexStride = 0;
     // Draw the sphere
     GLES20.glLineWidth(3);
     for ( int i = 0; i < nTriangles; i++){
    	int positionHandle = GLES20.glGetAttribLocation(program, "vPosition");
    	int colorHandle = GLES20.glGetUniformLocation(program, "vColor");
    	      // Enable a handle to the triangle vertices
    	 GLES20.glEnableVertexAttribArray( positionHandle);
         int j = i % 4;
    	 GLES20.glUniform4fv(colorHandle, 1, colors[j], 0);
         GLES20.glVertexAttribPointer( positionHandle, COORDS_PER_VERTEX,
                           GLES20.GL_FLOAT, false, vertexStride, vertexBuffer);

    	 GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawOrders[i].length,
                     GLES20.GL_UNSIGNED_SHORT, sphereIndices[i] );
    	// Disable vertex array
         GLES20.glDisableVertexAttribArray(positionHandle);

     }	        
  }

  // Get source code of a shader
  protected String getShaderCode( int type )
  {
	 InputStream inputStream = null;
	 String str = null;
	 try {
	   if ( type == GLES20.GL_VERTEX_SHADER )
         inputStream = context.getResources().openRawResource(R.raw.vshader);
	   else
	     inputStream = context.getResources().openRawResource(R.raw.fshader);
       byte[] reader = new byte[inputStream.available()];;
       while (inputStream.read(reader) != -1) {}
       str = new String ( reader );
     }  catch(IOException e) {
       Log.e(LOG_APP_TAG, e.getMessage());
   }
			 
   return  str;
 }

  public static int loadShader (int type, String shaderCode ) {

    // create a vertex shader type (GLES20.GL_VERTEX_SHADER)
    // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
    int shader = GLES20.glCreateShader(type);

    // add the source code to the shader and compile it
    GLES20.glShaderSource(shader, shaderCode);
    GLES20.glCompileShader(shader);

    return shader;
  }	    
}

class XYZ {
  float x = 0, y = 0, z = 0;
  
  XYZ ()
  {
	  x = y = z = 0;
  }
  
  XYZ ( float x0, float y0, float z0 )
  {
	  x = x0;
	  y = y0;
	  z = z0;
  }
  
  XYZ ( XYZ p )
  {
	  x = p.x;
	  y = p.y;
	  z = p.z;
  }
}
