/*
 * Licensed Materials - Property of Perforce Software, Inc. 
 * © Copyright Perforce Software, Inc. 2014, 2021 
 * © 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 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;

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;

/**
 * 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.
 * 
 */
SuppressWarnings("serial")
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 Perforce 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, 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( Math.toRadians(10.2), -Math.toRadians(45.2)),
        new IlvPoint(Math.toRadians(10.2), -Math.toRadians(45.3)),
        new IlvPoint(Math.toRadians(10.5), -Math.toRadians(45.4)),
        new IlvPoint(Math.toRadians(10.8), -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() {
      Override
      public void computationDone(IlvMapControllingPolyline tc) {
        registerFlyThroughAction(tc, layer);
      }
    });
    // add some control that updates the fly through when layer style changes.
    flyThroughStyle.addStyleListener(new StyleListener() {
      Override
      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() {
      Override
      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", Double.valueOf(lon)); //$NON-NLS-1$
        symbology.getModel().setObjectProperty(moving, "latitude", Double.valueOf(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.0);
        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.0);
    model3D.add3DComponent(label1);
  }

  /**
   * Main method
   * 
   * @param args
   *          unused parameter.
   */
  public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
      Override
      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);
      }
    });
  }
}