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

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


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.interfaces.*;
import ovt.datatype.*;
import ovt.mag.*;
import ovt.util.*;
import ovt.event.*;

import java.io.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import java.lang.Runtime;

//Designed for CLUSTER class
public class ClusterSat extends Sat {

// private Hashtable mjdHash=new Hashtable();
 protected static GeiAndVei GV= new GeiAndVei();
 protected static double minMjd=0,maxMjd=0; // why static ?????????
 public final static int MaxSatsNum=4;  // Number of satelites 
 protected int satNumber; 

 public ClusterSat(String name, String orbitFileName, OVTCore core) throws IOException {
    super(name, orbitFileName, core, true);
    setIcon(new javax.swing.ImageIcon(OVTCore.getImagesDir() + "cluster_sat.gif"));
    int satNumber = new Integer("" + orbitFileName.charAt(orbitFileName.length()-4)).intValue();
    //System.out.println("CLUSTER # " + satNumber);
    // set the colour
    SatelliteModule module = (SatelliteModule)getChild("Sat");
    Color color = Color.blue;
    switch (satNumber) {
        case 1 : color = Color.black; break;
        case 2 : color = Color.red; break;
        case 3 : color = Color.green; break;
        case 4 : color = Color.magenta; break;
    }
    module.setColor(color); 
    
    defineTimeLimits(getOrbitFileName());
//    launchMjd = getFirstMjd(getOrbitFileName())[0];
 }
 
 /** Return the number of Cluster s/c 1..4 */
 public int getSatNumber() {
    return satNumber;
 }

 /** @return spin vector in gei, vector parallel to Z-GSE if no spin data available */
  protected double[] getSpinVectGEI(double mjd) {
    double[] res = super.getSpinVectGEI(mjd);
    if (res == null) {
        //System.out.println("Spin for "+ new Time(mjd) +" is not available. Setting it to Z-GSE");
        Matrix3x3 mtrx = getTrans(mjd).gse_gei_trans_matrix();
        res = mtrx.multiply(new double[]{0, 0, 1});
    } //else System.out.println("Spin for "+ new Time(mjd) +" is available.");
    return res;
  }
  
  /** redefinition of previous method. 
   * @return true
   */
  public boolean isSpinAvailable(){
    return true;
  }
 
 private static class ClusterRecord extends Object{
    public final int MaxCoefs=10;

    //1st record
    int nsatin;

    //2nd record
    public int recID; //Shall be 200+nSatin
    public double dayBeg,dayEnd,epoch,revEpo,smAxis,oMotin;

    //3rd record: Reference state vector & S/C Earth center dist. at epoch
    public double Y[],rDist;
    public int koeff;
    
    //Data record
    public double[][] Coefs;   //Data matrix with up to 10x6 coefs.
    
    public ClusterRecord(){
       Y=new double[6];
    }

    public int set2ndRecord(String ss){
       StringTokenizer stok=new StringTokenizer(ss);
       int i=0;  //Elements counter
       
       while(stok.hasMoreTokens() && i<7){
          String s1=new String(stok.nextToken());
          switch(i){
             case 0: //recID;
                recID=new Integer(s1).intValue();
                if(recID!=(nsatin+200))   //cheking for error
                   return 1;
                else break;
             case 1: //dayBeg
                dayBeg=new Double(s1).doubleValue()+18262.0;
                break;
             case 2: //dayEnd
                dayEnd=new Double(s1).doubleValue()+18262.0;
                break;
             case 3: //epoch
                epoch=new Double(s1).doubleValue()+18262.0;
                break;
             case 4: //revEpo
                revEpo=new Double(s1).doubleValue();
                break;
             case 5: //smAxis
                smAxis=new Double(s1).doubleValue();
                break;
             case 6: //oMotin
                oMotin=new Double(s1).doubleValue();
                return 0;   // this is last record, returning ...
             default:
                return 0;
          }
          ++i;
       }
       if(i<7)return 1;  // should be more parameters in line!
       return 0; // No any error!!!
    }
    
    public int set3rdRecord(String ss){
       StringTokenizer stok=new StringTokenizer(ss);
       int i=0,tmp_koeff;  //Elements counter
       
       while(stok.hasMoreTokens() && i<8){
          String s1=new String(stok.nextToken());
          switch(i){
             case 0: //koeff;
                tmp_koeff=new Integer(s1).intValue();
                koeff=tmp_koeff-300;
                if(koeff<0 || koeff>=MaxCoefs){
                   return 1;  //error in this record
                }
                Coefs=new double[koeff][6];  // Init. array!
                break;
             case 1:
                Y[i-1]=new Double(s1).doubleValue(); break;
             default:
                if(i>=1 && i<=6){
                   Y[i-1]=new Double(s1).doubleValue();
                   break;
                } else {
                   if(i==7)
                      rDist=new Double(s1).doubleValue();
                   break;
                }
          }
          ++i;
       }
       if(i<8){
          return 1;  // should be more parameters in line!
       }
       return 0; // No any error!!!
    
    }

    //id should be 1..10
    public int setDataRecord(int id,String ss){
       StringTokenizer stok=new StringTokenizer(ss);
       int nRec4Check;            // Just for checking
       int i=0;  //Elements counter
       
       while(stok.hasMoreTokens() && i<7){
          String s1=new String(stok.nextToken());
          switch(i){
             case 0: //nrec4Check;
                nRec4Check=new Integer(s1).intValue();
                if(nRec4Check!=(11*id+koeff))   //cheking for error
                   return 1;
                else break;
             default: //setting data record
                if(i>=1 && i<=6){
                   Coefs[id-1][i-1]=new Double(s1).doubleValue();
                   break;
                } else return 0;
          }
          ++i;
       }
       if(i<7)return 1;  // should be more parameters in line!
       return 0; // No any error!!!    
    }
    
/*    public void print2nd(){
       System.out.println(recID+","+dayBeg+","+dayEnd+","+epoch+","+revEpo+","+
       smAxis+","+oMotin);
    }
    public void print3rd(){
       System.out.println(koeff+","+Y[0]+","+Y[1]+","+Y[2]+","+Y[3]+","+
       Y[4]+","+Y[5]+","+rDist);
    }
    public void printData(){
       for(int i=0;i<koeff;++i){
          for(int j=0;j<6;++j)
             System.out.print(" "+Coefs[i][j]);
          System.out.println("");
       }
    }    
    public void printAll(){
       System.out.println("********************************************");
       print2nd();
       print3rd();
       printData();
    }*/
 }

  public double getFirstDataMjd() {
    return minMjd;
  }

  public double getLastDataMjd() {
    return maxMjd;
  }

  
 // Gets min/max Mjd from cluster*.cl datafile (reads header: 1st line)
 private void defineTimeLimits(String datafile) throws IOException, FileNotFoundException{
    BufferedReader inData;
    String str=new String();
    String tmps=new String();
    String tmps2=new String();
    int lineCount=0,i;
    double mjdBeg=-1000.0,mjdEnd=-1000.0;
    
    try {
        inData=new BufferedReader(new FileReader(datafile));
    } catch (FileNotFoundException e){
        throw new IOException("File "+datafile+" not found.");
    } catch (IOException e){
        throw new IOException("IO error with "+datafile+" datafile.");
    }
    
    while(inData.ready()){
       str=inData.readLine();
       if (str == null) break;
       if(str.length()<40)continue;
       ++lineCount;
       StringTokenizer tok=new StringTokenizer(str);
       if(tok.hasMoreTokens()){
          tmps=tok.nextToken();
          // reading mjd limits lines
          int code=new Integer(tmps).intValue();
          if(code==201 || code==202 || code==203 || code==204){
             if(mjdBeg==-1000.0){        // getting begining mjd
                mjdBeg=new Double(tok.nextToken()).doubleValue();
             }
             tmps2=str;
          }
       }
    }
    inData.close();
    
    i=0;
    StringTokenizer tok2=new StringTokenizer(tmps2);
    while(tok2.hasMoreTokens()){  //reading mjd limits lines
       tmps=tok2.nextToken();
       ++i;
       if(i==3){  // reading mjd limits lines
          mjdEnd=new Double(tok2.nextToken()).doubleValue();
          break;
       }
    }
    if(mjdBeg<mjdEnd){
       this.minMjd=mjdBeg+18262.0;
       this.maxMjd=mjdEnd+18262.0;
       return;
    } else {
       throw new IOException("Invalid mjd limits in "+datafile+" datafile.");
    }
 }

public void fill_GEI_VEI(String datafile, double[] timeMap, 
                 double[][]  gei_arr,double[][]  vei_arr) throws IOException {
    BufferedReader inData;
    String str = "";
    int codeOfLine,i,j, k=0;
    double curMjd = timeMap[k];

    try {
        inData=new BufferedReader(new FileReader(datafile));
    } catch (FileNotFoundException e){
        throw new IOException("File "+datafile+" not found.");
    } catch (IOException e){
        throw new IOException("IO error with "+datafile+" datafile.");
    }

    while(inData.ready() && k<timeMap.length) {
       str=inData.readLine();    // Reading 1st record
       if (str == null) break;
       if(str.length()<40)
          continue;              //Just skipping bad data lines
       codeOfLine=(int)getStringCode(str); // Now str is a substring       

       if(codeOfLine>=1 && codeOfLine<=MaxSatsNum){   //Satellite ID (nsatin)
          ClusterRecord cR=new ClusterRecord();
          cR.nsatin=codeOfLine;  // Set up number of satellite (nsatin)!!!
          str=inData.readLine(); // Reading 2nd record: 200+satin ...
          if(cR.set2ndRecord(str)!=0)
             continue;   //Just skipping bad data lines
          if(curMjd<cR.dayBeg || curMjd>cR.dayEnd)
             continue;   //Not required record!
          str=inData.readLine(); // Reading 3rd record: 30X ...
          if(cR.set3rdRecord(str)!=0)
             continue;   //Just skipping bad data lines
          for(i=1,j=0;i<=cR.koeff;++i){ //Reading data lines
             str=inData.readLine();
             if(cR.setDataRecord(i,str)!=0){
                j=1;
                break; //Just skipping bad data lines
             }
             
          }
          if(i<cR.koeff || j!=0)
             continue;   //Just skipping bad data lines

          //Treatment:
          while (curMjd<=cR.dayEnd || k<timeMap.length) {  //Treatment of MJDs as much as possible.
             curMjd = timeMap[k];
             double[] posAndVel=new double[6];
             posAndVel=solveKepler(curMjd, cR);
             for(int jx=0;jx<3;jx++){
                gei_arr[k][jx] = posAndVel[jx];
                vei_arr[k][jx] = posAndVel[jx+3];
             }
             k++;
          } 
          
       } else continue;  //Just skipping bad data lines
       
    }
    inData.close();
 }

 // Output: res[0] = launchMjd, res[1]=sma - do not uses!!!
 public double[] getFirstMjd(String filename) throws IOException {
    double[] res = new double[2];
    res[0] = minMjd;
    return res;
 }

 /** Keper solver. Returns S/C position & velocity for input mjd
  * @param Mjd
  * @return double []
  */
 public static double[] solveKepler(double day,ClusterRecord x){
    double[] X=new double[6]; //Outout position (km) & velocity (km/s)
    double dmanom=(day-x.epoch)*86400.0/x.oMotin;
    double revnum=x.revEpo+dmanom/6.2831853072;
    double arin,arm,rvwam,tam,comp,b,g0=0.0,g1=0.0,bet,d=0.0,g2,g3,fx,
       gx,rx,ft,gt,daymid,scale,s,pa,p,pb;
    int i,l;

    arin=x.smAxis/x.rDist;
    arm=(x.rDist-x.smAxis)/x.smAxis;
    rvwam=(x.Y[0]*x.Y[3]+x.Y[1]*x.Y[4]+x.Y[2]*x.Y[5])*x.oMotin/(x.smAxis*x.smAxis);

    // Calc. of ECC anomaly by Newton's iteration
    tam=dmanom-rvwam;
    comp=1.0e-7+1.0e-10*Math.abs(tam);
    b=tam;

    //Iterations to solve Kepler's equation
    for(i=1;i<=15;++i){
       g0=Math.cos(b);
       g1=Math.sin(b);
       bet=tam-arm*g1+rvwam*g0;
       d=(bet-b)/(1.0+arm*g0+rvwam*g1);
       b+=d;
       //This gives the accuracy 1.0e-14 in b & g's
       if(Math.abs(d)<=comp)
          break;
    }
    g0-=d*g1;
    g1+=d*g0;
    g2=1.0-g0;
    g3=b-g1;
    fx=1.0-g2*arin;
    gx=(dmanom-g3)*x.oMotin;
    
    for(i=0;i<3;++i)
       X[i]=fx*x.Y[i]+gx*x.Y[i+3];

    rx=Math.sqrt(X[0]*X[0]+X[1]*X[1]+X[2]*X[2]);
    ft=-g1*x.smAxis*arin/(x.oMotin*rx);
    gt=1.0-g2*x.smAxis/rx;
    
    for(i=3;i<6;++i)
       X[i]=ft*x.Y[i-3]+gt*x.Y[i];

    //Check if polynomial coefs. are required
    if(x.koeff<=1)return X;
    daymid=0.5*(x.dayBeg+x.dayEnd);
    scale=4.0/(x.dayEnd-x.dayBeg);
    //Add chebyshev polynomial to kepler state vector
    s=scale*(day-daymid);
    pa=1.0;
    p=s*0.5;
    for(i=0;i<6;++i)
       X[i]+=x.Coefs[0][i]+x.Coefs[1][i]*p;

    if(x.koeff<=2)return X;
    
    for(l=2;l<x.koeff;++l){
       pb=pa;
       pa=p;
       p=s*pa-pb;

       for(i=0;i<6;++i)
          X[i]+=x.Coefs[l][i]*p;
    }
    return X; 
 }

 /** Returns first code of data string & remained substring
  * @param String from CLUSTER data file
  * @return Code of data or -1 if error
  */
 private static double getStringCode(String ss){
    StringTokenizer tok=new StringTokenizer(ss);
    String tmps=new String();
    double code;
    
    if(tok.hasMoreTokens())
       tmps=tok.nextToken();
    else return -1.0;
    code=new Double(tmps).doubleValue();
    return code;
 }

}
