import java.awt.*;
import java.net.*;
import java.io.*;
import java.util.*;
import java.text.*;

public class Repel2 extends BufferedApplet
{
    RepelThread2 rt = null;

    String[] spinButtonLabels = {"SPIN","STOP"};
    Button spinButton;

    String[] sliderLabels =
       {"TREND1","TREND2","TREND3","ENERGY","GRAVITY"};
    Slider[] slider = new Slider[sliderLabels.length];

    public void stop() {
       super.stop();
       if (rt != null)
          rt.stop();
    }

    public static double[] richCheap; // array stores rich/cheap values for all futures
    public static String[] indices;   // array stores index symbols for all futures
    public static String[] futures;   // array stores futures symbols
    public static Hashtable indexOrder = new Hashtable(); 
    int maxRichCheap, minRichCheap;
    String getFuturesURL; // = getParameter("getFuturesURL");

    int numberOfIndices = 1;          // default number of indices
    NumberFormat nf = NumberFormat.getInstance();

    int n=8, N[] = {4,6,8,12,20,50,70,100,140,200,280,400,560,800,1120,1600};
    double theta = 0, cos = 0, sin = 0;
    int mx = 0, px = -1, py = 0, chosen = -1;
    boolean mouseIsDown = false;

    public void init()
    {
	try
	{
	    getFuturesURL = getParameter("getFuturesURL");
	    System.out.println("Read params: " + getFuturesURL);
	}
	catch(Exception e)
	{
	    System.out.println("Error reading params: " + e);
	}

	P = initP(); // this was previously initialized below ( search for "initP" )

	System.out.println("indexOrder=" + indexOrder); 


    }

    public boolean mouseDown(Event e, int x, int y) {
       mouseIsDown = true;
       if (spinButton.down(x,y))
	  return true;
       for (int i = 0 ; i < slider.length ; i++)
          if (slider[i].down(x,y))
	     return true;
       if (x >= 50 && x < w-20)
	   mx = x;
       return true;
    }
    public boolean mouseMove(Event e, int x, int y) {
       mouseIsDown = false;
       x -= w/2;
       y -= h/2;
       if (x*x + y*y < w*w/16) {
	  px = x + w/2;
	  py = y + h/2;
       }
       else
	  px = -100;
       return true;
    }
    public boolean mouseDrag(Event e, int x, int y) {
       mouseIsDown = true;
       if (spinButton.drag(x,y))
	  return true;
       for (int i = 0 ; i < slider.length ; i++)
	  if (slider[i].drag(x,y)) 
	  {
	      rt.setVar(i, slider[i].getValue());
	      return true;
          }
       if (x >= 100) 
       {
	   if (chosen >= 0) 
	   {
	       px = x;
	       py = y;
	   }
	   else 
	   {
	       rotate(-.01 * (x - mx));
	       mx = x;
	   }
       }
       return true;
    }
    public boolean mouseUp(Event e, int x, int y) 
    {
	mouseIsDown = false;
	px = -100;
	if (spinButton.up(x,y))
	    return true;
	for (int i = 0 ; i < slider.length ; i++)
	    if (slider[i].up(x,y))
		return true;

	// process the number of points change
	/*
	if (x < 50) 
	{
	    int i = (y - 10) / 14;
	    if (i >= 0 && i < N.length) 
	    {
		n = i;
		P = initP();
		rt.set(P, nColors);
	    }
	    return true;
	} 
	*/


	/* 
	// process the number of colors change!
	if (x >= w-20) 
	{
	    // number of coloring option: 0 - 1 color, 1 - all (as many as there are indices!) colors
	    int i = (y - 10) / 14 + 1; 
	    //System.out.println("i=" + i); 
	    if (i == 1)
	    {
		rt.set(P, nColors = i);
	    }
	    else if (i == 2)
	    {
		// number of indices
		rt.set(P, nColors = numberOfIndices);
	    }

	    //if (i >= 1 && i <= 9)
		//rt.set(P, nColors = i);
	} */

	return true;
    }

    void rotate(double t) 
    {
	// theta is an angle between the -z axis (i.e., the direction
	// towards the observer from inside the screen), and the
	// current point in the horizontal plane. w/4 is acrually the
	// radius of the sphere. So, 
	theta += t;
	//System.out.println("Rotating by " + t + "; theta = " + theta); 
	cos = w/4 * Math.cos(theta);
	sin = w/4 * Math.sin(theta);
    }

    /////////////////////////////////////////
    // draw all the elements of the applet!
    /////////////////////////////////////////
    int w = 0, h;
    public void render(Graphics g) 
    {
	//System.out.println("-1, N[n]=" + N[n]); 
	int i, j, k;
	if (w == 0) 
	{
	    w = bounds().width;
	    h = bounds().height;
	    rotate(0);
	    //System.out.println("-1.1"); 
	    spinButton = new Button(3, h-20, 34, 15);
	    spinButton.labels = spinButtonLabels;

	    for (i = 0 ; i < slider.length ; i++) 
	    {
		slider[i] = new Slider(w/2, h+(i-slider.length)*20, w/2-5, 15);
		slider[i].label = sliderLabels[i];
	    }
	    //System.out.println("-1.2"); 
	    slider[RepelThread2.TREND1].setValue(.9);
	    slider[RepelThread2.TREND2].setValue(.1);
	    slider[RepelThread2.TREND3].setValue(.9);
	    slider[RepelThread2.ENERGY].setValue(2); // max
	    //System.out.println("-1.3"); 
	    rt = new RepelThread2(slider.length);
	    rt.set(P, nColors);
	    for (int s = 0 ; s < slider.length ; s++)
		rt.setVar(s, slider[s].getValue());
	    rt.start();
	    //System.out.println("-1.4"); 
	    rt.setRichCheap(richCheap); 
	    //System.out.println("rt:" + rt.toTheString() + "\nrich_cheap has " + richCheap.length + " items"); 
	}

	//System.out.println("-2"); 

	g.setColor(Color.white);
	g.fillRect(0,0,w,h);
	
	int rad = 4; //(int)(4 + 4 * slider[5].getValue());    // radius

	// paint the circle with bleak pink
	g.setColor(sphereColor);
	g.fillOval(w/4+rad,h/4+rad,w/2-2*rad,h/2-2*rad);

	// write numbers: zero level, min and max
	g.setColor(Color.black); 
	g.drawString("0.00", w/4 - 30, w/2 + 5); // zero
	g.drawString(""+(int)richCheap[0], w/2-15, w/4 - 7); // min
	g.drawString(""+(int)richCheap[richCheap.length-1], w/2-12, 3*w/4 + 15); // max

	// draw meridians
	g.setColor(lightGray); 
	for (j=0; j<7; j++)
	{
	    //int rc_val = (int)(richCheap[0] - (j+1)*(richCheap[0] - richCheap[richCheap.length-1])/8);
	    //g.setColor(Color.black);
	    //g.drawString("" + rc_val, 
	    //	 w/2 - (int) Math.sqrt(sqr(w/4) - sqr( w/4 - (j+1)*w/16 )) - 30, 
	    //	 w/4 + (j + 1) * (w/16) + 5); 
	    //g.setColor(lightGray); 
	    g.drawLine(1 + rad + w/2 - (int) Math.sqrt(sqr(w/4) - sqr( w/4 - (j+1)*w/16 )), 
		       w/4 + (j + 1) * (w/16), 
		       w/2 + (int) Math.sqrt(sqr(w/4) - sqr( w/4 - (j+1)*w/16 )) - rad - 1,
		       w/4 + (j + 1) * (w/16)); 
	}

	//System.out.println("-3"); 
	
	//////////////////////////////////////////////////////////////
	// Paint only the points on the back hemisphere, 
	// and paint them bleak
	//////////////////////////////////////////////////////////////
	for (i = 0 ; i < N[n] ; i++)
	    if (sin * P[i][0] - cos * P[i][2] <= -w/20)
	    {
		int x = w/2 + (int)(cos * P[i][0] + sin * P[i][2]);
		int y = h/2 + (int)(h/4 * P[i][1]);
		g.setColor(backColor[colorId(i)]);
		g.fillOval(x-rad,y-rad,2*rad,2*rad);
	    }
	g.setColor(edgeColor);
	g.drawOval(w/4+rad,h/4+rad,w/2-2*rad,h/2-2*rad);
	
	//System.out.println("1"); 

	///////////////////////////////////////////////
	// draw legend for indices
	///////////////////////////////////////////////
	int ctr = 0;
	g.setColor(Color.black);
	g.drawString("Legend of Indices", 10, 40);
	for (Enumeration e = indexOrder.keys(); e.hasMoreElements() ; )
	{
	    String idx = (String) e.nextElement(); 
	    //System.out.println("idx=" + idx); 
	    g.setColor( color[((Integer) indexOrder.get(idx)).intValue()] ); 
	    g.drawRect(10, 50 + ctr*20, 20, 10); 
	    g.fillRect(10, 50 + ctr*20, 20, 10); 
	    g.setColor(Color.black);
	    g.drawString(idx, 40, 60 + ctr*20); 
	    ctr++;
	}

	///////////////////////////////////////////////
	// detect if any point is chosen
	///////////////////////////////////////////////
	if (mouseIsDown) 
	{
	    if (chosen >= 0) 
	    {
		double X = (double)(px-w/2) / (w/4);
		double Y = (double)(py-h/2) / (h/4);
		double RR = X*X + Y*Y;
		if (RR < 1) 
		{
		    double Z = Math.sqrt(1 - RR);
		    P[chosen][0] = (cos * X + sin * Z) / (w/4);
		    P[chosen][1] = Y;
		    P[chosen][2] = (sin * X - cos * Z) / (w/4);
		}
	    }
	}
	else
	    chosen = -1;
	
	//System.out.println("2"); 

	//////////////////////////////////////////////////////////////
	// out of N[n] points pick the ones on the front hemisphere
	// and paint them bright
	//////////////////////////////////////////////////////////////
	for (i = 0 ; i < N[n] ; i++)
	    if (sin * P[i][0] - cos * P[i][2] > -w/20) 
	    {
		int x = w/2 + (int)(cos * P[i][0] + sin * P[i][2]);
		int y = h/2 + (int)(h/4 * P[i][1]);
		if (!mouseIsDown && chosen<0 && (px-x)*(px-x)+(py-y)*(py-y) <= 16)
		    chosen = i;

		g.setColor(color[colorId(i)]);
		g.fillOval(x-rad,y-rad,2*rad,2*rad);

		// if the dot is selected draw a nice little black circle around it
		if (chosen == i)
		{
		    g.setColor(Color.black);
		    rad += 2;
		    g.drawOval(x-rad,y-rad,2*rad,2*rad);
		    rad -= 2;

		    // announce the selected future
		    g.setColor(Color.black);
		    g.drawString("idx: " + indices[i] + "; future: " + futures[i] + "; rich/cheap: " + nf.format(richCheap[i]), w/4, 20);

		}

		/* g.setColor(i == chosen ? Color.red : color[colorId(i)]);
		  g.fillOval(x-rad,y-rad,2*rad,2*rad); */
	    }

	if (! mouseIsDown)
	    rt.setChosen(chosen);

	//System.out.println("3"); 

	////////////////////////////////////////////////
	// draw array of collections' sizes (4..1600)
	////////////////////////////////////////////////
	/*
	for (i = 0 ; i < N.length ; i++) 
	{
	    g.setColor(n==i ? Color.red : lightBrown);
	    g.drawString("" + N[i], 5, 20 + 14*i);
	}
	*/

	/////////////////////////////////////////////
	// draw array of collections' colors (1..9)
	/////////////////////////////////////////////
	/*
	for (i = 0 ; i < 9 ; i++)
	{
	    g.setColor(nColors==i+1 ? Color.black : lightGray);
	    g.drawString("" + (i+1), w-10, 20 + 14*i);
	}
	*/

	////////////////////////////////////////////////
	// draw switch for one color vs index-colored dots
	////////////////////////////////////////////////
	/*g.setColor(lightGray);
	g.drawString("" + 1, w-10, 20);
	g.drawString("All", w-15, 20+14*1); */

	//System.out.println("4"); 

	////////////////////////////////////////////////
	// spin button
	////////////////////////////////////////////////
	spinButton.render(g);
	for (i = 0 ; i < slider.length ; i++)
	    slider[i].render(g);

	if (spinButton.getValue() == 1)
	    rotate(.025);

	//g.setColor(new Color(160,160,255));
	//g.drawString("" + version, w-20, h/2);
	//System.out.println("5"); 
    }

    //int version = 10;

    Color buttonColor = new Color(255,230,230);
    Color lightBrown = new Color(200,150,150);
    Color lightGray  = new Color(200,200,200);
    int[] srgb = {250,240,240}; // pink, color of the sphere
    Color sphereColor = new Color(srgb[0],srgb[1],srgb[2]);
    Color edgeColor = new Color(srgb[0]/2,srgb[1]/2,srgb[2]/2);
    int[][] rgb = 
    {
	{255,0,0},
	{240,150,0},
	{140,200,0},
	{0,140,0},
	{0,160,255},
	{0,0,255},
	{200,0,120},
	{180,120,50},
	{0,98,187},
	{161,0,193},
	{230,73,0},
	{255,108,56},
	{255,200,95},
	{0,0,0},

	{255,0,0},
	{240,150,0},
	{140,200,0},
	{0,140,0},
	{0,160,255},
	{0,0,255},
	{200,0,120},
	{180,120,50},
	{0,98,187},
	{161,0,193},
	{230,73,0},
	{255,108,56},
	{255,200,95},
	{0,0,0}
    };

    int nColors = 1;

    // returns color id of point number i; color[colorId(i)] returns actual color
    /* int colorId(int i) { return (i % nColors) * color.length / nColors; } */
    int colorId(int i) 
    { 
	int r = ((Integer) indexOrder.get(indices[i])).intValue();
	//System.out.println("Want to return " + r); 
	return r;
    }

    Color[] color = newColors(), backColor;

    // create an array of front colors and back colors; return the front colors
    Color[] newColors()
    {
	color = new Color[rgb.length];     // array of 9 colors
	backColor = new Color[rgb.length]; // array of 9 colors
	for (int i = 0 ; i < color.length ; i++) 
	{
	    // front hemisphere colors, taken from the rgb array
	    color[i] = new Color(rgb[i][0],rgb[i][1],rgb[i][2]);
	    
	    // back hemisphere colors = front colors + (5/6) * srgb color components
	    backColor[i] = new Color(
				     (rgb[i][0] + 5*srgb[0])/6,
				     (rgb[i][1] + 5*srgb[1])/6,
				     (rgb[i][2] + 5*srgb[2])/6
				     );
	}
	return color;
    }

    double[][] P; // = initP();

    double[][] initP() 
    {
	nf.setMinimumFractionDigits(2); 
	nf.setMaximumFractionDigits(2); 


	//System.out.println("before!"); 
	N[n] = getFutures(); // save the number of futures
	//System.out.println("after!"); 
	java.util.Random R = new java.util.Random(0);
	P = new double[N[n]][3];
	for (int i=0; i<N[n] ; i++)
	    random(R, P[i]);
	return P;
    }

    void random(java.util.Random R, double[] p) 
    {
       double u, v, w;
       do {
          u = 2*R.nextDouble()-1;
          v = 2*R.nextDouble()-1;
          w = 2*R.nextDouble()-1;
       } while (u*u + v*v + w*w > 1);
       p[0] = u;
       p[1] = v;
       p[2] = w;
       RepelThread2.normalize(p);
    }

    // helper for quare of a float
    float sqr(float f)
    {
	return (f*f);
    }


    //////////////////////////////////////
    // get all the futures and order them 
    // by their rich/cheap trading ratio
    //////////////////////////////////////
    int getFutures()
    {
	int ctr = 100; 
	    
	try
	{
	    //getFuturesURL = getParameter("getFuturesURL"); 
	    System.out.println("getFuturesURL = " + getFuturesURL); 
	}
	catch (NullPointerException npe)
	{
	    System.out.println("Error reading applet param: " + npe); 
	}

	// if no futures URL supplied, generate them randomly: futures, indices, richCheap indicators
	if (getFuturesURL == null)
	{
	    System.out.println("Generating random futures!"); 
	    String[] alphabet = {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", 
			       "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"};

	    java.util.Random R = new java.util.Random(System.currentTimeMillis());
	    
	    richCheap = new double[ctr]; // create array of rich/cheap values for all futures
	    indices = new String[ctr]; // create array of indices of size sqrt(ctr) 
	    futures = new String[ctr]; // create array of future symbols for all futures
	    String[] uniqueIndices = new String[(int)Math.sqrt(ctr)];

	    // build sqrt(ctr) indices
	    for (int i = 0; i<(int)Math.sqrt(ctr); i++)
	    {
		// create random indices
		uniqueIndices[i] = alphabet[nextInt(R,26)] + alphabet[nextInt(R,26)] + 
		    alphabet[nextInt(R,26)] + "." + alphabet[nextInt(R,26)] + 
		    alphabet[nextInt(R,26)];
		//System.out.println("unique index " + i + " is " + uniqueIndices[i]); 
	    }

	    // populate randomly all arrays
	    for (int i=0; i<ctr; i++)
	    {
		String idx = uniqueIndices[nextInt(R,(int)Math.sqrt(ctr))];
		indices[i] = idx;
		//System.out.print(i + ": " + indices[i]); 
		futures[i] = idx.substring(0, idx.indexOf(".")) + alphabet[nextInt(R,26)] + 
		    nextInt(R,2) + "." + alphabet[nextInt(R,26)] + alphabet[nextInt(R,26)] + 
		    alphabet[nextInt(R,26)];
		
		//System.out.print(" " + futures[i]); 

		richCheap[i] = R.nextFloat()*240 - 120; // rnd float bet (-120, 120)
		//System.out.println(" " + richCheap[i]); 
	    }

	    //System.out.println("Before sorting rich/cheap of length " + richCheap.length); 

	    // sort richCheap array since there is no sorting in java 1.1
	    double temp;
	    for(int i=0; i<richCheap.length; i++)
		for (int j=i+1; j<richCheap.length; j++)
		{
		    //System.out.println("i=" + i + "; j=" + j); 
		    if (richCheap[i] < richCheap[j])
		    {
			temp = richCheap[i];
			richCheap[i] = richCheap[j];
			richCheap[j] = temp;
		    }
		}
	}
	else // else read them from the URL
	{
	    System.out.println("Using real futures from " + getFuturesURL); 
	    int old_pos = -1, pos = -1;
	    try
	    {
		String str = "", indx = ""; 
		
		BufferedReader br = new BufferedReader(new InputStreamReader(new URL("http://hqsas60:1112/cgi-bin/ged/fvcalc/getFutures.pl").openStream() ) );
		
		// read from the URL; first line is the total number of futures in db
		if ((str = br.readLine()) != null)
		{
		    ctr = Integer.valueOf(str).intValue();
		    
		    ///////////////////////////////////////////////////////////
		    // error from the db!
		    ///////////////////////////////////////////////////////////
		    if (ctr == -1)
		    {
			richCheap = new double[0]; // create array of rich/cheap values for 0 futures
			indices = new String[0]; // create array of index symbols for all futures
			futures = new String[0]; // create array of future symbols for all futures
			return ctr; 
		    }

		    richCheap = new double[ctr]; // create array of rich/cheap values for all futures
		    indices = new String[ctr]; // create array of index symbols for all futures
		    futures = new String[ctr]; // create array of future symbols for all futures
		}

		ctr = 0; // actually use ctr for counting
		// read records with futures!
		while ( (str = br.readLine()) != null)
		{
		    //System.out.println(str); 
		    
		    // get index
		    pos = str.indexOf(" ");
		    //System.out.println( "pos=" + pos + "; index: " + str.substring(0, pos) ); 
		    indx = str.substring(0, pos); 
		    old_pos = pos+1;
		    
		    // get future mdSymbol
		    pos = str.indexOf(" ", pos+1);
		    //System.out.println( "pos=" + pos + "; future: " + str.substring(old_pos, pos) ); 
		    futures[ctr] = str.substring(old_pos, pos); 
		    old_pos = pos+1;
		    
		    // get future mdSymbol
		    //System.out.println("pos=" + pos + "; rich_cheap: " + str.substring(old_pos, str.length()) + "\n"); 
		    richCheap[ctr] = Double.valueOf(str.substring(old_pos, str.length())).doubleValue();
		    //System.out.print("richCheap[" + ctr + "] = " + richCheap[ctr]); 
		    indices[ctr] = indx;
		    //System.out.println("; index of future " + ctr + " is [" + indices[ctr] + "]"); 
		    //System.out.print("\"" + indices[ctr] + "\", "); 

		    ctr++; 
		}
	    }
	    catch(MalformedURLException mue)
	    {
		mue.printStackTrace();
	    }
	    catch(IOException ioe)
	    {
		ioe.printStackTrace();
	    }
	}
	    
	setNumberOfIndices(); // look thru array of index names and count them
	return ctr;
    }

    // simulates nextInt(int) that's not present in JDK 1.1
    int nextInt(Random R, int max)
    {
	return (int) (R.nextFloat()*max);
    }


    // get rich_cheap val for a goven future
    public static double getRichCheap(int numOfFuture)
    {
	System.out.println("Returning rich_cheap["+numOfFuture+"] = " + richCheap[numOfFuture]); 
	return richCheap[numOfFuture]; 
    }

    private void setNumberOfIndices()
    {
	Hashtable h = new Hashtable();
	for (int i=0; i<indices.length; i++)
	{
	    h.put(indices[i], new Integer(1));
	}
	numberOfIndices = h.size(); // set glob var to the number of uniq indices
	//System.out.println("Setting number of indices to " + numberOfIndices); 

	// set indexOrder
	int ctr = 1;
	for (Enumeration e = h.keys(); e.hasMoreElements() ; )
	{
	    indexOrder.put(e.nextElement(), new Integer(ctr++));
	}
	//System.out.println("indexOrder: " + indexOrder); 
    } 

} 

class RepelThread2 extends Thread
{
    final static int TREND1  = 0;
    final static int TREND2  = 1;
    final static int TREND3  = 2;
    final static int ENERGY  = 3;
    final static int GRAVITY = 4;
    
    double[][] P = null, currentP = null;
    int chosen = -1, 
	M = 0;               // number of colors
    double[] var;            // array of slider values
    
    double[] richCheap;      // array of richCheap values

    RepelThread2(int nVars) 
    {
	var = new double[nVars];
    }
    
    // set up array of points (each - 3 coords), and number n of colors
    public void set(double[][] newP, int n) 
    {
	currentP = newP;
	this.M = n;
    }

    // 
    public void setVar(int v, double value) { var[v] = value; }

    // set the rich_xcheap value array
    void setRichCheap(double[] rc)
    {
	richCheap = new double[rc.length]; 
	richCheap = rc;
    }

    public void setChosen(int i) 
    {
	chosen = i;
    }

    double[] v = new double[3];
    double[] p = new double[3];

    public void run() 
    {
	int j; // loop var
	try 
	{
	    while (true) 
	    {
		P = currentP;
		int N = P.length;              // number of points in the game
		double t = .02 / Math.sqrt(N); // coeff 
		double T;                      // tension

		// a goes through all points
		for (int a = 0 ; a < N ; a++)
		    if (a != chosen)  // a is not chosen by user to be dragged away
		    {
			for (j = 0 ; j < 3 ; j++)
			    p[j] = P[a][j]; // p - vector of coords
			// vector p now contains coords of current point number a
		      
			int aColorId = colorId(a);

			// b goes thru all points...
			for (int b = 0 ; b < N ; b++) 
			{
			    if (b != a) // ...except point number a !
			    {
				// vector v is a vector between points a and b
				for (j = 0 ; j < 3 ; j++)
				    v[j] = P[b][j] - P[a][j];
			      
				// sqr of length of vector ab (i.e, sqr of dist bet a & b)
				double rr = v[0]*v[0] + v[1]*v[1] + v[2]*v[2];
			      
				// tension T = 0.02 * energy / (sqrt(N) *
				// d*d ), where N is a number of points,
				// so sqrt(N) provides a scaling factor
				// over the collective of points; d is a
				// distance between points a and b;
				// so tension T is reverse-proportional
				// to the square of the distance, just
				// like the gravity; energy - is just a
				// coefficient by which the tension T is
				// additionally multiplied.
				T = t * var[ENERGY] / rr;
				
				// M is a number of current colors; the
				// portion of code below figures out the
				// tension depending on the color of
				// affecting points
				int C = ( (M*1000 + b-a) % M ) & 7;
				//System.out.println("M=" + M + "; b=" + b + 
				//	                  "; a=" + a + "; C=" + C);
			      
				/*
				  if (C==1 || C==2 || C==4)        // ONE BIT SET
				  {
				  //System.out.print("1: Mult T by " + (1+2*var[TREND1])); 
				  T *= 1 + 2 * var[TREND1];
				  }
				  else if (C==3 || C==5 || C==6)   // TWO BITS SET
				  {
				  //System.out.print("2: Mult T by " + (1+2*var[TREND2]));
				  T *= 1 + 2 * var[TREND2];
				  }
				  else if (C==7)                   // THREE BITS SET
				  {
				  //System.out.print("3: Mult T by " + (1+2*var[TREND3])); 
				  T *= 1 + 2 * var[TREND3];
				  }
				*/
				
				// determine the repelling factor for same color
				if (aColorId == colorId(b))
				{
				    T *= 1;
				}
				else
				{
				    T *= 2;
				}
				
				//System.out.println("colorId(" + a + ")=" + aColorId + 
				//"; bColorId(" + b + ")=" + colorId(b) + 
				//"; T=" + T); 

				// move point a; since there is a
				// _repelling_ effect, we use a minus
				// sign, not plus.
				
				//for (j = 0 ; j < 3 ; j++)
				//p[j] -= T * v[j];

				// instead of shifting the point away
				// from its peer in 3 dimensions, we
				// only shift it away in 2 dimensions,
				// excluding Z-axis.
				p[0] -= T * v[0];
				p[1] -= T * v[1]; // weaker tension
				p[2] -= T * v[2];
				
				// apply gravity; dividing by N just
				// provides better looking graphics,
				// without it gravity is too strong, with
				// div by N*N it's too weak...
				// 
				// p[0] is horiz, incr to the right
				// p[1] is vert, incr downwards
				// p[2] is depth, incr towards the screen
				if (var[GRAVITY] != 0)
				    p[1] += var[GRAVITY] / (N*N);
				
				// normalize(p); // done below!
			    }
			} // for b goes thru all points...
			
			// apply rich_cheap value (anti-gravity!) 
			p[1] -= richCheap[a] / Math.pow(N, 1.4); 

		       // point which has coords saved in
		       // vector p might now (and will!) lay
		       // beyond the 1-sphere, so normalize()
		       // brings it back onto the surface...
			normalize(p); 

			for (j = 0 ; j < 3 ; j++)
			    P[a][j] = p[j];
		    } // end if (a != chosen)...
		
		//System.out.println("rich_cheap[5] = " + richCheap[5]); 
		sleep(30);
	    } // end while(true)...
	    
	} // end try...
	catch(InterruptedException e){};
    }

    // 
    double near(double t, double r) 
    {
	t = Math.abs(t);
	if (t > r)
	    return 0;
	return 1 - t / r;
    }

// static normalization method
    static void normalize(double v[]) 
    {
	double s = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
	v[0] = v[0] / s;
	v[1] = v[1] / s;
	v[2] = v[2] / s;
    }

    /////////////////////////////////////////////////////////////
    // methods added by Kirill Karpelson

    // returns color id of point number i; color[colorId(i)] returns actual color
    int colorId(int i) 
    {
	//System.out.println("rt: colorId(" + i + ") = " + ((i % 8) * 9 / 8)); 
	return ((Integer) Repel2.indexOrder.get(Repel2.indices[i])).intValue() ;
	//return (i % M) * 9 / M; 
    }

    // debug method
    String toTheString()
    {    
	StringBuffer sb = new StringBuffer("Points P:\n"); 
	if (P != null)
	{
	    for (int i=0; i<P.length; i++)
	    {
		sb.append("\t" + i + ": (" + P[i][0] + "," + P[i][1] + "," + P[i][2] + ")\n"); 
	    }
	}

	sb.append("\nCurrentP:\n"); 

	if (currentP != null)
	{
	    for (int i=0; i<currentP.length; i++)
	    {
		sb.append("\t" + i + ": (" + currentP[i][0] + "," + currentP[i][1] + "," + currentP[i][2] + ")\n"); 
	    }
	}

	sb.append("\nChosen: " + chosen + "\n"); 
	sb.append("\nColors M: " + M + "\n"); 
	sb.append("\nVar:\n"); 

	if (var != null)
	{
	    for (int i=0; i<var.length; i++)
	    {
		sb.append("\t" + var[i] + "\n"); 
	    }
	}

	if (v != null ) { sb.append("v: " + ": (" + v[0] + "," + v[1] + "," + v[2] + ")\n"); }
	if (p != null) { sb.append("p: " + ": (" + p[0] + "," + p[1] + "," + p[2] + ")\n"); }

	return sb.toString();
    }

}

