package opengl.glslsphere2;

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 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>();
  
  float eyePos[] = {5f, 5f, 10f, 1f};          //viewing position
  float lightPos[] = {5f, 10f, 5f, 1f};        //light source position
  float lightAmbi[] = {0.1f, 0.1f, 0.1f, 1f};  //ambient light
  float lightDiff[] = {1f, 0.8f, 0.6f, 1f};    //diffuse light
  float lightSpec[] = { 0.3f, 0.2f, 0.1f, 1f}; //specular light
  //material same for ambient, diffuse, and specular
  float materialColor[] = {1f, 1f, 1f, 1f};
  float shininess = 50f; 
  short drawOrders[][];   //draw order lists for solid sphere
  
  // 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 );
         
         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);
    
    sphereIndices = new ShortBuffer[nTriangles];
    for ( int i = 0; i < nTriangles; i++) {
      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);
    }
    Log.v ( "size:", Integer.toString( vertices.size() ));
    Log.v ( "n indices", Integer.toString( drawOrders.length ));
  } // 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);
     int eyePosHandle = GLES20.glGetUniformLocation(program, "eyePosition");
     int lightPosHandle = GLES20.glGetUniformLocation(program, "lightPosition");
     int lightAmbiHandle = GLES20.glGetUniformLocation(program, "lightAmbient");
     int lightDiffHandle = GLES20.glGetUniformLocation(program, "lightDiffuse");
     int lightSpecHandle = GLES20.glGetUniformLocation(program, "lightSpecular");
     int materialColorHandle = GLES20.glGetUniformLocation(program, "materialColor");
     int shininessHandle = GLES20.glGetUniformLocation(program, "shininess");
     GLES20.glUniform4fv(eyePosHandle, 1, eyePos, 0);
     GLES20.glUniform4fv(lightPosHandle, 1, lightPos, 0);
     GLES20.glUniform4fv(lightAmbiHandle, 1, lightAmbi, 0);
     GLES20.glUniform4fv(lightDiffHandle, 1, lightDiff, 0);
     GLES20.glUniform4fv(lightSpecHandle, 1, lightSpec, 0);
     GLES20.glUniform1f(shininessHandle, shininess);
     GLES20.glUniform4fv(materialColorHandle, 1, materialColor, 0);
   
     int vertexStride = 0;
        // Draw the sphere
     GLES20.glLineWidth(3);
     for ( int i = 0; i < nTriangles; i++){
    	int positionHandle = GLES20.glGetAttribLocation(program, "vPosition");
    	 // Enable a handle to the triangle vertices
    	 GLES20.glEnableVertexAttribArray( positionHandle);
         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;
  }
}