//=====================================================================//
// Program by Anna Shleyfman
// CS 8803e -- Geomorph
// Project 1
//=====================================================================//

import java.io.*;
import javax.vecmath.*;
import java.util.Random;                
import java.util.Arrays; //used to quicksort the edge table

import Delaunay;
import Display;

//======================================================================
//                            Class Geomorph
//======================================================================

public class Geomorph 
{

    //======================================================================
    //                         Declarations 
    //======================================================================
    /**
     * 2D array of two dimentional vertices
     */
    Point3d _vertices[] = null;      // vertex table (V)
    final int num_vert = 250;       // number of vertices in the model
    protected int _vindex[] = null;  // triangle (corner) table
    protected int num_t = 0;         // current num of tri in _vindex[]

    int _o[] = null;                 // opposite table
                                     // if entry == -1, edge is on the border
                                     // if entry == -2, this triangle is marked
                                     //                 degenerate

    Delaunay del;                    // for vecmeth operations
    static Display dis;
    static Random rand;              // random number generator - requires seed
    //===============================================================

    int _m[] = null;  //this table sets the new vertices to 0, existing to 1
    int numFixed =0;  // number of fixed points in the model (depends on step)
    Point3d[] vert_prev = null;  // copy of the previous state ov vertex table

    //======================================================================
    //                          Constructors
    //======================================================================
    /**
     * Default constructor
     */ 
    public void Geomorph()
    {
	// this is a default constructor
    }

    //======================================================================
    //                             Accessors
    //======================================================================
 
    /**
    *   returns coordinates of vertex, given the vertex index
    *   negative vertices are used to create delaunay triangulation
    **/ 
    protected Point3d g_vert( int vindex )  //get vertex
    {
	Point3d foundVertex;

	switch( vindex ){
	case -1:
	    foundVertex = new Point3d( 3, -2, 0 );
	    break;
	case -2:
	    foundVertex = new Point3d( 0, 3, 0 );
	    break;
	case -3:
	    foundVertex = new Point3d( -3, -2, 0 );
	    break;
	case -5:  // test case
	    foundVertex = new Point3d( -5, -5, 0 );
	    break;
	case -6:  // test case
	    foundVertex = new Point3d( 0, 0, 0 );
	    break;
	case -7:  // test case
	    foundVertex = new Point3d( 0, 2, 0 );
	    break;
	default:         // vindex >= 0
	    foundVertex = _vertices[ vindex ];
	}// switch vindex

	return foundVertex;	
    }// g_vert (get vertex)


    //===============================================================    

    /**
     * returns the opposite corner
     * if opposite corner == -1, it's a border edge, and opposite corner
     * doesn't exist.
     */
    private int o( int corner ) 
    {
        return _o[ corner ];
    }

    //===============================================================

    /**
     * given a corner, return corresponding triangle 
     */
    private int t( int corner ) 
    {
    	return corner/3;
    }

    //===============================================================
    
    /** 
    *    returns the vertex number of the passed corner
    **/
    protected int g_ind( int corner )  //get vertex index
    {
        return _vindex[ corner ];
    }    

    //===============================================================
    /**
     * returns the corner of the opposite triangle that shares right edge
     * with current triangle
     */
    private int right( int corner )
    {
	return o( p( corner ) );
    }//right

    //===============================================================
    /**
     * returns the corner of the opposite triangle that shares left edge
     * from the given corner with current triangle
     * Can return -1 if opposite doesn't exist
     */
    private int left( int corner )
    {
	return o( n( corner ) );
    }//right

    //===============================================================
    /** 
    *   @return previous corner
    *   corners oriented counterclockwise
    *   reused Volker's code
    */
    protected int p(int corner) 
    {
	int previous=0;
	int t = corner/3;       

	previous = corner-3*t;  // p E [0,2]
	previous--;

	if (previous==-1)
	    previous=2;

 	previous +=3*t;
	
      	return previous;
    }// previous

    //===============================================================

    /** 
    *   @return next corner
    *   corners oriented counterclockwise
    *   reused Volker's code
    */
    protected int n(int corner) 
    {
	int next=0;
	// integer-values: / is same as DIV
 	
	int t = corner/3;
	next = corner - 3*t;
	
	// modulo 
	next++;
	
	if( next==3 )
	    next=0;
	
	next += 3*t;
	
	//System.out.println("DEBUG: corner "+ corner+" next corner "+next);
	return next;
    }// next


    //======================================================================
    //                          Vertex creation
    //======================================================================
    /**
     * generates a random coordinate in the range [-0.5 , 0.5 ]
     */
    private double make_coord( Random rand )
    {
	return (rand.nextDouble() - 0.5);
    }// make_coord

    //======================================================================
    /**
     * Assigns values to the coordinates of pints in _vertices aray.
     * Each vertex is 2D.  Coordinate values will range from [-0.5, 0.5]
     */
    private void init_vertices()
    {
	double vertex;
	Random rand = new Random(); //instantiate class to generate 
	                            //random values

	_vertices = new Point3d[ num_vert ];   // vertex table (V)

	for( int i=0; i < num_vert; i++ ){     // init _vertices array
	    _vertices[i] = new Point3d( make_coord( rand ), 
					make_coord( rand ), 0 );
	}//for

    } //init_vertices
	

    //========================================================================
    // Anna's code goes here
    //========================================================================

    /*
     * Prompts the user with the passed promptStr and converts user's
     * input into double
     * The input should be in the range between 0 and 1.
     * @param promtStr String to prompt the user
     * @return integer value of the string
     * <br>
     */
    private double promptDouble( String promptStr )
    {
	BufferedReader in = new BufferedReader( new 
	                                        InputStreamReader(System.in));
	String sInput = ""; // text input
	double dInput = 1;  // integer value of sInput

	boolean flag = false;

	while( flag == false ){
	    try {
		System.out.println( promptStr );
		sInput = in.readLine();
		dInput = Double.parseDouble( sInput );

		if( (dInput < 0) || (dInput > 1) ){
		    System.out.println( "\nI N V A L I D  input, try again" );
		    continue;
		}
		flag = true;
	    }//try
	    catch (IOException e) {
		System.out.println( e );
	    }// catch
	}//while

	return dInput;
    }// promptDouble

    //========================================================================
    /*
     * Prompts the user with the passed promptStr to hit a key
     * @param promtStr String to prompt the user
     * <br>
     */
    private void hitKeyToCont( String promptStr )
    {
	BufferedReader in = new BufferedReader( new 
	                                        InputStreamReader(System.in));
	String sInput;  // text input

	try {
	    System.out.println( promptStr );
	    sInput = in.readLine();
	}//try
	catch (IOException e) {
	    System.out.println("IOException " + e);
	}// catch

    }// promptInteger

    //========================================================================
    /*
     * Prompts the user with the passed promptStr and converts user's
     * input into integer
     * @param promtStr String to prompt the user
     * @return integer value of the string
     * <br>
     */
    private int promptInteger( String promptStr )
    {
	BufferedReader in = new BufferedReader( new 
	                                        InputStreamReader(System.in));
	String sInput;  // text input
	int iInput;     // integer value of sInput

	try {
	    System.out.println( promptStr );
	    sInput = in.readLine();
	    iInput = Integer.parseInt( sInput );
	}//try
	catch (IOException e) {
	    System.out.println("IOException " + e);
	    iInput = -1;
	}// catch

	return iInput;
    }// promptInteger

    //===============================================================
    /**
     * creates _m table 
     * does NOT change the _vertices table
     */
    private void create_M_table()
    {
	numFixed = 0;
	_m = new int[ _vertices.length ];

	// init the _m table
	for( int i=0; i<_m.length; i++ ){
	    _m[i]=0;
	}// for init _m

	for( int i=0; i<_o.length; i++ ){
	    if( _o[i] == -1 ){
		_m[ g_ind( n(i) ) ] = 1; //fix vertex
		numFixed++;
	    }//if
	}//for

    }//create_M_table

    //===============================================================

    /**
     * adds random z coordinate for each vtx
     */
    private void add3dimension()
    {
	for( int i=0; i < _vertices.length; i++ ){  
	    _vertices[i].z =  make_coord( rand );
	}//for

    }// add3dimension

    //===============================================================
    /**
     * resets the coordinates of nonfixed point to zero if user so desires
     */
    private void init_m_to_zero()
    {
	Point3d zero = new Point3d( 0.0, 0.0, 0.0 );
	
	for( int i=0; i<_m.length; i++ ){
	    if( _m[i] == 0 )
		_vertices[i] = zero;
	}//for

    }//init_m_to_zero

    //===============================================================

    /**
     * For the given corner, compute the center of gravity from all of
     * the neighbors, and set the _vertices table entry for this corner
     * (coordinates) to the new compluted value.  The updated model is 
     * then displayed.
     */
    private Point3d computeCenterOfGravity( int c )
    {
	Point3d sum = new Point3d( 0.0, 0.0, 0.0 );
	int numNeighbors = 0;   // number of triangles incident on c
	int next;               // dummy looping variable
	int start = n( c );	// a corner of a triangle incident on c (!=c)

	sum.add( sum, _vertices[ g_ind( start ) ] );
	next = p( o( start ) );               //c.n.o.p
	//	System.out.println( "next" + next );

	numNeighbors++;
	//changeColor( t( start ), violet ); 

	if( next < 0 ){
	    return g_vert( g_ind( c ));
	}

	while( next != start ) {
	    //changeColor( t( next ), violet ); 
	    sum.add( sum, _vertices[ g_ind( next ) ] );
	    numNeighbors++;
	    next = p( o( next ) );     //next.n.o.p -- reset for next iteration
	    if( next < 0 ){
		return g_vert( g_ind( c ));
	    }
	}
	
	//compute center of gravity
	if( numNeighbors != 0 ){
	    sum.scale( 1.0/ ((double)numNeighbors) );
	}

	return sum;

    }// computeCeneterOfGravity

    //===============================================================

    /**
     * this method is used instead of one in the library of Point3d,
     * which does not work properly, and sets 2 arrays instead of one.
     */
    private Point3d add( Point3d a, Point3d b )
    {
	return (new Point3d( a.x + b.x, a.y + b.y, a.z + b.z ));
    }//add


    //===============================================================

    /**
     * this method is used instead of one in the library of Point3d,
     * which does not work properly, and sets 2 arrays instead of one.
     */
    private Point3d sub( Point3d a, Point3d b )
    {
	return (new Point3d( a.x - b.x, a.y - b.y, a.z - b.z ));
    }//add

    //===============================================================

    /**
     * This method is called when selected vertex (defined by corner c) is fixed
     * It lifts the neighbors of the vertex by difference between its position 
     * and the computed sum, then updates the display
     */
    private void lift_neighbors( int c, double thresh, Point3d sum )
    {
	// get the vertex coordinates for corner c
	Point3d lift = new Point3d( g_vert( g_ind( c ) ) );
	int next;                  // dummy looping variable
	int start = n( c );	   // a corner of a triangle incident on c (!=c)

	lift.sub( sum );           // subtract sum from lift
	lift.scale( thresh );      // lift * thresh

	if( _m[ g_ind( start ) ] == 0 ){
	    //changeColor( t( start ), yellow ); 
	    set_vert( start,  add( lift, _vertices[ g_ind( start ) ] ));
	}//if
	next = p( o( start ) );    //c.n.o.p

	while( next != start ) {
	    if( _m[ g_ind( next ) ] == 0 ){
		//changeColor( t( next ), yellow ); 
		set_vert( next,  add( lift, _vertices[ g_ind( next ) ] ));
	    }//if
	    next = p( o( next ) ); //next.n.o.p -- reset for next iteration
	}//while

    }//lift_neighbors

    //===============================================================

    /**
     * set the _vertices table entry for this corner to the new 
     * compluted value.  The updated model is then displayed.
     */
    private void set_vert( int corner, Point3d value )
    {
	_vertices[ g_ind( corner ) ] = value;
	dis.updateMesh();
    }
    //===============================================================

    /**
     * returns random corner/vertex of the model depending on scale parameter
     */
    public int get_rand( int scale )
    {
	return (( int )java.lang.Math.floor( scale * rand.nextDouble() ));	
    }

    //===============================================================

    /**
     * set the _vertices table entry for this corner to the new 
     * compluted value.  The updated model is then displayed.
     * @param c -- corner id
     * @param thresh - threshold
     * @param sum - average position of the corner's neighbors
     */
    private void set_vert( int c, double thresh, Point3d sum )
    {
	// get the vertex coordinates for corner c
	int ind = g_ind( c );      //vertex index
	Point3d move = new Point3d( g_vert( ind ) ); 
	
	//compute distance for vertex movement
	move.sub( sum );       // pos - sum
	move.scale( thresh );  // move * thresh

	set_vert( c,  sub( _vertices[ ind ], move ));

    }//set_vert

    //===============================================================

    /**
     * This method finds the step from the user, and then shrinks the 
     * model by setting every point in the step to it's value, and 
     * finding the center of gravity to the rest of the points.
     */
    public void shrink()
    {
	int c = 24;   //random corner to shrink
	int repeat = 1;
	int numCorners = _o.length - 1; // used for random num generator
	Point3d sum;  //used for moving the vertices during shrink
	int seePrevious = 0;

	//prompt user to decide if to lift the neighbors when 
	//fixed vertex is hit
	int lift = 0;

	double thresh = promptDouble("Please input decimal threshold [0,1];\n"
				     + "0 will leave model unchanged: ");

	//prompt user for the number of collapses
	repeat = promptInteger( "Please input number of shrinks: " );

	while( repeat > 0 ){
	    for( int i=0; i<repeat; i++){

		c = get_rand( numCorners );
		if( _m[ g_ind(c) ] >= 0 ){

		    //System.out.println( "corner: " + c );
		
		    int vert = g_ind(c); //identify a vertex that lies upon 
		                         //this corner
		    sum = computeCenterOfGravity( c );
		    
		    if( _m[vert] == 0 ){
			set_vert( c, thresh, sum );
		    }//if
		    else if( lift == 1 ){  //lift the neighbors, update display
			lift_neighbors( c, thresh, sum );
		    }
		}// if nonfixed vertex
	    }//for
	    
	    /*
	    seePrevious = 0;
	    while( true ){
		seePrevious = promptInteger( "If you'd like to see previous " +
					     "model state enter 1, else 0: ");
		if( seePrevious == 1 ){
		    swap_vert_and_previous();
		    dis.updateMesh();
		}else{
		    break;
		}
	    }
	    move_vert_to_previous();
	    */
	    repeat = promptInteger( "Please input number of shrinks: " );

	}//while
	System.exit(1);
    }//shrink

    //========================================================================
    // init the copy of prevous state of _vertices[] 

    private void init_vert_prev()
    {
	vert_prev = new Point3d[ _vertices.length ];
	//copy vertices into vert_prev
	move_vert_to_previous();
    }//

    //========================================================================
    /**
     * copy _vertices[] into vert_prev
     */
    private void move_vert_to_previous()
    {
	//copy vertices into vert_prev
	System.arraycopy( _vertices, 0, vert_prev, 0, _vertices.length );
    }//

    //========================================================================
    /**
     * copy _vertices[] into vert_prev
     */
    private void swap_vert_and_previous()
    {
	Point3d[] temp = new Point3d[ _vertices.length ];

	System.arraycopy( vert_prev, 0, temp, 0, _vertices.length );

	//copy vert_prev into vertices 
	System.arraycopy( _vertices, 0, vert_prev, 0, _vertices.length );
	_vertices = temp;
	System.out.println( "Swapping " );
    }//

    //========================================================================
    // Anna's code ends here
    //========================================================================


    //======================================================================
    //                            Debugging
    //======================================================================
    /**
     * prints out _vindex[] for testing purposes
     */
    public void print_vindex()
    {
	System.out.println( "num_t: " + num_t + "printing _vindex: ");
	for( int i=0; i<num_t*3; i++)
	    System.out.println( i + " v: " + _vindex[i] + " o: " + _o[i] );
    }

    //======================================================================
    /**
     * count border edges
     * return num of border edges
     */
    public int count_b_edges()
    {
	int count =0;

	for( int i=0; i<_o.length; i++ ){
	    if( _o[i] == -1 )
		count++;
	}
	
	System.out.println( "Num border edges = " + count );
	System.out.println( "Num Triangles = " + num_t );

	return count;
    }//count_border_edges

    //======================================================================
    //                              Main
    //======================================================================

    /**
     * main is used for testing purposes and to run the program
     */
    public static void main(String argv[]) 
    {
	Geomorph geo = new Geomorph();
	geo.del = new Delaunay( geo );  //globally declared
	dis = new Display( geo );
	int toCollapse = 0;
	rand = new Random();  	// seed random number generator		

	geo.init_vertices();    // create vertices
	geo.del.delaunay();     // create triangle mesh, init _vindex[] & _o[]
	geo.add3dimension();    // adds random z coordinate for each vtx

	geo.create_M_table();   // length = num vertices
	dis.drawmesh();
	geo.hitKeyToCont("Hit key to continue");

	geo.init_m_to_zero();   // sets nonfixed point coordinates to zero
	//geo.init_vert_prev();   // init copy of prevous state of _vertices[] 
	dis.updateMesh();
	//geo.print_vindex();
	geo.shrink();           // shrink the model

	return;
    }// main
    //======================================================================

}// end of Geomorph class
//======================================================================
//                       end of Geomorph class
//======================================================================
