/*
 * Licensed Materials - Property of Rogue Wave Software, Inc. 
 * © Copyright Rogue Wave Software, Inc. 2014, 2015 
 * © Copyright IBM Corp. 2009, 2014
 * © Copyright ILOG 1996, 2009
 * All Rights Reserved.
 *
 * Note to U.S. Government Users Restricted Rights:
 * The Software and Documentation were developed at private expense and
 * are "Commercial Items" as that term is defined at 48 CFR 2.101,
 * consisting of "Commercial Computer Software" and
 * "Commercial Computer Software Documentation", as such terms are
 * used in 48 CFR 12.212 or 48 CFR 227.7202-1 through 227.7202-4,
 * as applicable.
 */

import ilog.views.IlvGrapher;
import ilog.views.IlvManagerView;
import ilog.views.IlvPoint;
import ilog.views.maps.IlvCoordinate;
import ilog.views.maps.IlvCoordinateSystemProperty;
import ilog.views.maps.IlvDisplayPreferencesProperty;
import ilog.views.maps.IlvMapLayerTreeProperty;
import ilog.views.maps.IlvMapScaleLimiter;
import ilog.views.maps.beans.IlvJMapsManagerViewControlBar;
import ilog.views.maps.beans.IlvLayerTreePanel;
import ilog.views.maps.beans.IlvMapLayer;
import ilog.views.maps.beans.IlvMapLayerTreeModel;
import ilog.views.maps.beans.editor.IlvCoordinatePanelFactory;
import ilog.views.maps.datasource.IlvGraphicLayerDataSource;
import ilog.views.maps.datasource.IlvMapDataSourceModel;
import ilog.views.maps.datasource.IlvMapDataSourceProperty;
import ilog.views.maps.defense.symbology.app6a.IlvApp6aSymbol;
import ilog.views.maps.defense.symbology.app6a.IlvApp6aSymbologyTreeViewActions;
import ilog.views.maps.defense.terrain.IlvMapControllingPolyline;
import ilog.views.maps.defense.terrain3d.component.Ilv3DCorridor;
import ilog.views.maps.defense.terrain3d.component.Ilv3DExtrudedPolygon;
import ilog.views.maps.defense.terrain3d.component.Ilv3DHemisphere;
import ilog.views.maps.defense.terrain3d.component.Ilv3DLabel;
import ilog.views.maps.defense.terrain3d.model.Ilv3DModel;
import ilog.views.maps.defense.terrain3d.symbol.Ilv3DSymbolManager;
import ilog.views.maps.defense.terrain3d.view.Ilv3DTrajectory;
import ilog.views.maps.defense.terrain3d.view.Ilv3DView;
import ilog.views.maps.defense.terrain3d.view.IlvFlyThroughAction;
import ilog.views.maps.defense.terrain3d.view.IlvFlyThroughStyle;
import ilog.views.maps.defense.terrain3d.view.IlvMapTerrainFlyThrough;
import ilog.views.maps.format.dted.IlvRasterDTEDReader;
import ilog.views.maps.graphic.style.StyleEvent;
import ilog.views.maps.graphic.style.StyleListener;
import ilog.views.maps.raster.datasource.IlvRasterDataSourceFactory;
import ilog.views.maps.raster.datasource.IlvTiledRasterDataSource;
import ilog.views.maps.srs.coordsys.IlvGeographicCoordinateSystem;
import ilog.views.maps.symbology.swing.IlvSymbologyTreeView;
import ilog.views.sdm.IlvSDMEngine;
import ilog.views.sdm.IlvSDMException;
import ilog.views.sdm.renderer.IlvRendererUtil;
import ilog.views.sdm.renderer.IlvStyleSheetRenderer;
import ilog.views.swing.IlvJScrollManagerView;
import ilog.views.util.IlvProductUtil;
import ilog.views.util.waitcursor.IlvWaitCursorManager;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.StringTokenizer;

import javax.swing.JFrame;
import javax.swing.JSplitPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

/**
 * This class is a basic sample application that displays a 2D view and a 3D
 * view of the same DTED elevation data. A few symbols are also added to the
 * map, and automatically represented in the 3D view as well.
 *  
 */
public class Simple3DScene extends JFrame {
        
  static {
    // This sample uses JViews Maps for Defense features. When deploying an
    // application that includes this code, you need to be in possession
    // of a Rogue Wave JViews Maps for Defense Deployment license.
    IlvProductUtil.DeploymentLicenseRequired(
        IlvProductUtil.JViews_Maps_for_Defense_Deployment);
    // replace the stylesheet renderer with map class.
    IlvRendererUtil.addRendererAlias(IlvRendererUtil.getRendererAlias(IlvStyleSheetRenderer.class.getName()), ilog.views.sdm.renderer.maps.IlvMapStyleSheetRenderer.class.getName());
  }
  // members
  private IlvManagerView view;
  private Ilv3DView view3D;
  private IlvSDMEngine symbology;
  private JSplitPane p;
  private IlvApp6aSymbol moving, ground1, ground2;
  private Font font = new Font("Times New Roman", Font.ITALIC,16);         //$NON-NLS-1$
  
  /**
   * Convenience method to resolve URLs
   * @param resource 
   * @return URL
   */
  private static URL findURL(String resource) {
    URL rtnURL = null;
    if (new File(resource).exists()) {
      try {
        rtnURL = new File(resource).toURI().toURL();
      } catch (Exception e) {
        e.printStackTrace();
      }
    } else {
      rtnURL = Simple3DScene.class.getResource(resource);
    }
    return rtnURL;
  }
  
  /**
   * Constructor
   */
  private Simple3DScene() {
        setTitle("Sample 3D application"); //$NON-NLS-1$
        
    getContentPane().setLayout(new BorderLayout());
    // create and configure manager view
    view = new IlvManagerView();
    view.setManager(new IlvGrapher());
    view.setBackground(new Color(168, 207, 247));
    view.setSize(480, 480);
    view.setKeepingAspectRatio(true);
    view.getManager().setNamedProperty(new IlvCoordinateSystemProperty(IlvGeographicCoordinateSystem.WGS84));
    IlvMapDataSourceModel dsm = IlvMapDataSourceProperty.GetMapDataSourceModel(view.getManager());
    IlvMapLayerTreeModel ltm = IlvMapLayerTreeProperty.GetMapLayerTreeModel(view.getManager());
    // limit zoom in/out
    IlvMapScaleLimiter limiter = new IlvMapScaleLimiter(1, (float) (1 / 1E9));
    limiter.setView(view);
    
    // create DTED reader
    IlvRasterDTEDReader r = new IlvRasterDTEDReader();
    try {
        // add DTED file
        r.addMap(findURL("data/N45.DT1"));//$NON-NLS-1$
    } catch (IOException e) {
        // ignore
    }
    // Create a data source from the DTED reader, and insert it in the data
    // source model of the map
    IlvTiledRasterDataSource ds = IlvRasterDataSourceFactory.buildTiledImageDataSource(view.getManager(), r, true, false, null);
    dsm.insert(ds);
    ltm.addChild(null, ds.getInsertionLayer());
    ds.getInsertionLayer().setName("DTED N45.DT1"); //$NON-NLS-1$
    ds.getInsertionLayer().getStyle().setVisibleIn3DView(true);
    ds.start();
    // create symbols.
    moving = new IlvApp6aSymbol("SFAPMHR----CUS-", Math.toRadians(10.5), Math.toRadians(45.5)); //$NON-NLS-1$
    ground1 = new IlvApp6aSymbol("SFAPMHR----CUS-", Math.toRadians(10.86), Math.toRadians(45.9));//$NON-NLS-1$
    ground2 = new IlvApp6aSymbol("SHGPEWMAS-FA--A", Math.toRadians(10.875), Math.toRadians(45.885));//$NON-NLS-1$
    
    
    // add symbols on the map and start the map loading process
    buildSymbology(view);
    //  build the 3D view.
    build3DScene();
    
    buildFlyThrough(view);
    // configure GUI
    IlvJScrollManagerView scroll = new IlvJScrollManagerView(view);
    scroll.setPreferredSize(view.getSize());
    p = new JSplitPane(JSplitPane.VERTICAL_SPLIT, scroll, view3D);
    IlvJMapsManagerViewControlBar toolbar = new IlvJMapsManagerViewControlBar();
    toolbar.setView(view);
    getContentPane().add(toolbar, BorderLayout.NORTH);
    getContentPane().add(p, BorderLayout.CENTER);
    IlvLayerTreePanel pp=new IlvLayerTreePanel();pp.setView(view);
    getContentPane().add(pp,BorderLayout.EAST);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    pack();
  }
  private void buildFlyThrough(IlvManagerView view1) {
        // Retrieve some global properties of the manager.
    IlvMapDataSourceModel dsm = IlvMapDataSourceProperty.GetMapDataSourceModel(view1.getManager());
    IlvMapLayerTreeModel ltm = IlvMapLayerTreeProperty.GetMapLayerTreeModel(view1.getManager()); 
    // Create a data source to store the fly through polygon.
    IlvGraphicLayerDataSource ds = new IlvGraphicLayerDataSource();
    ds.setManager(view1.getManager());
    // insert the data source into the manager's data source tree.
    dsm.insert(ds);   
    // set its layer up.
    final IlvMapLayer layer = ds.getInsertionLayer();
    layer.setAllowingMoveObjects(true);
    layer.setName("Fly Through"); //$NON-NLS-1$
    IlvFlyThroughStyle flyThroughStyle=new IlvFlyThroughStyle();    
    flyThroughStyle.setAltitude(100);
    flyThroughStyle.setSpeed(100);
    layer.setStyle(flyThroughStyle);
    // insert the layer on the manager's layer tree
    ltm.addChild(null, layer);
    // Create a fly through graphical object.
    IlvPoint points[];
    points = new IlvPoint[] {
                new IlvPoint((float)Math.toRadians(10.2), -(float)Math.toRadians(45.2)),
                new IlvPoint((float)Math.toRadians(10.2), -(float)Math.toRadians(45.3)),
                new IlvPoint((float)Math.toRadians(10.5), -(float)Math.toRadians(45.4)),
                new IlvPoint((float)Math.toRadians(10.8), -(float)Math.toRadians(45.8)),
    };
    final IlvMapTerrainFlyThrough flyThroughGraphics = new IlvMapTerrainFlyThrough(points);
    flyThroughGraphics.setStyle(layer.getStyle());
    // add a listener to register a new fly through action on the 3D view when the polygon changes.
    flyThroughGraphics.addChangeListener(new IlvMapControllingPolyline.ComputeListener() {
      public void computationDone(IlvMapControllingPolyline tc) {
        registerFlyThroughAction(tc,layer);
      }
    });
    // add some control that updates the fly through when layer style changes.
    flyThroughStyle.addStyleListener(new StyleListener() {
       public void styleChanged(StyleEvent event) {
        if (event.getAttribute().equals(IlvFlyThroughStyle.SPEED)
            ||event.getAttribute().equals(IlvFlyThroughStyle.HEIGHT)) {
          registerFlyThroughAction(flyThroughGraphics,layer);
        }
     }
    });
    // Add the fly through graphical object to the data source.
    ds.add(flyThroughGraphics,IlvGeographicCoordinateSystem.KERNEL);
     // Start the data source to transform the polygon and display it on the view.
    try {
      ds.start();
    } catch (Exception e) {
      e.printStackTrace();
    }
    // register initial action:
    registerFlyThroughAction(flyThroughGraphics,layer);
  }
  

  void registerFlyThroughAction(IlvMapControllingPolyline flyThroughGraphics,IlvMapLayer layer) {
    // retrieve the fly through coordinates:
    IlvCoordinate []coords=flyThroughGraphics.getLatLongs();
    // retrieve other parameters in the object style
    IlvFlyThroughStyle ftstyle=(IlvFlyThroughStyle)flyThroughGraphics.getStyle();  
    
    // create a fly through trajectory
    Ilv3DTrajectory.Point [] trajPoints=new Ilv3DTrajectory.Point[coords.length];
    for (int i = 0; i < coords.length; i++) {
        Ilv3DTrajectory.Point lookat = null;
        if (i< (coords.length-1)) {
                lookat = new Ilv3DTrajectory.Point(ftstyle.getSpeed(),coords[i+1].x,coords[i+1].y,ftstyle.getAltitude()*0.9);
        }
      trajPoints[i]=new Ilv3DTrajectory.Point(ftstyle.getSpeed(),coords[i].x,coords[i].y,ftstyle.getAltitude(),lookat);
      
    }
    Ilv3DTrajectory traj= new Ilv3DTrajectory(trajPoints);
    // Create the fly through action and register it
    IlvFlyThroughAction action = new IlvFlyThroughAction(layer.getName(), traj,view3D);
    view3D.registerFlyThrough(layer,action);   
  }
  /**
   * This method adds a few symbols on the map
   * @param v 
   */
  private void buildSymbology(IlvManagerView v) {
    try {
      URL symbolCss = findURL("data/app6.css");//$NON-NLS-1$
      symbology = new IlvSDMEngine();
      symbology.setReferenceView(v);
      symbology.setHighlightingSelection(false);
      symbology.setGrapher((IlvGrapher) v.getManager());
      symbology.setStyleSheets(new String[] { symbolCss.toString() });
      symbology.getModel().setAdjusting(true);
      symbology.getModel().addObject(moving, null, null);
      symbology.getModel().setObjectProperty(moving, IlvApp6aSymbol.MODIFIER_ALTITUDE_OR_DEPTH, "1000");//$NON-NLS-1$
      symbology.getModel().setObjectProperty(moving, IlvApp6aSymbol.MODIFIER_DIRECTION_OF_MOVEMENT_INDICATOR, "100");//$NON-NLS-1$
      symbology.getModel().addObject(ground1, null, null);
      symbology.getModel().addObject(ground2, null, null);
      symbology.getModel().setAdjusting(false);
    } catch (IlvSDMException e) {
      e.printStackTrace();
    }
    //          show symbology panel
    IlvSymbologyTreeView symbPanel = new IlvSymbologyTreeView(symbology);
    IlvApp6aSymbologyTreeViewActions app6Actions = new IlvApp6aSymbologyTreeViewActions();
    app6Actions.setLatLonPicker(new IlvCoordinatePanelFactory.CoordPointInputPanel(v, IlvDisplayPreferencesProperty.GetDisplayPreferences(v.getManager()).getCoordinateFormatter()));
    symbPanel.setSymbologyTreeViewActions(app6Actions);
    getContentPane().add(symbPanel, BorderLayout.WEST);
    Timer timer = new Timer(20, new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        long t = System.currentTimeMillis();
        double lon = Math.toRadians(10.5 + 0.2 * Math.cos(t / 5000.0));
        double lat = Math.toRadians(45.5 + 0.2 * Math.sin(t / 5000.0));
        symbology.getModel().setAdjusting(true);
        symbology.getModel().setObjectProperty(moving, "longitude", new Double(lon)); //$NON-NLS-1$
        symbology.getModel().setObjectProperty(moving, "latitude", new Double(lat)); //$NON-NLS-1$
        symbology.getModel().setAdjusting(false);
      }
    });
    timer.start();
   }
  /**
   * this method monitors performs 3D view creation when the data source.
   *  
   */
  void build3DScene() {
    view.fitTransformerToContent();
    view.repaint();
    Ilv3DModel model3D = new Ilv3DModel(view.getManager());
//    model3D.setKeepElevationDataCache(false);
    // build 3D terrain for specified region
    
    model3D.buildTerrain(Math.toRadians(10), Math.toRadians(45), Math.toRadians(11), Math.toRadians(46));
    
    
    URL url = findURL("data/zones.txt"); //$NON-NLS-1$
    createExtrudedPolygons(model3D,url);
    
    // create 3D hemisphere (radius in meters)
    Ilv3DHemisphere hemiSphere1 = new Ilv3DHemisphere(ground1.getLongitude(), ground1.getLatitude(), 0, 1000);
    hemiSphere1.setColor(new Color(0f, 0.5f, 0, 0.3f));
    hemiSphere1.setLonLatSteps(40);
    hemiSphere1.setElevationSteps(40);
    model3D.add3DComponent(hemiSphere1);
    // create 3D hemisphere (radius in meters)
    Ilv3DHemisphere hemiSphere2 = new Ilv3DHemisphere(ground2.getLongitude(), ground2.getLatitude(), 0, 1000);
    hemiSphere2.setColor(new Color(1.0f, 0.3f, 0.3f, 0.5f));
    hemiSphere2.setLonLatSteps(20);
    hemiSphere2.setElevationSteps(20);
    model3D.add3DComponent(hemiSphere2);
    
    
    // corridor coordinates
    final double[] longs;
    final double[] lats;
    final double[] elevs;
    final double[] radiuses;    
    longs = new double[] {Math.toRadians(10.57), Math.toRadians(10.62), Math.toRadians(10.68),Math.toRadians(10.79), ground2.getLongitude()};
    lats = new double[] {Math.toRadians(45.53), Math.toRadians(45.6), Math.toRadians(45.65),Math.toRadians(45.75), ground2.getLatitude()};
    elevs = new double[] { 3000, 2000, 1200, 500, 250 };
    radiuses = new double[] { 2000, 1500, 1000, 500, 200 }; // in meters
    
   // create 3D corridor
    Ilv3DCorridor corridor = new Ilv3DCorridor(longs, lats, elevs, radiuses);
    corridor.setStepsAlongAxis(1);
    corridor.setStepsAboutAxis(20);
    corridor.setColor(new Color(0.3f, 0.3f, 0.6f, 0.5f));
    model3D.add3DComponent(corridor);   
    
    // create 3D labels    
    addLabelForSymbol(ground1,font,model3D);
    addLabelForSymbol(ground2,font,model3D);

    
    // create 3D view
    view3D = new Ilv3DView(model3D);
    view3D.setUseLighting(true);
    view3D.setPreferredSize(new Dimension(640, 400));
    
    // create a 3D symbol manager, and set it on the 3D model
    Ilv3DSymbolManager symbolManager = new Ilv3DSymbolManager(symbology, view3D);
    model3D.setSymbolManager(symbolManager);
    
  }
  
  
  
  private void createExtrudedPolygons(Ilv3DModel model3D, URL url) {
        try {
                InputStreamReader isr = new InputStreamReader(url.openStream());
                BufferedReader buf = new BufferedReader(isr);
                while(true) {
                        String label = buf.readLine();
                        if (label == null) {
                                // no more lines to read
                                break;
                        }                       
                        if (label.length() == 0) {
                                continue;
                        }
                        String color = buf.readLine();
                        String line = buf.readLine();                   
                        
                        StringTokenizer st = new StringTokenizer(line," ,"); //$NON-NLS-1$
                        
                        int count = st.countTokens() / 2;
                        double[] xi = new double[count];
                        double[] yi = new double[count];
                        double centerX = 0;
                        double centerY = 0;
                        for (int i = count-1; i >=0; i--) {
                                String lon = st.nextToken();
                                String lat = st.nextToken();
                                xi[i] = Double.parseDouble(lon);
                                yi[i] = Double.parseDouble(lat);
                                centerX += xi[i];
                                centerY += yi[i];
                        }
                        
                        // create extruded polygon
                        double altitude = 500;
                        Ilv3DExtrudedPolygon polygon = new Ilv3DExtrudedPolygon(xi,yi,0,400);
                        
                        st = new StringTokenizer(color,","); //$NON-NLS-1$
                        int[] colorElements = new int[]{128,128,128,255};
                        int i=0;
                        while (st.hasMoreElements()) {
                                colorElements[i] = Integer.parseInt(st.nextToken());
                                              i++;                                      
                                }
                        
                        polygon.setColor(new Color(colorElements[0],colorElements[1],colorElements[2],colorElements[3]));
                        model3D.add3DComponent(polygon);
                        
                        Ilv3DLabel label1 = new Ilv3DLabel(centerX/count,centerY/count, altitude +20,
                                        label,font);
                        label1.setHorizontalOffset(10);
                        label1.setVerticalOffset(10);
                        label1.setOutlineWidth(3.0f);
                        model3D.add3DComponent(label1);
                }
        } catch (IOException e) {
                // ignore
        }
        }
  
  private void addLabelForSymbol(IlvApp6aSymbol symbol, Font symbolfont, Ilv3DModel model3D) {
          double groundHeight = model3D.getGroundHeightAt(symbol.getLongitude(), -symbol.getLatitude());
          String alt = (String)symbol.getProperty( IlvApp6aSymbol.MODIFIER_ALTITUDE_OR_DEPTH);
          double altitude = 0;
          if (alt != null) {
                  altitude = Double.parseDouble(alt);
          } else {
                  alt = "0"; //$NON-NLS-1$
          }
          Ilv3DLabel label1 = new Ilv3DLabel(symbol.getLongitude(),symbol.getLatitude(), altitude + 1000 + groundHeight,
                                                                                "Alt = " + alt,symbolfont); //$NON-NLS-1$
          label1.setHorizontalOffset(20);
          label1.setVerticalOffset(10);
          label1.setOutlineWidth(3.0f);
          model3D.add3DComponent(label1);
  }

  /**
   * Main method
   * @param args  unused parameter.
   */
  public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                        IlvWaitCursorManager.init(); // magically fixes the 'unable to set pixel format ...' jogl exception by pushing a new EventQueue 
                        Simple3DScene frame = new Simple3DScene();
                        frame.setVisible(true);
                }
        });
  }
}