import java.awt.*;
import java.net.*;
import java.io.*;
import java.util.*;
import java.text.*;
//import netscape.javascript.JSObject;

public class Repel32 extends BufferedApplet
{
/*
    // Color Scheme 1 (Original)
    private final Color bgColor = Color.white;
    private final Color areoColor = Color.black;
    private final Color highlightColor = Color.black;
    private final Color fontFgColor = Color.black;
    private final Color globeBorderColor = new Color(123,121,123);
    private final Color parallelsColor = new Color(206,203,206);
    //private final Color globeBgColor = new Color(255,243,247);
    private final Color globeBgColor = new Color(199,221,241);
    private final Color buttonBgColor = new Color(255,231,231);
    private final Color buttonFgColor = Color.white;
    private final Color sliderBgColor = new Color(255,231,231);
    private final Color sliderBarColor = new Color(181,162,165);
    private final Color [] legendIndexBoxColor = {
    						  new Color(165,0,198),
						  new Color(255,0,0),
						  new Color(0,142,0),
						  new Color(0,0,255),
						  new Color(247,150,0),
						  new Color(0,162,255),
						  new Color(140,203,0),
						  new Color(206,0,123),
						  new Color(0,97,189),
						  new Color(181,121,49),
    						};
    private final int denom = 6; // denominator that determines the color of dots on the back hemisphere
*/

    // Color Scheme 1a (Light Blue and Green)
    private final Color bgColor = new Color(233,255,255);
    private final Color areoColor = Color.black;
    private final Color highlightColor = Color.black;
    private final Color fontFgColor = Color.black;
    private final Color globeBorderColor = new Color(123,121,123);
    private final Color parallelsColor = new Color(206,203,206);
    private final Color globeBgColor = new Color(199,221,241);
    private final Color buttonBgColor = new Color(199,221,241);
    private final Color buttonFgColor = Color.black;
    private final Color sliderBgColor = new Color(199,221,241);
    private final Color sliderBarColor = new Color(181,162,165);
    private final Color [] legendIndexBoxColor = {
    						  new Color(165,0,198),
						  new Color(255,0,0),
						  new Color(0,142,0),
						  new Color(0,0,255),
						  new Color(247,150,0),
						  new Color(0,162,255),
						  new Color(140,203,0),
						  new Color(206,0,123),
						  new Color(0,97,189),
						  new Color(181,121,49),
    						};
    private final int denom = 6; // denominator that determines the color of dots on the back hemisphere

	/*
    // Color Scheme 2 (Inverted)
    private final Color bgColor = Color.black;
    private final Color fontFgColor = Color.white;
    private final Color areoColor = Color.white;
    private final Color highlightColor = Color.white;
    private final Color globeBorderColor = new Color(132,134,132);
    private final Color parallelsColor = new Color(49,52,49);
    private final Color globeBgColor = new Color(0,12,8);
    private final Color buttonBgColor = new Color(0,24,24);
    private final Color buttonFgColor = Color.black;
    private final Color sliderBgColor = new Color(0,24,24);
    private final Color sliderBarColor = new Color(74,93,90);
    private final Color [] legendIndexBoxColor = {
    						    new Color(90,255,57),
    						    new Color(0,255,255),
    						    new Color(255,113,255),
    						    new Color(255,255,0),
    						    new Color(8,105,255),
    						    new Color(255,93,0),
    						    new Color(115,52,255),
    						    new Color(49,255,132),
    						    new Color(255,158,66),
    						    new Color(74,134,206)
    						};
    private final int denom = 2; // denominator that determines the color of dots on the back hemisphere
    */

    RepelThread2 rt = null;
    //JSObject win; // to comunicate with javascript!

    java.util.Random R = new java.util.Random(0);

    String[] spinButtonLabels = {"SPIN","STOP"};
    Button spinButton;

    private static final int DEBUG = 1;  // debug flag
    boolean unpublished = true; // flag allowing applet to publish numbers to javascript

    String[] sliderLabels =
       {
       	"INT. RATE, %",     // interest rate 1..100 %, normal around 5 %
       	"DIVYLD",
       	"DAYS2EXP",
       	"ENERGY",
       	"TEMPERATURE"
       };
    Slider[] slider = new Slider[sliderLabels.length];

    // array of values; used because the very 1st time richCheap
    // values have to be calculated, nothing is rendered yet,
    // hence values of real sliders are not available yet. So
    // we initialize this array of doubles and use int rate
    // and other slider variables to recalc the rich cheap
    // values indirectly, through this array.
    double [] sliderValues = new double[sliderLabels.length];

    // array of flags, telling whether the slider was changed or not
    boolean [] sliderChanged = new boolean[sliderLabels.length];

    // create a format to print only the decimal parts of prices
    DecimalFormat nform;
    NumberFormat nf = NumberFormat.getInstance(); // for richCheap printing

    public void stop() {
       super.stop();
       if (rt != null)
          rt.stop();
    }

    public static double[] richCheap; // array stores rich/cheap values for all futures
    public static double[] futurePrices; // array stores market prices for all [ctr] futures
    public static double[] indexPrices;  // array stores market prices for all [sqrt(ctr)] indices
    public static String[] indices;   // array stores index symbols for all [ctr] futures
    public static String[] futures;   // array stores all [ctr] futures symbols
    public static Hashtable indexOrder = new Hashtable(); // keys: index symbols, values: index ordinals in indexPrices array
    public static String[] uniqueIndices; // list of unique index symbols

    int maxRichCheap, minRichCheap;
    String getFuturesURL; // = getParameter("getFuturesURL");

    int numberOfIndices = 1;          // default number of indices

    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, highlighted = -1;
    boolean mouseIsDown = false;

    public void init()
    {
    	// init siderChanged flag array
    	for (int i=0; i< sliderChanged.length; i++)
    	{
    	    sliderChanged[i] = false;
    	}

    	// set up format to print prices as integers
     	nform = (DecimalFormat)NumberFormat.getInstance();
    	nform.setMaximumFractionDigits(0);
    	
    	// set up format for printing richCheap
       	nf.setMinimumFractionDigits(2);
	nf.setMaximumFractionDigits(2);

	try
	{
	    getFuturesURL = getParameter("getFuturesURL");
	    debug(2, DEBUG, "Read params: " + getFuturesURL);
	    //win = JSObject.getWindow(this); // get window context
	}
	catch(Exception e)
	{
	    System.out.println("Error reading params: " + e);
	    System.out.println(" ...or error getting window context: " + e);
	}
    	
    	// At this point we have somehow to create some initial values for the sliders
    	// and use them in RC calculation in the function getRichCheapValue() called
    	// from getFutures(), called from initP().
    	sliderValues[rt.INTRATE]  = 5.0 / 100; // range: slider values in 0..1 map into 0..100 %
    	sliderValues[rt.DIVYLD]   = 1.2 / 100; // range: slider values in 0..1 map into 0..100 %
    	sliderValues[rt.DAYS2EXP] = 50 / 1000; // range: slider values in 0..1 map into 0..1000 days
    	sliderValues[rt.ENERGY] = 1;
    	sliderValues[rt.TEMPERATURE] = 0; // no noise!
    	
	// this calls getFutures()!!!!!!!!!!!
	P = initP(); // this was previously initialized below ( search for "initP" )

	debug(2, DEBUG, "indexOrder=" + indexOrder);
    }

    // publish value to javascript
    public void publishValue(String jsf, String val)
    {
	String [] paramList = {val}; // array of params to js function, just 1 for now
	//win.call(jsf, paramList);
	debug(2, DEBUG, "Just tried to publish " + paramList[0] + " to " + jsf);
    }

    public boolean mouseDown(Event e, int x, int y) {
       	mouseIsDown = true;

       	////////////////////////////////////////////
       	// process mouse click on the spin button //
       	////////////////////////////////////////////
       	if (spinButton.down(x,y))
	    return true;

	////////////////////////////////////////
	// process mouse click on the sliders //
	////////////////////////////////////////
       	for (int i = 0 ; i < slider.length ; i++)
            if (slider[i].down(x,y))
		return true;
	
        if (x >= 50 && x < w-20)
	    mx = x;

       // highlight a dot
       for (int i = 0; i < N[n]; i++)
	   if (sin * P[i][0] - cos * P[i][2] > -w/20)  // front hemisphere
	   {
	       // dot coords: dx, dy
	       int dx = w/2 + (int)(cos * P[i][0] + sin * P[i][2]);
	       int dy = h/2 + (int)(h/4 * P[i][1]);

	       // if we clicked nearby...
	       if ((x-dx)*(x-dx)+(y-dy)*(y-dy) <= 16)
	       {
		   debug(2, DEBUG, "Clicked nearby...");
		   if (highlighted == i) // current already highlighted...
		   {
		       highlighted = -1; // un-highlight!
		       debug(2, DEBUG, i + " already highlighted, setting to -1");
		       unpublished = false;
		   }
		   else
		   {
		       highlighted = i;  // highlight
		       unpublished = true;
		       debug(2, DEBUG, "Highlighting " + i);
		   }
	       }
	   }
       debug(2, DEBUG, "Hghlghtd number " + highlighted);

       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;

       ////////////////////////////////////////
       // mouse is dragged on a spin button
       ////////////////////////////////////////
       if (spinButton.drag(x,y))
	  return true;

       ////////////////////////////////////////
       // mouse is dragged on a slider
       ////////////////////////////////////////
       for (int i = 0 ; i < slider.length ; i++)
	  if (slider[i].drag(x,y))
	  {
	      rt.setVar(i, slider[i].getValue());
	      slider[i].label = sliderLabels[i] + ": " +
			showForSlider(i, ( (rt != null) ? rt.getVar(i) : sliderValues[i] ) );
	      sliderChanged[i] = true; // this slider was changed! This causes recalc of richCheap in render()
	      return true;
          }

       ///////////////////////////////////////////////////////////////////
       // mouse is dragged on the white field => causes globe to rotate //
       ///////////////////////////////////////////////////////////////////
       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;
	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;
	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)
    {
	debug(2, DEBUG, "-1.4, N[n]=" + N[n]);
	int i, j, k;
	if (w == 0)
	{
	    w = bounds().width;
	    h = bounds().height;
	    rotate(0);
	
	    debug(2, DEBUG, "-1.4");
	
	    ///////////////////////////////////////
	    // process spin button
	    ///////////////////////////////////////	
	    spinButton = new Button(3, h-20, 34, 15);
	    spinButton.labels = spinButtonLabels;
	    spinButton.bgColor = buttonBgColor;
	    spinButton.fgColor = buttonFgColor;

	    ///////////////////////////////////////
	    // process sliders
	    ///////////////////////////////////////
	    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] + ": " + showForSlider(i, sliderValues[i] );
		slider[i].bgColor = sliderBgColor;
		slider[i].fgColor = fontFgColor;
	    }
	
	    debug(2, DEBUG, "-1.3");
	
	    debug(2, DEBUG, //"rt.getVar(0)=" + rt.getVar(rt.INTRATE) +
	    	"; sliderValues[0]=" + sliderValues[0] +
	    	"; slider[0].getValue()=" + slider[0].getValue());
	
	    slider[RepelThread2.INTRATE].setValue(sliderValues[RepelThread2.INTRATE]);
	    slider[RepelThread2.DIVYLD].setValue(sliderValues[RepelThread2.DIVYLD]);
	    slider[RepelThread2.DAYS2EXP].setValue(sliderValues[RepelThread2.DAYS2EXP]);
	    slider[RepelThread2.ENERGY].setValue(sliderValues[RepelThread2.ENERGY]);
	    slider[RepelThread2.TEMPERATURE].setValue(sliderValues[RepelThread2.TEMPERATURE]);

	    debug(2, DEBUG, "-1.2");
	
	    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();
	
	    debug(2, DEBUG, "-1.1");
	
	    rt.setRichCheap(richCheap);
	}

	debug(2, DEBUG, "-1");

	g.setColor(bgColor);
	g.fillRect(0,0,w,h);
	
	int rad = 4; //(int)(4 + 4 * slider[5].getValue());    // radius

	// paint the circle with bleak pink
	g.setColor(globeBgColor);
	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(fontFgColor);
	g.drawString("0.00", w/4 - 30, w/2 + 5); // zero
	g.drawString(""+(int)maxOfArray(richCheap), w/2-15, w/4 - 7); // min
	g.drawString(""+(int)minOfArray(richCheap), w/2-12, 3*w/4 + 15); // max

	// draw parallels
	g.setColor(parallelsColor);
	for (j=0; j<7; j++)
	{
	    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));
	}
	debug(2, DEBUG, "0");
	
	//////////////////////////////////////////////////////////////
	// 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(globeBorderColor);
	g.drawOval(w/4+rad,h/4+rad,w/2-2*rad,h/2-2*rad);
	
	debug(2, DEBUG, "1");

	///////////////////////////////////////////////
	// draw legend for indices
	///////////////////////////////////////////////
	int ctr = 0;
	g.setColor(fontFgColor);
	g.drawString("Legend of Indices", 10, 40);
	for (Enumeration e = indexOrder.keys(); e.hasMoreElements() ; )
	{
	    String idx = (String) e.nextElement();
	    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(fontFgColor);
	    debug(2, DEBUG, "Testing formatted indexPrice: " +
	    	nform.format( indexPrices[(((Integer) indexOrder.get(idx)).intValue())] ) +
	    	" for index " + idx + "; index order #: " + ((Integer) indexOrder.get(idx)).intValue() );
	    g.drawString(idx + ", " +
	    			nform.format( indexPrices[(((Integer) indexOrder.get(idx)).intValue())] ),
	    		 40,
	    		 60 + ctr*20);
	    ctr++;
	}

	debug(2, DEBUG, "2");

	///////////////////////////////////////////////
	// 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;

	//////////////////////////////////////////////////////////////
	// 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(areoColor);
		    rad += 2;
		    g.drawOval(x-rad,y-rad,2*rad,2*rad);
		    rad -= 2;

		    // announce the chosen future
		    g.setColor(fontFgColor);
		    g.drawString("idx: " + indices[i] + "; future: " + futures[i] +
				 "; rich/cheap: " + nf.format(richCheap[i]) +
				 "; ip=" + nform.format(indexPrices[((Integer) indexOrder.get(indices[i])).intValue()]) +
				 "; fp=" + nform.format(futurePrices[i]), w/4, 20);

		}

		// highlight the highlighted dot
		if (highlighted == i)
		{
		    g.setColor(highlightColor);
		    g.fillOval(x-rad,y-rad,2*rad,2*rad);

		    // announce the selected future
		    g.setColor(fontFgColor);
		    g.drawString("Highlighted: idx: " + indices[i] + "; future: " + futures[i] + "; rich/cheap: " + nf.format(richCheap[i]), w/4, 40);
		
		    // publish values to javascript!
		    if (unpublished)
		    {
		    	publishValue("setS", "" + indexPrices[((Integer) indexOrder.get(indices[i])).intValue()]);
		    	publishValue("setr", "" + rt.getVar(rt.INTRATE));
		    	publishValue("setq", "" + rt.getVar(rt.DIVYLD));
		    	publishValue("setT", "" + rt.getVar(rt.DAYS2EXP) * 1000);
		    	publishValue("func1", ""); // calc
		    	unpublished = false;
		    }
		}
	    }

	if (! mouseIsDown)
	    rt.setChosen(chosen);

	debug(2, DEBUG, "3");

	////////////////////////////////////////////////
	// spin button
	////////////////////////////////////////////////
	spinButton.render(g);
	
	for (i = 0 ; i < slider.length ; i++)
	    slider[i].render(g);

	// rotate
	if (spinButton.getValue() == 1)
	    rotate(.025);

	debug(2, DEBUG, "4");

	// if any of the global variables (sliders) have been
	// changed then recalc the richCheap array
	if (sliderChanged[rt.INTRATE] || sliderChanged[rt.DIVYLD] || sliderChanged[rt.DAYS2EXP])
	{
	    for (i=0; i<N[n]; i++)
	    {
	    	richCheap[i] = getRichCheapValue(i); // compute it!
	    }
	    // reset flags!
	    sliderChanged[rt.INTRATE] = false;
	    sliderChanged[rt.DIVYLD] = false;
	    sliderChanged[rt.DAYS2EXP] = false;
	}
    }

    int nColors = 1;

    // returns color id of point number i; color[colorId(i)] returns actual color
    int colorId(int i)
    {
	int r = ((Integer) indexOrder.get(indices[i])).intValue();
	return r;
    }

    Color[] color = newColors(), backColor;

    // create an array of front colors and back colors; return the front colors
    Color[] newColors()
    {	
	color = new Color[legendIndexBoxColor.length];
	backColor = new Color[legendIndexBoxColor.length];
		
	for (int i = 0 ; i < color.length ; i++)
	{
	    // front hemisphere colors
	    color[i] = legendIndexBoxColor[i];
	
	    // back hemisphere colors = (front colors + 5* srgb color components) / 6
	    backColor[i] = new Color(
	    			     (legendIndexBoxColor[i].getRed() + 5*globeBgColor.getRed())/denom,
	    			     (legendIndexBoxColor[i].getGreen() + 5*globeBgColor.getGreen())/denom,
	    			     (legendIndexBoxColor[i].getBlue() + 5*globeBgColor.getBlue())/denom
				    );
	}
	return color;
    }

    double[][] P; // = initP();

    double[][] initP()
    {
	N[n] = getFutures(); // save the number of futures
	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;
	int idxlen = (int)Math.sqrt(ctr); // number of indices
	
	try
	{
	    //getFuturesURL = getParameter("getFuturesURL");
	    debug(2, DEBUG, "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)
	{
	    debug(2, DEBUG, "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]; // array of rich/cheap values for all futures
	    futurePrices = new double[ctr]; // array of market prices for all futures
	    // array of market prices for all indices
	    indexPrices = new double[idxlen];
	    indices = new String[ctr]; // create array of indices of size ctr
	    futures = new String[ctr]; // create array of future symbols for all futures
	    uniqueIndices = new String[idxlen];
	
	    // build sqrt(ctr) indices
	    for (int i = 0; i<idxlen; 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)];
		indexPrices[i] = R.nextFloat()*15000; // indices levels in 0..15000
		debug(2, DEBUG, "Index price " + i + " = " + indexPrices[i]);
	    }

	    // populate randomly all arrays
	    for (int i=0; i<ctr; i++)
	    {
	    	int idxnum = nextInt(R,idxlen);  // an ordinal number of an index in the indices array

	    	// line below makes every index have same # of futures,
	    	// organizes dots into similar colonies, not very interesting...
	    	//int idxnum = (int) (i / idxlen);   // returns o if i in (0..9), 1 if i in (10.19), etc
	    	
	    	ndebug(2, DEBUG, "i=" + i + "; idxnum=" + idxnum);
	    	
		String idx = uniqueIndices[idxnum];
		ndebug(2, DEBUG, "; uniq Idx=" + idx);
		
		indices[i] = idx;
		ndebug(2, DEBUG, "; indices[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)];
		ndebug(2, DEBUG, "; futures[i]=" + futures[i]);
		
		// make future price be within 5 % of its index price
		futurePrices[i] = indexPrices[idxnum] * (1.0 + R.nextFloat()/10.0-0.05);
		ndebug(2, DEBUG, "; indexPrices[idxnum]=" + indexPrices[idxnum] + "; futurePrices[i]=" + futurePrices[i]);
	    }
		
	    // debug block: print all arrays
	    for (int i=0; i<idxlen; i++) { debug(2, DEBUG, "uniq Index[" + i + "]=" + uniqueIndices[i]); }
	    for (int i=0; i<idxlen; i++) { debug(2, DEBUG, "index Price[" + i + "]=" + indexPrices[i]); }	
	    for (int i=0; i<ctr; i++) { debug(2, DEBUG, "fut price[" + i + "]=" + futurePrices[i]); }
	    for (int i=0; i<ctr; i++) { debug(2, DEBUG, "indices[" + i + "]=" + indices[i]); }
	    for (int i=0; i<ctr; i++) { debug(2, DEBUG, "futures[" + i + "]=" + futures[i]); }
	
	}
	else // else read them from the URL
	{
	    debug(2, DEBUG, "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)
		{
		    // get index
		    pos = str.indexOf(" ");
		    indx = str.substring(0, pos);
		    old_pos = pos+1;
		
		    // get future mdSymbol
		    pos = str.indexOf(" ", pos+1);
		    futures[ctr] = str.substring(old_pos, pos);
		    old_pos = pos+1;
		
		    // get future mdSymbol
		    richCheap[ctr] = Double.valueOf(str.substring(old_pos, str.length())).doubleValue();
		    indices[ctr] = indx;
		    ctr++;
		}
	    }
	    catch(MalformedURLException mue)
	    {
		mue.printStackTrace();
	    }
	    catch(IOException ioe)
	    {
		ioe.printStackTrace();
	    }
	}
	
	setNumberOfIndices(); // look thru array of index names and count them
	// also set the indexOrder hash!

	// debug indexOrder hash	
        debug(2, DEBUG, "Size of indexOrder: " + indexOrder.size());
	for (Enumeration e = indexOrder.keys(); e.hasMoreElements() ; )
	{
	    String idx = (String) e.nextElement();
	    debug(2, DEBUG, "indexOrder(" + idx + ")=" + ((Integer) indexOrder.get(idx)).intValue());
	}

	for (int i=0; i<ctr; i++)
	{
	    // everything else has to be computed or assigned before this
	    // one, because richCheapo is actually calculated based on other
	    // numbers, not just assigned as a random value...		
	    richCheap[i] = getRichCheapValue(i); // compute it!
	}	
	return ctr;
    }

    // Based on all other info (prices etc) calc rich cheap ratio
    // RC = (FV - FP) / FP, where RC - ric cheap, FV - fair value, FP - traded future price
    // FV can be computed using the formula:
    // FV = S * exp( (r-q)*T/365 ), where S - index price, r - interest rate, q - div yld,
    //     T - time in days till future expiration
    // Accepts i as a current index in the array of futures, futurePrices[i] yields FP,
    // indexPrices[indexOrder.get(i)] yields S, rt.getVar(rt.INTRATE) yields the interest rate r,
    // rt.getVar(rt.DIVYLD) yields the dividend yield q, rt.getVar(rt.DAYS2EXP) yields days to
    // expiration T.
    // Returns the RC value computed according to the formulas above.
    private double getRichCheapValue(int i)
    {
    	debug(2, DEBUG, "getRichCheapValue: i=" + i +
    		"; indices[i]=" + indices[i] +
    		"; indexOrder.get()=" + ((Integer) indexOrder.get(indices[i])).intValue() +
    		"");
    	double S = indexPrices[((Integer) indexOrder.get(indices[i])).intValue()];
	double r, q, T;
	
    	debug(2, DEBUG, "rt = " + rt);
    	
    	// if RepelThread is not yet initialized then use the array of doubles
    	if (rt == null)
    	{
    	    r = sliderValues[rt.INTRATE];
    	    q = sliderValues[rt.DIVYLD];
    	    T = sliderValues[rt.DAYS2EXP] * 1000; // adjust for the days scale
    	}
    	else // use the actual RepelThread
    	{
    	    r = rt.getVar(rt.INTRATE);
    	    q = rt.getVar(rt.DIVYLD);
    	    T = rt.getVar(rt.DAYS2EXP) * 1000; // adjust for the days scale
    	}
    	
    	double FV = S * Math.exp( (r-q)*T/365 );
    	double FP = futurePrices[i];
    	return 2000 * (FV - FP) / FP;
    }

    // find max of the array of doubles
    private double maxOfArray(double [] items)
    {
    	double max = items[0];
    	for (int i=1; i<items.length; i++)
    	{
    	    if (items[i] > max) { max = items[i]; }
    	}
    	return max;
    }

    // find min of the array of doubles
    private double minOfArray(double [] items)
    {
    	double min = items[0];
    	for (int i=1; i<items.length; i++)
    	{
    	    if (items[i] < min) { min = items[i]; }
    	}
    	return min;
    }

    // helper method
    private String showForSlider(int sliderIndex, double sliderValue)
    {
    	if (sliderIndex == 0 || sliderIndex == 1) // int rate or div yld
    	{
    	    return (nf.format(sliderValue*100) + " %");
    	}
    	else if (sliderIndex == 2) // days to exp
    	{
    	    return nform.format((int)(sliderValue*1000));
    	}
    	else if (sliderIndex == 4) // days to exp
    	{
    	    return nf.format(sliderValue);
    	}
    	else
    	{
    	    return ("" + sliderValue);
    	}
    }

    // 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)
    {
	debug(2, DEBUG, "Returning rich_cheap["+numOfFuture+"] = " + richCheap[numOfFuture]);
	return richCheap[numOfFuture];
    }

    private void setNumberOfIndices()
    {
	numberOfIndices = uniqueIndices.length; // set glob var to the number of uniq indices
    	for (int i=0; i<numberOfIndices; i++)
    	{
    	    indexOrder.put(uniqueIndices[i], new Integer(i) );
    	}
    }

    // debug: if debug level of this dbg msg is equal or greater than debug
    // level of the program, print it with the new line char at the end
    private static void debug(int local_debug, int global_debug, String msg)
    {
    	ndebug(local_debug, global_debug, msg+"\n");
    }

    // same as above, without new line char at the end
    private static void ndebug(int local_debug, int global_debug, String msg)
    {
    	if (local_debug <= global_debug) { System.out.print(msg); }
    }

}

class RepelThread2 extends Thread
{
    public final static int INTRATE  = 0;
    public final static int DIVYLD  = 1;
    public final static int DAYS2EXP  = 2;
    public final static int ENERGY  = 3;
    public final static int TEMPERATURE = 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
    java.util.Random R = new java.util.Random(System.currentTimeMillis()); // random obj

    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;
    }

    // set slider array variable according to slider position!
    public void setVar(int v, double value) { var[v] = value; }

    // get slider array variable
    public double getVar(int sliderIndex) { return var[sliderIndex]; }

    // set the rich_cheap 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;
			
				/*
				  if (C==1 || C==2 || C==4)        // ONE BIT SET
				  {
				  //System.out.print("1: Mult T by " + (1+2*var[INTRATE]));
				  T *= 1 + 2 * var[INTRATE];
				  }
				  else if (C==3 || C==5 || C==6)   // TWO BITS SET
				  {
				  //System.out.print("2: Mult T by " + (1+2*var[DIVYLD]));
				  T *= 1 + 2 * var[DIVYLD];
				  }
				  else if (C==7)                   // THREE BITS SET
				  {
				  //System.out.print("3: Mult T by " + (1+2*var[DAYS2EXP]));
				  T *= 1 + 2 * var[DAYS2EXP];
				  }
				*/
				
				// determine the repelling factor for same color
				if (aColorId == colorId(b))
				{
				    T *= 1;
				}
				else
				{
				    T *= 2;
				}
				
				// 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];
				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);
				
				// GRAVITY is now substituted with TEMPERATURE - random noise
				
				// 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);
			
			// apply random noise!
			p[0] = getNoise(p[0]);
			p[1] = getNoise(p[1]);
			p[2] = getNoise(p[2]);
			
		       	// 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)...
		sleep(30);
	    } // end while(true)...
	
	} // end try...
	catch(InterruptedException e){};
    }

    // is it near??
    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

    // return a sligh noise given an original value
    double getNoise(double orig_val)
    {
    	double t = var[TEMPERATURE] * 100;
    	double percentage = R.nextDouble()*t-t/2; // percentage in -1..1
    	return orig_val * (100 + percentage) / 100;     	// take that percentage of the orig value
    }

    // returns color id of point number i; color[colorId(i)] returns actual color
    int colorId(int i)
    {
	return ((Integer) Repel32.indexOrder.get(Repel32.indices[i])).intValue();
    }

    // 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();
    }

}