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

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


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

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

/*
 * Earth.java
 *
 * Created on March 22, 2000, 8:22 AM
 */
 
package ovt.object;

import ovt.*;
import ovt.mag.*;
import ovt.util.*;
import ovt.event.*;
import ovt.beans.*;
import ovt.datatype.*;
import ovt.interfaces.*;
import ovt.object.editor.*;
import ovt.object.vtk.ProgrammableEarthSource;

import vtk.*;

import java.io.*;
import java.lang.*;
import java.util.*;
import java.beans.*;
import javax.swing.*;


/** 
 *
 * @author  root
 * @version 
 */
public class Earth extends VisualObject implements TimeChangeListener, 
                      CoordinateSystemChangeListener {

  public static final int CONTINENTS = 0;
  public static final int GRID       = 1;
  public static final int BOTH       = 2; //added by kono
  public static final int TEXTURE    = 3; 
  
  protected vtkAssembly[] actors = new vtkAssembly[4];
  protected vtkAssembly actor = null;
  /** Holds value of property earthType. */
  protected int earthType = GRID;
  /** The type of the earth to be used in the case of no conflicts */
  protected int prefferedEarthType = TEXTURE;
  
  protected EarthTypeEditor earthTypeEditor;

  
  /** Creates new Earth */
  public Earth(OVTCore core) {
    super(core, "The Earth", OVTCore.getImagesDir() + "globe.gif");
    Log.log("Earth :: init ... ", 5);
    Descriptors descriptors = super.getDescriptors();
    try {
        BasicPropertyDescriptor pd = new BasicPropertyDescriptor("earthType", this);
        pd.setDisplayName("EarthType");
        
        earthTypeEditor = new EarthTypeEditor(pd);
        addPropertyChangeListener(earthTypeEditor);
        pd.setPropertyEditor(earthTypeEditor);
        descriptors.put(pd);
    } catch (IntrospectionException e2) {
        System.out.println(getClass().getName() + " -> " + e2.toString());
        System.exit(0);
    }
    setEarthType(TEXTURE); 
    setVisible(true);
  }
  
  private vtkAssembly createContinentsActor() {
    vtkAssembly assembly = new vtkAssembly();
    
    // create actor  
    // create sphere geometry
    vtkTexturedSphereSource tss = new vtkTexturedSphereSource();
    tss.SetRadius(1.0);
    tss.SetThetaResolution(40);
    tss.SetPhiResolution(40);

      // map to graphics library
    vtkPolyDataMapper earthMapper = new vtkPolyDataMapper();
    earthMapper.SetInput(tss.GetOutput());

    // actor coordinates geometry, properties, transformation
    vtkActor act = new vtkActor();
    act.SetMapper(earthMapper);
        
    assembly.AddPart(act);
        
    try {
        ContinentsReader cr = new ContinentsReader(OVTCore.getMdataDir()+"coastline.dat");
        vtkPolyDataMapper mapper = new vtkPolyDataMapper();
            mapper.SetInput(cr.GetOutput());
    
        act = new vtkActor();
            act.SetMapper(mapper);
            act.GetProperty().SetColor(0, 0, 0);

        
            assembly.AddPart(act);
    } catch (IOException e) {
        Log.err("Error loading coastline from "+OVTCore.getMdataDir()+"coastline.dat", 0);
    }       
       
     return assembly;
  }
  
  

  private vtkAssembly createGridActor() {
    ProgrammableEarthSource earthSource = new ProgrammableEarthSource();
    earthSource.setRadius(1);
    earthSource.setPhiResolution(5);
    earthSource.setThetaResolution(5);
      
    vtkPolyData polyData = earthSource.GetOutput();
    
    vtkThresholdPoints thresholdPoints = new vtkThresholdPoints();
      thresholdPoints.SetInput(polyData);  
      thresholdPoints.ThresholdBetween(1, 1);
 
    // create slid surface
      
    vtkPolyDataMapper mapper = new vtkPolyDataMapper();
      mapper.SetInput(polyData);
      mapper.ScalarVisibilityOff();
      
    vtkActor surfaceActor = new vtkActor();
        surfaceActor.SetMapper(mapper);
        surfaceActor.GetProperty().SetColor(0.9, 0.9, 0.9);
        //surfaceActor.SetScale(0.999);
    
    // Grid 
    
    mapper = new vtkPolyDataMapper();
      mapper.SetInput(thresholdPoints.GetOutput());
      
    vtkActor gridActor = new vtkActor();
        gridActor.SetMapper(mapper);
        gridActor.GetProperty().SetColor(0.9, 0.9, 0.9);
    
    vtkAssembly assembly = new vtkAssembly();
        assembly.AddPart(surfaceActor);
        assembly.AddPart(gridActor);
    
    return assembly;
  }

  
  private vtkAssembly createGridedContinentsActor() {
        // create actor  
        // create sphere geometry
    vtkTexturedSphereSource tss = new vtkTexturedSphereSource();
    tss.SetRadius(1.0);
    tss.SetThetaResolution(80);
    tss.SetPhiResolution(80);

        // map to graphics library
    vtkPolyDataMapper earthMapper = new vtkPolyDataMapper();
    earthMapper.SetInput(tss.GetOutput());

        // actor coordinates geometry, properties, transformation
    vtkActor act = new vtkActor();
    act.SetMapper(earthMapper);

        // load in the texture map
    vtkTexture atext = new vtkTexture();
    vtkPNMReader pnmReader = new vtkPNMReader();
    pnmReader.SetFileName(getCore().getImagesDir()+"earth_grid.pnm");
    atext.SetInput(pnmReader.GetOutput());
    atext.InterpolateOn();
    
    // this should be fixed in future
    if (!OVTCore.isServer()) act.SetTexture(atext);

    vtkAssembly assembly = new vtkAssembly();
    assembly.AddPart(act);
    
    ProgrammableEarthSource earthSource = new ProgrammableEarthSource();
    earthSource.setRadius(1);
    earthSource.setPhiResolution(5);
    earthSource.setThetaResolution(5);
      
    vtkPolyData polyData = earthSource.GetOutput();
    
    vtkThresholdPoints thresholdPoints = new vtkThresholdPoints();
      thresholdPoints.SetInput(polyData);  
      thresholdPoints.ThresholdBetween(1, 1);
 
    // create slid surface
      
    vtkPolyDataMapper mapper = new vtkPolyDataMapper();
      mapper.SetInput(polyData);
      mapper.ScalarVisibilityOff();
      
    vtkActor actor1 = new vtkActor();
    actor1.SetMapper(mapper);
    actor1.GetProperty().SetColor(.9, .9, .9);
    actor1.SetScale(0.999);
    
    // Grid 
    
    mapper = new vtkPolyDataMapper();
      mapper.SetInput(thresholdPoints.GetOutput());
      
    vtkActor act2 = new vtkActor();
    act2.SetMapper(mapper);
    act2.GetProperty().SetColor(.9, .9, .9);
    
    assembly.AddPart(act2);
    
    return assembly;
  }

  
private vtkAssembly createTextureActor() {
      // create actor  
      // create sphere geometry
    vtkTexturedSphereSource tss = new vtkTexturedSphereSource();
    tss.SetRadius(1.0);
    tss.SetThetaResolution(40);
    tss.SetPhiResolution(40);

      // map to graphics library
    vtkPolyDataMapper earthMapper = new vtkPolyDataMapper();
    earthMapper.SetInput(tss.GetOutput());

    // actor coordinates geometry, properties, transformation
    vtkActor act = new vtkActor();
    act.SetMapper(earthMapper);

    // load in the texture map
        
        vtkTexture atext;
        if (OVTCore.isServer()) {
            //atext = new vtkOpenGLOffscreenTexture();
        } else { 
            vtkPNMReader pnmReader = new vtkPNMReader();
            Log.log("Loading texture from " + getCore().getImagesDir()+"earth.ppm", 5);
        pnmReader.SetFileName(getCore().getImagesDir()+"earth.ppm");
            atext = new vtkTexture();
            atext.SetInput(pnmReader.GetOutput());
        atext.InterpolateOn();
            // avoid BUS ERROR bug in OpenGL. This should be fixed in future
        if (!OVTCore.isServer()) act.SetTexture(atext);
        }
    
        
    vtkAssembly assembly = new vtkAssembly();
    assembly.AddPart(act);
    return assembly;
  }

  private vtkAssembly getActor(int type) {
      switch (type) {
          case CONTINENTS: if (actors[CONTINENTS] == null) actors[CONTINENTS] = createContinentsActor();break;
          case GRID:  if (actors[GRID] == null) actors[GRID] = createGridActor();break;
          case BOTH:  if (actors[BOTH] == null) actors[BOTH] = createGridedContinentsActor(); break;
          case TEXTURE: if (actors[TEXTURE] == null) actors[TEXTURE] = createTextureActor(); break;
      }
      return actors[type];
  }
  
  protected void show() {
    actor = getActor(getEarthType());
    getRenderer().AddActor(actor);
    rotate();
  }
  
  protected void hide() {
    if (actor != null) {
      getRenderer().RemoveActor(actor);
      actor = null;
    }
  }
  
  public void setVisible(boolean visible) {
    if (visible != isVisible()) {
      if (visible) show();
      else hide();
      super.setVisible(visible);
    }
  }
  
  public void rotate() {
    Matrix3x3 m3x3 = getTrans(getMjd()).trans_matrix(getPolarCS(), getCS());
    actor.SetUserMatrix(m3x3.getVTKMatrix()); 
  }
  
  public void timeChanged(TimeEvent evt) {
    if (isVisible()) rotate(); 
  }

  public void coordinateSystemChanged(CoordinateSystemEvent evt) {
    
    if (isVisible()) rotate();
    
    if (evt.getWindow() == Const.POLAR) {
      // it is not possible to show earth with continents 
      // if CS is not GEO
      if (evt.getNewCS() == CoordinateSystem.GEO) {
        earthTypeEditor.setEnabled(CONTINENTS, true);
        earthTypeEditor.setEnabled(BOTH, true);
        earthTypeEditor.setEnabled(TEXTURE, true);
        setEarthType(prefferedEarthType);
      } else { // new CS != GEO
        earthTypeEditor.setEnabled(CONTINENTS, false);
        earthTypeEditor.setEnabled(BOTH, false);
        earthTypeEditor.setEnabled(TEXTURE, false);
        // if the privious CS was GEO - store earthType.
        if (evt.getOldCS() == CoordinateSystem.GEO) {
          prefferedEarthType = getEarthType();
        }
        if (getEarthType() != GRID)
            setEarthType(GRID);
      }
    }
  }

  
  /** Getter for property earthType.
   * @return Value of property earthType.
   */
  public int getEarthType() {
    return earthType;
  }
  /** Setter for property earthType.
   * @param earthType New value of property earthType.
   *
   * @throws IllegalArgumentException
   */
  public void setEarthType(int earthType) throws IllegalArgumentException {
    int oldEarthType = this.earthType;
    if ((getPolarCS() != CoordinateSystem.GEO) && (earthType == CONTINENTS))
        throw new IllegalArgumentException("Continents cannot be displayed in the " +
            CoordinateSystem.getCoordSystem(getPolarCS()) + " coordinate system");
    
    this.earthType = earthType;
    if (isVisible()) { hide(); show(); }
    propertyChangeSupport.firePropertyChange ("earthType", new Integer (oldEarthType), new Integer (earthType));
  }
  
  
  public static String getEarthType(int type) {
    switch (type) {
      case CONTINENTS:  return "Continents";
      case GRID      :  return "Grid";
      case BOTH      :  return "Continents & Grid";
      case TEXTURE   :  return "Texture";
    }
    throw new IllegalArgumentException("invalid earth type '"+type+"'");
  }

}




class ContinentsReader {
    String file;
    vtkPolyData profile = new vtkPolyData();

ContinentsReader(String file) throws IOException {
    this.file = file;
    Log.log("Loading continents from " + file + " ...", 0);  
    vtkCellArray lines = new vtkCellArray();
    vtkPoints points = new vtkPoints();
      
    double i = 0;
    
    
    String str;
      
    try {
      //DataInputStream inData = new DataInputStream(new FileInputStream(file));
      BufferedReader inData = new BufferedReader(new FileReader(file));
      double lat, lon;
      double[] r_lat_lon = new double[3];
      r_lat_lon[0] = 1;
      double[] r;
      int n_points = 0;
      int n_lines = 0;
    
      int line_start = 0;
      StringTokenizer st;
      String line;
      String s1, s2;
      while((line = inData.readLine()) != null) {
        
        if (!line.equals("nan nan")) {
            st = new StringTokenizer(line, "\t");
            s1 = st.nextToken();
            s2 = st.nextToken();
            lon = new Double(s1).doubleValue();
            lat = new Double(s2).doubleValue();
            r_lat_lon[1] = lat;
            r_lat_lon[2] = lon;
            //System.out.println(lat + "\t" + lon);
            r = Utils.sph2rec(r_lat_lon);
            points.InsertNextPoint(r[0], r[1], r[2]);
        } else {
            lines.InsertNextCell(points.GetNumberOfPoints() - line_start);
            for(int j=line_start; j<points.GetNumberOfPoints(); j++) 
            lines.InsertCellPoint(j);
            
            line_start = points.GetNumberOfPoints();
        }
    }
        
    profile.SetPoints(points);
    profile.SetLines(lines);
    inData.close();
   } catch (FileNotFoundException e){
         throw new IOException("File "+file+" not found.");
   } catch (IOException e){
         throw new IOException("IO error with "+file+" datafile.");
   } // try - catch wrong!!
   System.out.println(" done.");  
}
    
    vtkPolyData GetOutput () {
        return profile;
    }
    
}
