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

  Program:   Orbit Visualization Tool
  Source:    $Source: /stor/devel/ovt2g/ovt/object/Sat.java,v $
  Date:      $Date: 2001/04/18 23:51:50 $
  Version:   $Revision: 1.77 $


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 vtk.*;

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

import java.beans.*;
import java.io.*;
import java.util.*;
import java.awt.event.*;
import javax.swing.*;


public class Sat extends VisualObject implements CoordinateSystemChangeListener, 
TimeChangeListener, MagPropsChangeListener, PositionSource, MenuItemsSource {

  
  protected String orbitFileName;

  public double Hourfwd = 4.40;
// [NSAT]={4.40, 57.0, 66.0, 82.0, 24.0};

/*  orbital period hours = 2.7645e-6*A*sqrt(A) A(km)=semimajor axis   */
//   public double MapRadius = 8.0;   /* radius for mapping cusp etc. */

/**
 * Satellite's trajectory
 */
  protected Trajectory tra = new Trajectory();
  double maxVelocity = 0, minVelocity = Double.MAX_VALUE;
  

  //added by kono:
  protected Tle tleFile;
  //0 - TLE data file; 1,2,3,4 - CLUSTER data file; 5 - CLUSTER other
  private byte satType=0;
  protected String spinFileName;
  protected SpinData spinData;
  
  protected MainFieldlineModule mainFieldlineModule;
  protected MagFootprintModule magFootprintModule;
  protected OrbitMonitorModule orbitMonitorModule;
    /** Holds value of property firstDataMjd. */
  private double firstDataMjd;
  
  /** Holds value of property lastDataMjd. */
  private double lastDataMjd;
  
  public Sat(String name, String orbitFileName, OVTCore core)
          throws IOException {
    super(core, name, true);
    Log.log("Sat '" + name + "' :: init ...", 3);
    initialize(name, orbitFileName, true);
  }
  
  public Sat(String name, String orbitFileName, OVTCore core, boolean setupDataPeriod)
          throws IOException {
    super(core, name, true);
    initialize(name, orbitFileName, setupDataPeriod);
  }

  protected void initialize(String name, String orbitFileName, boolean setupDataPeriod) 
          throws IOException{
    this.orbitFileName = orbitFileName;
    
    //following paragraph of code added by kono
    String tmps=new String(orbitFileName);
    int tmpi=tmps.lastIndexOf('.');
    this.spinFileName=new String(tmps.substring(0,tmpi)+".spin");
    this.spinData=new SpinData(spinFileName);
    if (orbitFileName.endsWith(".tle")) {              //Def. type of data?
       satType = 0;                                    //TLE satellite
       tleFile=new Tle(this.orbitFileName);
       firstDataMjd = tleFile.getFirstMjd();
       lastDataMjd = tleFile.getLastMjd();
    } else {                                           //CLUSTER satellite
       char type=orbitFileName.charAt(orbitFileName.length()-4);
       switch(type){
          case '1': satType = 1; break;
          case '2': satType = 2; break;
          case '3': satType = 3; break;
          case '4': satType = 4; break;
          default: satType = 5;       //error!!!
       }
    }

    setIcon(new ImageIcon(OVTCore.getImagesDir() + "satellite.gif"));
    // create sat modules and put them to children
    
    
    AbstractSatModule satModule = new SatelliteModule(this);
    addPropertyChangeListener(satModule);
    addChild(satModule);
    
    satModule = new OrbitModule(this);
    addPropertyChangeListener(satModule);
    addChild(satModule);
    
    satModule = new LabelsModule(this);
    addPropertyChangeListener(satModule);
    addChild(satModule);
    
    
    mainFieldlineModule = new MainFieldlineModule(this);
    addPropertyChangeListener(mainFieldlineModule);
    addChild(mainFieldlineModule);
    
        
    // create mainFootprintModule
    AbstractSatModule mainFootprintModule = new AbstractSatModule(this, "Footprints", getCore().getImagesDir()+"footprints.gif", true);
    // create magFootprintModule
    magFootprintModule = new MagFootprintModule(mainFootprintModule);
    // let MagFPM listen fo eventd from mainFootprintModule
    mainFootprintModule.addPropertyChangeListener(magFootprintModule);
    mainFootprintModule.addChild(magFootprintModule);
    // add mainFootprintModule
    addPropertyChangeListener(mainFootprintModule);
    addChild(mainFootprintModule);
    
    // dont' add this modules if in the server mode
    if (!OVTCore.isServer()) {
        /*satModule = new DataModule(this);
        addPropertyChangeListener(satModule);
        addChild(satModule);*/
        
        orbitMonitorModule = new OrbitMonitorModule(this);
        addPropertyChangeListener(orbitMonitorModule);
        addChild(orbitMonitorModule);
        
    }
    
    if (setupDataPeriod) setupDataPeriod();
    updateEnabled();
  }
  
  /** @return spin vector in gei */
  protected double[] getSpinVectGEI(double mjd) {
    return spinData.getSpinVect(mjd);
  }
  
  //added by kono
  /**
   *@return null or spin vector in current CS (abs. value of vector is a spin rate)
   */
  public double[] getSpinVectorRate(){
     if (this.isSpinAvailable()){
        double[] geiSpin = getSpinVectGEI(getMjd());
        if(geiSpin==null)
           return null;
        Trans trans=this.getTrans(getMjd());
        Matrix3x3 mtrx=trans.gei_gsm_trans_matrix(); //GEI -> GSM
        double[] gsmSpin=mtrx.multiply(geiSpin);
        mtrx=trans.gsm_trans_matrix(getCS());        //GSM -> current CS
        return mtrx.multiply(gsmSpin);               // result in current CS
     } else return null;
  }
  
  //added by kono
  /**
   *
   *@return null or angles between spin & B(mag. field), V(velocity), S(sun) vectors
   */
  public double[] getSpinAngles() {
      /*double mjdx=getMjd();
     double[] geiSpinVect=getSpinVectGEI(mjdx);  //spin vect in GEI
     if(geiSpinVect==null)
        return null;
     GeiAndVei geivei;
     try {
        geivei=this.getSatPos(mjdx);                   //sat pos&vel in GEI CS
     } catch (IOException e){
        return null;
     }
     Trans trans=this.getTrans(mjdx);
     Matrix3x3 mtrx=trans.gei_gsm_trans_matrix();      //GEI -> GSM matrix
    
     double[] gsmSpinVect=mtrx.multiply(geiSpinVect);  //spin vect in GSM
     double[] gsmPos=mtrx.multiply(geivei.gei);        //sat position in GSM
     double[] gsmVel=mtrx.multiply(geivei.vei);        //sat velocity in GSM
     double[] gsmB=this.getMagProps().bv(gsmPos,mjdx); // B vector in GSM
     double[] geiES=Utils.sunmjd(mjdx);                //Earth->Sun vect in GEI
     double[] gsmES=mtrx.multiply(geiES);              //Earth->Sun vect in GSM
     double[] gsmSS=Vect.sub(gsmES,gsmPos);            //Sat->Sun vect in GSM

     Vect.normf(gsmB,1.0);
     Vect.normf(gsmSS,1.0);
     Vect.normf(gsmVel,1.0);
     Vect.normf(gsmSpinVect,1.0);
      */
     double[] angles=new double[3];
     //angles[0]=Vect.angleOf2vect(gsmSpinVect, gsmB);   //angle between Sp & B
     //angles[1]=Vect.angleOf2vect(gsmSpinVect, gsmVel); //angle between Sp & V
     //angles[2]=Vect.angleOf2vect(gsmSpinVect, gsmSS);  //angle between Sp & SunDir

     return angles;  //all in radians!!!, kono
  }
  
  //added by kono
  public boolean isSpinAvailable(){
    return spinData.isAvailable();
  }
  
  //added by kono
  public byte getSatType(){
     return satType;
  }
  
  public String getOrbitFileName() {
    return orbitFileName;
  }
  
    /** Returns the time for the first data entry in OrbitFile
   * @return The time for the first data in OrbitFile
   */
  public double getFirstDataMjd() {
    return firstDataMjd;
  }
  
  /** Returns the time for the last data entry in OrbitFile
   * @return Value of property lastDataMjd.
   */
  public double getLastDataMjd() {
    return lastDataMjd;
/*    try {
       return Tle.getLastMjd(this.orbitFileName);
    } catch (IOException e){
       System.err.println(e.toString());
       return 1000000;
    }*/
  }
  
  /** aa
   * @return Value of property timeSet...
   */
  public TimeSet getTimeSet() {
    return getCore().getTimeSettings().getTimeSet();
  }
  

  /** Getter for property startMjd.
   * @return Value of property startMjd.
   */
  public double getStartMjd() {
    return getTimeSet().getStartMjd();
  }
  
  /** Getter for property stopMjd.
   * @return Value of property stopMjd.
   */
  public double getStopMjd() {
    return getTimeSet().getStopMjd();
  }
  
  /** Getter for property stepMjd.
   * @return Value of property stepMjd.
   */
  public double getStepMjd() {
    return getTimeSet().getStepMjd();
  }
  
  /** Sets up Sat's first data entry time */ 
  protected void setupDataPeriod() throws IOException {
    int ymd, hms, ms;
    double  sma = 0, periodh, mjd;
    int k;
    Hourfwd = 24.0;
    ymd = 800101;
    hms = ms = 0;
//    double res[] = getFirstMjd(getOrbitFileName()); // satorbit.c
        
//    firstDataMjd = res[0];
    
//    sma = res[1];
    sma = 7000.0;       // to be corrected by kono from koko (satorbit.c)
    periodh = 2.764e-6 * sma * Math.sqrt(sma);
    k = (int)periodh * 60;
    k = (k / 10 )* 10;
    periodh = (double) k / 60.0;
    Hourfwd = periodh;
  }

// modification of fill_traj from suppr.c

  protected void updateTrajectory() {
    System.out.print("Computing orbit for " + getName()+" ... ");
    int i, n, ok, maxzi;
    double  geo[] = new double[3],  gsm[] = new double[3], gse[] = new double[3];
    double[] gei, vei;
    int  plus = 1;
    double  mjd, model, dist_bs, dist_mp, dist_cu, maxz;
    double altf,mlt;

    double ClatMax=0.0;  // max-corrected-latitude in orbit (subpr.c line: 40)
    double velocity;
    ok=1;
    maxz=0.0;
    maxzi=0;
    ClatMax=0.0;
    Trans trans;
    TrajectoryPoint tr;
    GeiAndVei geiAndVei;
    tra.clear();


    //System.out.println(new TimeSet(getStartMjd(), getStopMjd(), getStepMjd()));
    
    double[] timeMap = getTimeSet().getValues();
    long start = System.currentTimeMillis();
    int N = timeMap.length;
    int l = 0;
    double[][] gei_arr = new double[N][3];
    double[][] vei_arr = new double[N][3];
    
    try {
        fill_GEI_VEI(orbitFileName, timeMap, gei_arr, vei_arr); 
    } catch (IOException e2) {
        Log.err(e2.getMessage(), 0);
    }
    long fill_time = System.currentTimeMillis();
    Log.log("fill_GEI_VEI : " + (fill_time - start)/60 + " sek", 3);
            
     for (int k=0; k<N; k++){
        mjd = timeMap[k];
        tr = new TrajectoryPoint();
        tr.mjd = mjd;
      
        gei = new double[3];
        vei = new double[3];
        for (i = 0; i < 3; i++){ 
            gei[i] = gei_arr[k][i];
            vei[i] = vei_arr[k][i];            
        }

        //l++;
        
        velocity = Vect.absv(vei);
        if (velocity > maxVelocity) maxVelocity = velocity;
        if (velocity < minVelocity) minVelocity = velocity;

        /*      
        System.out.println("mjd["+k+"]"+timeMap[k]);
        System.out.println("gei "+k+" : "+gei[0]+"|"+gei[1]+"|"+gei[2]);
        System.out.println("vei "+k+" : "+vei[0]+"|"+vei[1]+"|"+vei[2]);
	*/    
        for (i = 0; i < 3; i++) gei[i] /= Const.RE;
        
        // get transformation class
        trans = getTrans(mjd);
        // Transform gei to geo
        geo = trans.gei2geo(gei);
        // Transform geo to gsm
        gsm = trans.geo2gsm(geo);
        // Transform gei to gse
        gse = trans.gei2gse(gei);
        
        tr.rr = Vect.absv(gei);
        tr.gei = gei;
        tr.vei = vei;
        tr.geo = geo;
        tr.gsm = gsm;
        tr.gse = gse;
        tr.sm = trans.gsm2sm(tr.gsm);
        
        tr.fsm = new double[3];
        tr.fgeo = new double[3];
        tr.fsmc = new double[3];
        tr.efoot_gsm = new double[3];
        tr.foot_cor = new double[2][3];
        tr.foot_geo = new double[2][3];
        for (i = 0; i < 3; i++) {
                tr.fsm[i] = -99.0;
          tr.fgeo[i] = -99.0;
          tr.fsmc[i] = -99.0;
        tr.efoot_gsm[i]=-99.0;
        tr.foot_cor[0][i]=-99.0;
        tr.foot_geo[0][i]=-99.0;
        tr.foot_cor[1][i]=-99.0;
        tr.foot_geo[1][i]=-99.0;
              /*fgsm[i] = 99.0;*/
        }
      
        tr.dist_eq = tr.gsm[2];       // to be corrected 
        if (tr.dist_mp <  0.5) {
      
        }
        tra.put(tr);
     }
    

   
    long endtime = System.currentTimeMillis();
    Log.log("Trans time:  " + (endtime - fill_time)/60 + " sek", 3);
    
    System.out.println(getName() + "'s trajectory has " + tra.size() + " points"); 
    
    // System.out.println("max mlat = " + ClatMax + "\n");
    valid = true;
  }


  public void fill_GEI_VEI(String filename, double[] timeMap, 
            double[][]  gei_arr,double[][]  vei_arr) throws IOException {
    getSatPosJNI(orbitFileName,timeMap, gei_arr, vei_arr,timeMap.length);
  }

  /** Returns Sat's Trajectory */
  public Trajectory getTrajectory() {
  if (!isValid()) validate();
  return tra;
  }

  /** Returns current Sat's TrajectoryPoint */
  public TrajectoryPoint getTrajectoryPoint() {
    return getTrajectory().get(getMjd());       // here returned null!!!
  }

  /** Returns Sat's position in current CS */
  public double[] getPosition() {
    TrajectoryPoint p = getTrajectoryPoint();   // here returned null!!!
    if (p != null) return p.get(getCS());
    else return null;
  }

  /** Aproximates and Returns Sat's position based on trajectory object in current CS for specified mjd. */
  public double[] getPosition(double mjd) {
      return getTrajectory().evaluate(mjd, getCS());
  }
  
  public double[] getOrbitPlaneNormal(double mjd) {
    return getTrajectory().getOrbitPlaneNormal(mjd, getCS());
  }

  /** Returns the vector wich lays in the orbit plane and perpendicular to trajectory */
  public double[] getTrajectoryNormal(double mjd) {
    return getTrajectory().getNormal(mjd, getCS());
  }
  
  public FootprintCollection[] getMagFootprintCollection() {
    return magFootprintModule.getFootprintCollection();
  }
  
  
  public MagPoint[] getMagFootprints(double mjd) {
    FootprintCollection[] fpc =  getMagFootprintCollection();
    return new MagPoint[]{fpc[0].get(mjd), fpc[1].get(mjd)};
  }

  public final boolean canBeVisible()
  { return (getTrajectoryPoint() != null); }
/*
 public double[] getPosition() 
  { return getTrajectoryPoint().getCoord(getCore().getCoordSystem()); }
*/


  //--- FieldLine staff------
  
  protected FieldlineCollection getFieldlineCollection(int type) {
    return mainFieldlineModule.getFieldlineCollection(type);
  }
  
  protected Fieldline getFieldline(int type, double mjd) {
    return mainFieldlineModule.getFieldline(type, mjd);
  }
  
  public void validate() {
    updateTrajectory();
    valid = true;
  }



  


//-------------------------------------
// Get gei[3] and vei[3] using Sat.file and time.
//public static native int getSatPosJNI(String filename, double mjd, double[] gei, double[] vei);


 /** get S/C position and velocity
 * @param filename Orbit Data File
 * @param mjd Time
 * @throws IOException
 * @return S/C position and veocity
 */

// Output: res[0] = launchMjd, res[1] = sma
//public native void get_first_mjd(String filename, double[] res);
public native int getSatPosJNI(String filename, double[] mjd, double[][] gei, double[][] vei, int N);

/** Returns time for the first data in orbit file
 * @depricated since 0.001
 */
/*public double[] getFirstMjd(String orbitFileName) throws IOException {
  double[] res = new double[2];
  get_first_mjd(orbitFileName, res);
  
  //if (code =! 0) throw new IOException("IOException with file " + filename);
  return res;
}*/

  public void timeChanged(TimeEvent evt) {
    
    if (evt.getPropertyName().equals("time")) {
      // current time and time period have changed
      updateEnabled();
      invalidate();
    }
    
    // Tell children about time change
    Enumeration e = getChildren().elements();
    while (e.hasMoreElements()) {
      try {
       ((TimeChangeListener)(e.nextElement())).timeChanged(evt);
      } catch (ClassCastException e2) {
        System.out.println("this module doesn't care about time..");
      }
    }
  }

  public void updateEnabled() {
    TimeSet timeSet = getCore().getTimeSettings().getTimeSet();
    double globalStart = timeSet.getStartMjd();
    double globalStop = timeSet.getStopMjd();
    // global time period shoud be inside Sat's data period
      if (getFirstDataMjd() <= globalStart  &&  getLastDataMjd() >= globalStop) {
        if (!isEnabled())
          setEnabled(true);
      } else {
        // torbets
        if (isEnabled())
          setEnabled(false);
      }
  }

  public void coordinateSystemChanged(CoordinateSystemEvent evt) {
    Enumeration e = getChildren().elements();
    while (e.hasMoreElements()) {
      try {
       ((CoordinateSystemChangeListener)(e.nextElement())).coordinateSystemChanged(evt);
      } catch (ClassCastException e2) {
        //System.out.println("this module doesn't care about cs..");
      }
    }
  }
  
  public void magPropsChanged(MagPropsEvent evt) {
    mainFieldlineModule.magPropsChanged(evt);
    magFootprintModule.magPropsChanged(evt);
    orbitMonitorModule.magPropsChanged(evt);
  }

  public JMenuItem[] getMenuItems() {
     JMenuItem item = new JMenuItem("Orbit Monitor");
     item.setFont(Style.getMenuFont());
     item.setEnabled(isEnabled());
     item.addActionListener(new ActionListener(){
         public void actionPerformed(ActionEvent evt) {
            orbitMonitorModule.setVisible(true);
         }
     });
     return new JMenuItem[]{item};
  }
  
}
