/*=========================================================================

  Program:   Orbit Visualization Tool
  Source:    $Source: /stor/devel/ovt2g/ovt/object/Magnetosphere.java,v $
  Date:      $Date: 2001/04/07 18:43:56 $
  Version:   $Revision: 1.20 $


Copyright (c) 2000 OVT Team (Kristof Stasiewicz, Mykola Khotyaintsev, 
Yuri Khotyaintsev)
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification is permitted provided that the following conditions are met:

 * No part of the software can be included in any commercial package without
written consent from the OVT team.

 * Redistributions of the source or binary code must retain the above
copyright notice, this list of conditions and the following disclaimer.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS
IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT OR
INDIRECT DAMAGES  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE.

OVT Team (http://ovt.irfu.se)   K. Stasiewicz, M. Khotyaintsev, Y.
Khotyaintsev

=========================================================================*/

package ovt.object;

import ovt.*;
import ovt.mag.*;
import ovt.util.*;
import ovt.beans.*;
import ovt.event.*;
import ovt.datatype.*;
import ovt.interfaces.*;

import vtk.*;

import java.lang.Math;
import java.util.*;

import java.awt.event.*;

import javax.swing.*;

/**
 * This class caries about ploting magnetisphere 
 */

public class Magnetosphere extends VisualObject implements 
   TimeChangeListener, CoordinateSystemChangeListener, MagPropsChangeListener {  

  

   Hashtable mpause_edge;
   Fieldline f_line[][] = new Fieldline[MAXE][4]; /* coordinates of f-lines */

   Fieldline fcusp[] = new Fieldline[4]; /* corrected coordinates of cusp */

  protected vtkActor actor = null;
  protected MagnetosphereActorCollection actorCollection = new MagnetosphereActorCollection();
  protected Hashtable characteristicsCollection = new Hashtable();
  protected vtkPoints points;
  protected vtkScalars scalars;
  protected vtkCellArray lines;
  
  /** holds the time, for which the shown magnetosphere was calculated. */
  private double actorsMjd = -1;

   protected double bmin = 7.26, bmax = 57458; // min and max absv(Magnetic field)
   									        // they are updated in prepareData()
   /** 10 (6) number of angular sectors in 90 deg range */
   public static final int NAS = 2;  //6 
   /** 15 (6) number of edge points on 0 -Xlim interval */
   public static final int LIM = 2; //6    
   /** number of points on the mpause edge */
   public static final int MAXE = 2*NAS+LIM+1; 
   /** number of field lines */
   public static final int MAXFL = 2*(NAS+LIM)+4*NAS; 
   /** max number of points along a field line */
   public static final int NPF = 120;
   /** number of y-plane field lines !!! strange...*/
   public static final int NPY = 7; 
	/** modified Julian day */
   public double mjd;          

   public double tilt;         /* dipole tilt angle (deg) */

   public int n_cusp_index;    /* index such that f_line with NPF=ns-index */
   public int s_cusp_index;    /* gives position of the exterior cusp    */
   public double n_cusp[] = new double[3];    /* exterior cusp north */
   public double s_cusp[] = new double[3];    /* exterior cusp south */
   public int npy[][] = new int[2][NPY];       /* number of points in yplane lines */
   public float yplane[][][] = new float[2][NPY][NPF*3];  /*field lines at 70,75,80,85,90,85,80,75,70 */
   //public int ns[][] = new int[MAXE][4];     /* number of points  in f_line */
   //public double fcusp[][][] = new double[MAXE][4][3]; /* corrected coordinates of cusp */
   //public double f_line[][][] = new double[MAXE][4][NPF*3];  /* coordinates of f-lines */

                   /* all coordinates are in GSM in units of RE 
                           index 0 = north dusk
                                 1 = north dawn
                                 2 = south dawn
                                 3 = south dusk
                         */




// field-line tracing constants
public static final double  EPS		=	0.001;
public static final double  HMIN	=	0.001;
public static final double  FF		=	1.01;
public static final int  MAXCOUNT	=	300;
public static final double  STMIN	=	0.01;

public Magnetosphere(OVTCore core) {
	super(core, "Magnetosphere", core.getImagesDir()+"magnetosphere.gif");
	int is;
	/*for(int i=0; i<MAXE; i++) 
		for(is=0; is<4; is++)
			f_line[i][is] = new FieldLine(getMjd());*/
	
	//for(int i=0; i<4; i++) fcusp[i] = new Fieldline(getMjd());
        
        
	
}

public void setMjd(double mjd) {
  this.mjd = mjd;
  calculate();
  prepareData();
}

public void validate() {
  if (!isValid()) {
    //actorCollection.clear();
    valid = true;
  }
}



protected void calculate() {
    //Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
    
/* fills magnetospheric structure  
   taken from   subpr.c : fill_mps
*/
	Fieldline f_l;

	double[]   gsm  = new double[3], 
	           dir  = new double[3], 
			   foot = new double[3],
			   imf  = new double[3]; 
    double  step; 

    double[]   latline = {60.0, 70.0, 80.0, 90.0, 80.0, 70.0, 60.0}; // [NPY]
    int  max = NPF;
 

    double  x, r, sigy, sigz;
    int i, ii, m, is, n = 0, sense, option, method = 1;
    int  plus = 1, zero = 0 , minus = (-1); // static 
    double   stepin = 0.5, dangle = 90.0 *Const.D_TO_R / NAS;
    double   altc = 4.0*Const.RE;
    double  mlat = 0, mlt = 0, mlong = 0, ell = 0, dx, xmin;

    
    
    // clean data structures
    try {
      for(i=0; i<MAXE; i++) 
        for(is=0; is<4; is++)
          f_line[i][is].removeAllElements();
    } catch (NullPointerException e) {}
    
    
    dx = getMagProps().xlim / LIM;
    xmin = getMagProps().xlim - 10.0;

    System.out.print("Computing magnetosphere boundary [4] ... ");
    getCore().setStatus("Computing magnetosphere boundary ... ");
    //XYZWindow.statusLine.showProgressMonitor();
    
    ii = 1; //  no symetry
    for (is = 0; is < 4; is += ii) {
		
        sigy = (is == 1 || is == 2) ? -1.0 : 1.0;
        sigz = (is == 2 || is == 3) ? -1.0 : 1.0;
        sense = (int)sigz;
		
	System.out.print(" " + (is+1));
        getCore().setStatus("Computing magnetosphere boundary "+is+" of 4 ");
        try { Thread.currentThread().sleep(100); } catch (InterruptedException ignore) {}
        
        //System.out.printn("fill_mps is=" + is);
	for (m = 0; m < MAXE; m++) {
        	/* index 0 = noon
       		index NAS = Y axis
       		index NAS+LIM = parallel to Y at xlim
       		index 2*NAS+LIM at midnight 
    		*/

           if (m < NAS) {  /* day side (dusk for is=0; dawn for is=1)  */
               r = 8.0 + 6.0 * m / NAS;
               dir[0] = gsm[0] = r * Math.cos(sigy * dangle * m);
               dir[1] = gsm[1] = r * Math.sin(sigy * dangle * m);
               dir[2] = gsm[2] = 0.0;
           } else if (m <= NAS + LIM) {  /* dusk side for is=0 dawn for is=1 */
                   x = dx * (m - NAS);
                   gsm[0] = x;
                   gsm[1] = sigy * 23.9 * Math.atan(Math.sqrt((getMagProps().mSub - x) / 15.9));
                   
				   /* Binsack Howe formula */
				   
                   dir[0] = dir[2] = gsm[2] = 0.0;
                   dir[1] = sigy * 2.0;
           } else {
			   		// We are at xlim.
                   r = 23.9 * Math.atan(Math.sqrt((getMagProps().mSub - getMagProps().xlim) / 15.9));
                   gsm[0] = getMagProps().xlim;
                   dir[1] = gsm[1] = sigy * r * Math.cos(dangle * (m - NAS - LIM));
                   dir[2] = gsm[2] = sigz * r * Math.sin(dangle * (m - NAS - LIM));
                   dir[0] = 0.0;
                   //System.out.print("gsm[1]="+gsm[1]+" m="+m);
           }
		   

    	// find the position of magnetopause starting from gsm to dir direction
		
		Trace.lastline(getMagProps(), getMjd(), gsm, dir, xmin, altc, sense, Const.ERR_MP);
        
		//System.out.println("["+m+"] "+gsm[0]+"\t"+gsm[1]+"\t"+gsm[2]+"\n");        
		step = sigz * stepin;
        
		f_l = Trace.traceline(getMagProps(), getMjd(), gsm, step, plus);
		n = f_l.size();
		
		//System.out.println("["+m+"] -> "+ n +" points!");
        
		for (i=0; i<3; i++) dir[i] = f_l.lastPoint().gsm[i];
        if (Vect.absv(dir) > 1.2 + getMagProps().alt / Const.RE) {
            f_l = Trace.traceline(getMagProps(), getMjd(), gsm, step, zero);
        }

        /*ns[m][is] = n;
        if (ii > 1)
            ns[m][is+1] = n;
        if (ii > 2)
            ns[m][is+2] = ns[m][is+3] = n;
*/

		
        f_line[m][is] = f_l;
			
/* let's skip it...
if (ii > 1) { // dawn -dusk symetry 
				
        	//f_line[is+1].getLine(m).addPoint(i, xx[i], -yy[i], zz[i]);
			
			f_line[m][is+1] = (Fieldline)f_l.clone();
			f_line[m][is+1].symetrize(1, -1, 1);
			
			
		}
        if (ii > 2) { // north -south and dawn-dusk symmetry 
				
			//f_lines[is+2].getLine(m).addPoint(i, xx[i], -yy[i], -zz[i]);
			//f_lines[is+3].getLine(m).addPoint(i, xx[i], yy[i], -zz[i]);
            f_line[m][is+2] = (Fieldline)f_l.clone();
			f_line[m][is+2].symetrize(1, -1, -1);
			f_line[m][is+3] = (Fieldline)f_l.clone();
			f_line[m][is+3].symetrize(1, 1, -1);    
		}*/

        
      }
    }

/*
// find cusp_index and fill cusp position  
for (ii = 0; ii < 3; ii += 2) {
    x = 0.0;
    
	//m = ns[0][ii];
	m = f_line[0][ii].size();
    
	//n = ns[MAXE-1][ii];
	n = f_line[MAXE-1][ii].size();
    
	is = 1;
    while (x < Const.C_SEPARATION) {  // funnel in the exterior cusp 
        for (i = 0; i < 3; i++)
            dir[i] = f_line[0][ii].point(m-is)[i]
				- f_line[MAXE-1][ii].point(n-is)[i];
				
				
        x = Math.sqrt(dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2]);
        is ++;
    }
    if (ii == 0) {
        n_cusp_index = is;
        for (i = 0; i < 3; i++)
			n_cusp[i] = f_line[0][0].point(m-is)[i]
				- f_line[MAXE-1][0].point(n-is)[i] / 2.0;
		
    } else {
        s_cusp_index = is;
        for (i = 0; i < 3; i++)
			s_cusp[i] = f_line[0][2].point(m-is)[i]
				- f_line[MAXE-1][2].point(n-is)[i] / 2.0;
	}
}*/

// symmetrize equatorial projection if EQ_MIN==1  
/*
option = Const.EQ_MIN;
if (option == 1) {
    for (is = 0; is < 2; is++)
        for (m = 0; m <= NAS + LIM; m++) {
            for (i = 0; i < 3; i++) {
				gsm[i] = f_line[m][is].firstPoint().gsm[i];
                dir[i] = f_line[m][3-is].firstPoint().gsm[i];
			}
            x = Vect.absv(gsm) - Vect.absv(dir);
            if (x > 0.10) {  // north hemisphere exceeds south hemisphere
                step = stepin;
                f_l = Trace.traceline(getMagProps(), getMjd(), dir, step, plus);
                ii = is;
            } else if (x < -0.10) {
                step = (-stepin);
                f_l = Trace.traceline(getMagProps(), getMjd(), gsm, step, plus);
                ii = 3 - is;
            } else {
				System.out.println("ovt.objects.Magnetosphere.calculate(): error!");
				f_l = new Fieldline(getMjd());
			}
			
            if (Math.abs(x) > 0.09)
                f_line[m][ii] = f_l;
				
        	//System.out.println(m+"\t"+x);
		}
}*/

/*
// find corrected coord of the cusp  

for (is = 0; is < 4; is++)
for (ii = 0; ii < MAXE; ii++) {
    n = f_lines[is].getLine(ii).size() - 1;
	//n = ns[ii][is] - 1;
	
	for (int j=0; j<3; j++) 
    	gsm[j] = f_lines[is].getLine(ii).getPoint(n)[j];
    if (model < 0.0)
        dir = MagPack.gsmdToSM(gsm, mjd);
    else {
        dir = MagPack.gsmdToGEO(gsm, mjd);
        MagPack.setCorrgma(mjd, dir, altf, mlat, mlong, mlt, ell);
        dir = MagPack.mlatToSM(altf, mlat, mlt);
    }
    
	fcusp[is].addPoint(ii, dir[0], dir[1], dir[2]); 
	
	//System.out.println(dir[0]+"\t"+dir[1]+"\t"+dir[2]);
}
*/

  System.out.println(" Done.");
  getCore().setStatus("Computation of magnetosphere boundary done.");
  
}


// Has to be included when in update() method
protected void prepareData() {
  System.out.print(" Creating visual representation ...");
  
	int i, j, k, count, linesize;
	double [] p;

	Fieldline[] f_l = new Fieldline[MAXFL];
	
        // clean data
	points  = new vtkPoints();
	scalars = new vtkScalars();	
	lines   = new vtkCellArray();
	
	// Clue lines starting from y=0
	for (i = 0; i<NAS+LIM; i++) {
		
		f_l[i] = new Fieldline(getMjd());
		
		for (j = f_line[i][0].size() - 1; j >= 0; j--)
			f_l[i].add( f_line[i][0].point(j) );
		
		for (j = 1; j < f_line[i][3].size(); j++)
			f_l[i].add(f_line[i][3].point(j) );
		
		
		f_l[NAS+LIM+i] = new Fieldline(getMjd());
		
		for (j = f_line[i][1].size() - 1; j >= 0; j--)
			f_l[NAS+LIM+i].add( f_line[i][1].point(j) );
		
		for (j = 1; j < f_line[i][2].size(); j++)
			f_l[NAS+LIM+i].add( f_line[i][2].point(j) );
		
	}
	
	// Add fieldlines starting from Xlim
	for (k=0; k<4; k++) 
		for (i = 0; i < NAS; i++) {
			f_l[2*(NAS+LIM)+k*NAS+i] = new Fieldline(getMjd());
			for (j = 0; j < f_line[NAS+LIM+i][k].size(); j++)
				f_l[2*(NAS+LIM)+k*NAS+i].add( f_line[NAS+LIM+i][k].point(j) );
		}
	
	
	// Fill vtkPoints & scalars
	double b;
	bmax = 0; bmin = Double.MAX_VALUE;
        MagPoint magPoint;
	for (i = 0, count = 0; i<MAXFL; i++) {
		linesize = f_l[i].size();
		//System.out.println("-------------- NEW LINE ["+linesize+"]---------------------");
		for (j = 0; j<linesize; j++) {
                  magPoint = f_l[i].point(j);
			p = magPoint.gsm;
			points.InsertPoint(count+j, p[0], p[1], p[2]);
			//System.out.println((count+j)+"\t"+p[0]+"\t"+p[1]+"\t"+p[2]);
			b = Vect.absv(magPoint.bv);
			scalars.InsertScalar(count+j, b);
			if (b > bmax) bmax = b;
			if (b < bmin) bmin = b;
			//System.out.println(b);
		}
		
		count+=linesize;
	}
	//System.out.println("bmin="+bmin+"  bmax="+bmax);
	
	for (i = 0, count = 0; i<MAXFL; i++) {
		linesize = f_l[i].size();
		lines.InsertNextCell(linesize);
		for (j = 0; j<linesize; j++) {
			lines.InsertCellPoint(count+j);
			
		}
		count+=linesize;
	}
  System.out.println(" Done.");
}

public vtkActor getActor(double mjd) {
  if (!isValid()) validate();
  Characteristics charact = getMagProps().getMagFieldCharacteristics(mjd);
  vtkActor act = actorCollection.getActor(charact);
  if (act == null) {
      setMjd(mjd);
    
      vtkPolyData polyData = new vtkPolyData();

      polyData.SetPoints(points);
      polyData.SetLines(lines);
      polyData.GetPointData().SetScalars(scalars);
	
	/*vtkMaskPolyData mask = new vtkMaskPolyData();
	mask.SetInput(polyData);
	mask.SetOnRatio(1); // (2) - each 2-nd line i shown
	//mask.SetOffset(0);*/
	
      vtkLogLookupTable lut  = new vtkLogLookupTable();
      //lut.SetNumberOfColors(256);
      lut.SetHueRange(0.6667, 0);
	
      vtkPolyDataMapper mapper = new vtkPolyDataMapper();
        mapper.SetInput(polyData); //mask.GetOutput()
	mapper.SetScalarModeToUsePointData();
        mapper.ScalarVisibilityOn();
	mapper.SetScalarRange(MagProps.BMIN, MagProps.BMAX);
	mapper.SetLookupTable(lut);
	   
        act = new vtkActor();
        act.SetMapper(mapper);
          //act.GetProperty().SetColor(1,0,0);	
      
      // add actor to actor collection
      //int pos = getCore().getTimeSettings().getTimeSet().getCurrentMjdIndex();
      actorCollection.put(charact, act);
  }
  return act;
}

public void show() {
  actor = getActor(getMjd());
  getRenderer().AddActor(actor);
  rotate();
  actorsMjd = getMjd();
  firePropertyChange("updated", new Boolean(true), new Boolean(false));
}
	
public void hide() {
  getRenderer().RemoveActor(actor); 
  actorsMjd = -1;
}

public void setVisible(boolean visible) {
    if (isVisible() != visible) {
      if (visible) show();
      else hide();
      super.setVisible(visible);
    }
  }
  
  public void rotate() {
    Matrix3x3 m3x3 = getTrans(getMjd()).gsm_trans_matrix(getCS());
    if (actor !=null) actor.SetUserMatrix(m3x3.getVTKMatrix()); 
  }

  public void timeChanged(TimeEvent evt) {
      if (evt.getPropertyName().equals("time")) actorCollection.clear();
      if (isVisible()) {
          double mjd = getMjd();
          Characteristics ch = getMagProps().getMagFieldCharacteristics(mjd);
          vtkActor newActor = actorCollection.getActor(ch);
          if (newActor == null || newActor != actor) {
                invalidate(); hide(); show();
          } else rotate();
          if (!isUpdated()) firePropertyChange("updated", new Boolean(false), new Boolean(false));
      }
  }
  
  public void coordinateSystemChanged(CoordinateSystemEvent evt) {
    if (isVisible()) rotate();
  }
  
  public void magPropsChanged(MagPropsEvent evt) {
    if (isVisible()) {
      hide();
      show();
    }
  }
  
  public void update() {
      if (!isUpdated()) {
        //invalidate();
        if (isVisible()) {
            hide();
            show();
        }
        firePropertyChange("updated", new Boolean(true), new Boolean(false));
      }
  }
  
  public boolean isUpdated() {
      return (actorsMjd == getMjd());
  }
} // - end of Magnetosphere class

// ------------------------------_------------------------------

class MagnetosphereActorCollection extends Hashtable {
  
  public MagnetosphereActorCollection() {
      super();
  }
  
  /*public vtkActor getActor(Characteristics ch) {
    return (vtkActor)get(ch);
  }*/
  
  public vtkActor getActor(Characteristics ch) {
      Enumeration e = keys();
      while (e.hasMoreElements()) {
          Characteristics obj = (Characteristics)e.nextElement();
          if (ch.equals(obj)) return (vtkActor)get(obj);
      }
    return null;
  }
  
}
/*
class MagnetosphereActorCollection extends Vector {
    
    private Vector characteristics = new Vector();
  
  public MagnetosphereActorCollection() {
  }
  
  public void put(vtkActor actor, Characteristics ch) {
    addElement(actor);
    Log.log("MagnetosphereActorCollection.size="+size(), 0);
    characteristics.addElement(ch);
    Log.log("characteristics.size="+characteristics.size(), 0);
  }
  
  public vtkActor getActor(Characteristics ch) {
    int index = characteristics.indexOf(ch);
    Log.log("index="+index, 0);
    if (index == -1) return null;
    return (vtkActor)elementAt(index);
  }
  
  public void clear() {
    removeAllElements();
    characteristics.removeAllElements();
  }
  
  /*
  public double closest(double mjd) {
    Object obj = getElement(mjd);
    if (obj != null) return mjd;
    double res = Double.MIN_VALUE;
    double mjd1;
    Enumeration e = keys.elements();
    while (e.hasMoreElements()) {
      mjd1 = ((Double)e.nextElement()).doubleValue();
      if (Math.abs(res-mjd1) < Math.abs(res-mjd)) res = mjd1;
    }
    return res;
  }
  
}*/