/*
 * Licensed Materials - Property of Rogue Wave Software, Inc. 
 * © Copyright Rogue Wave Software, Inc. 2014, 2016 
 * © 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.
 */

package demo.triplebuffering;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.net.URL;

import javax.swing.JApplet;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.border.BevelBorder;
import javax.swing.border.LineBorder;

import ilog.views.IlvApplyObject;
import ilog.views.IlvGraphic;
import ilog.views.IlvManager;
import ilog.views.IlvManagerView;
import ilog.views.IlvPoint;
import ilog.views.IlvRect;
import ilog.views.IlvTransformer;
import ilog.views.swing.IlvJManagerViewControlBar;
import ilog.views.util.IlvResourceUtil;
import ilog.views.util.swing.IlvSwingUtil;

/**
 * The main class <code>TripleBuffering</code>.
 */
public class TripleBuffering extends JApplet {
  static {
    // This applet is designed to run only with default resource bundle
    // and various selected other resource bundles.
    // Setting the available resource suffixes avoids that the applet
    // tries to load resource bundles for other locales over the net,
    // even if the current locale of the browser is different.
    if (IlvResourceUtil.isInApplet())
      IlvResourceUtil.setAvailableResourceSuffixes("", "_ja");
  }

  /**
   * Data structure for airline companies.
   */
  private static final AirLine[] companies = { new AirLine("AA", "images/airline1.gif"),
      new AirLine("AL", "images/airline2.gif"), new AirLine("AM", "images/airline3.gif"),
      new AirLine("CP", "images/airline4.gif"), new AirLine("CS", "images/airline5.gif"),
      new AirLine("DE", "images/airline6.gif"), new AirLine("FI", "images/airline7.gif"),
      new AirLine("IB", "images/airline8.gif"), new AirLine("IT", "images/airline9.gif"),
      new AirLine("KL", "images/airline10.gif"), new AirLine("KO", "images/airline1.gif"),
      new AirLine("QA", "images/airline2.gif"), new AirLine("SA", "images/airline3.gif"),
      new AirLine("SB", "images/airline4.gif"), new AirLine("UN", "images/airline5.gif") };

  /**
   * Data structure that records the <code>Image</code> objects for the logos of
   * the companies.
   */
  private static final Image[] companiesLogos = new Image[companies.length];

  /**
   * Number of animated tracks.
   */
  private int maxTracks = 50;

  /**
   * Array of animated tracks.
   */
  private Track[] tracks;

  /**
   * <code>true</code> when the application is run as an applet.
   */
  public boolean isApplet = true;

  /**
   * Bounding box of the background map.
   */
  private static IlvRect mapBox;

  /**
   * Delta for track movements.
   */
  private static float delta;

  /**
   * The manager.
   */
  private IlvManager manager;

  /**
   * The view of the manager.
   */
  private IlvManagerView mgrview;

  /**
   * Timer for animation.
   */
  private Timer timer;

  /**
   * Font of the track symbols.
   */
  private Font font = new Font("Helvetica", Font.PLAIN, 10);

  /**
   * The check boxes.
   */
  private JCheckBox showLabelsButton, showLogosButton, lastPositionButton;
  private JCheckBox layerCache, tripleBuffer;

  /**
   * The combo box.
   */
  private JComboBox<String> combo;

  /**
   * To indicate the performance:
   */
  private JLabel fpsLabel;

  /**
   * The layer that contains the tracks.
   */
  private int trackLayer;

  /**
   * The history of fps. We show an average fps to avoid that the ftp flickers
   * too much.
   */
  private long[] fpsHistory = new long[50];
  private int fpsHIndex = 0;
  private boolean fpsHistoryFull = false;

  /**
   * Initializes the applet.
   */
  public void init() {
    super.init();

    // This sample uses JViews Diagrammer features. When deploying an
    // application that includes this code, you need to be in possession
    // of a Rogue Wave JViews Diagrammer Deployment license.
    // IlvProductUtil.DeploymentLicenseRequired(
    // IlvProductUtil.JViews_Diagrammer_Deployment);

    // Creates the manager

    manager = new IlvManager();

    // Creates the view for the manager

    mgrview = new IlvManagerView(manager) {
      public void paint(Graphics g) {
        long time = System.nanoTime();
        super.paint(g);
        time = System.nanoTime() - time;
        if (time == 0)
          time = 1;
        long fps = 1000000000 / time;
        fpsHistory[fpsHIndex++] = fps;
        if (fpsHIndex >= fpsHistory.length) {
          fpsHistoryFull = true;
          fpsHIndex = 0;
        }
        int max = (fpsHistoryFull ? fpsHistory.length : fpsHIndex);
        long sum = 0;
        for (int i = 0; i < max; i++) {
          sum += fpsHistory[i];
        }
        fpsLabel.setText("Refresh " + (sum / max) + " fps");
      }
    };

    // Sets the keep aspect ratio on

    mgrview.setKeepingAspectRatio(true);

    // Sets the background of the view.

    // Either a simple color...
    // mgrview.setBackground(new Color(88, 210, 221));

    // ...or a nice gradient obtained via a background pattern:
    try {
      URL url = getClass().getResource("images/BackgroundPattern.gif");
      mgrview.setBackgroundPatternLocation(url);
    } catch (Throwable th) {
      System.err.println("Couldn't get file: images/BackgroundPattern.gif");
    }

    // Reads the background map.
    try {
      manager.setFileName(getClass().getResource("world.ivl"));
    } catch (Exception th) {
      System.err.println("Couldn't get file: world.ivl");
    }

    // Sets the tripple buffering on for the layers of the map.
    mgrview.setTripleBufferedLayerCount(manager.getLayersCount());

    mapBox = manager.computeBBox(null);

    delta = mapBox.width / 200;

    getContentPane().setLayout(new BorderLayout(0, 0));

    // Tracks will be added in a new layer at the top.
    trackLayer = manager.getLayersCount();

    // Creates the tracks.
    tracks = new Track[maxTracks];

    for (int i = 0; i < maxTracks; i++) {
      Track track = newRandomTrack(Color.black, Color.blue, Color.yellow);
      manager.addObject(track, trackLayer, false);
      tracks[i] = track;
    }

    // Reads the foreground clouds.
    try {
      manager.read(getClass().getResource("clouds.ivl"));
    } catch (Exception th) {
      System.err.println("Couldn't get file: clouds.ivl");
    }

    IlvGraphic template = manager.getObject("cloud");
    manager.removeObject(template, false);
    for (int i = 0; i < 100; i++) {
      manager.addObject(newRandomCloud(template), trackLayer + 1, false);
    }
    manager.removeObject(manager.getObject("background"), false);
    manager.getManagerLayer(trackLayer + 1).setAlpha(0.75F);
    mgrview.setLayerCached(trackLayer + 1, true);

    getContentPane().add(mgrview, BorderLayout.CENTER);

    // Creates a toolbar for basic zoom/unzoom operations.

    IlvJManagerViewControlBar toolbar = new IlvJManagerViewControlBar();

    toolbar.setFloatable(false);
    toolbar.setSelectButtonAvailable(false);

    toolbar.setOrientation(JToolBar.VERTICAL);
    toolbar.setView(mgrview);

    getContentPane().add(toolbar, BorderLayout.WEST);
    getContentPane().add(createControlPanel(), BorderLayout.SOUTH);

    // Create the timer.
    timer = new Timer(66, new Animation());

    // true for debugging
    Timer.setLogTimers(false);
  }

  /**
   * Creates a random point located somewhere on the map.
   */
  public IlvPoint randomPoint() {
    IlvPoint randomPt = new IlvPoint(mapBox.x + (float) (Math.random() * mapBox.width),
        mapBox.y + (float) (Math.random() * mapBox.height));
    return randomPt;
  }

  /**
   * Creates a new track.
   */
  public Track newRandomTrack(Color color, Color hilit, Color alt) {
    // Company
    int companyIndex = (int) (Math.random() * companies.length);
    AirLine company = companies[companyIndex];

    // Flight name.
    String name = company.getName() + " 0" + (100 + (int) (Math.random() * 900));

    // Logo image
    Image image = getLogoImage(companyIndex, company.getLogoFileName());

    // Creates the track.
    Track track = new Track(name, image, randomPoint(), randomPoint(), font, color, hilit, alt);

    // Random speed [250 - 1500] mph
    track.setSpeed(250 + (int) (Math.random() * 1250));

    // Random height [5000-55000] ft
    track.setAltitude(100 * (50 + (int) (Math.random() * 500)));

    track.setWarningDistance((double) (delta * 2));

    return track;
  }

  /**
   * Returns the image for the logo of a given company. The image is read only
   * once for each company name.
   */
  private Image getLogoImage(int companyIndex, String companyLogoFileName) {
    Image image = companiesLogos[companyIndex];

    if (image != null)
      // the image was already read
      return image;

    // otherwise, read the image
    try {
      URL url = getClass().getResource(companyLogoFileName);
      image = Toolkit.getDefaultToolkit().getImage(url);
    } catch (Throwable th) {
      System.err.println("Couldn't get file: " + companyLogoFileName);
    }

    // store it, to avoid reading again next time
    companiesLogos[companyIndex] = image;

    return image;
  }

  /**
   * Creates a new cloud.
   */
  public IlvGraphic newRandomCloud(IlvGraphic template) {
    IlvGraphic cloud = template.copy();
    cloud.move(randomPoint());
    return cloud;
  }

  /**
   * Starts the applet.
   */
  public void start() {
    mgrview.setTransformer(new IlvTransformer(2.5, 0, 0, 2.5, 250, 150));
    timer.start();
  }

  /**
   * Stops the applet.
   */
  public void stop() {
    timer.stop();
  }

  /**
   * Called when this applet is being reclaimed in order to destroy any
   * resources that it has allocated.
   */
  public void destroy() {
    super.destroy();

    // This method is intended to workaround memory management issues
    // in the Sun JRE. Please refer to the method documentation for more
    // details and a description of the known issues.
    IlvSwingUtil.cleanupApplet();
  }

  /**
   * Changes the number of tracks.
   */
  private void setNumberOfTracks(int number) {
    if (number == maxTracks)
      return;
    maxTracks = number;
    timer.stop();
    manager.deleteAll(trackLayer, false);
    tracks = new Track[maxTracks];

    Track.transparentLastPositions = (maxTracks <= 100);
    Track.antialiasedLastPositions = (maxTracks <= 100);

    for (int i = 0; i < maxTracks; i++) {
      Track track = newRandomTrack(Color.black, Color.blue, Color.yellow);
      manager.addObject(track, trackLayer, false);
      tracks[i] = track;
    }
    showLabelsButton.setSelected(true);
    showLogosButton.setSelected(true);
    lastPositionButton.setSelected(true);
    mgrview.repaint();
    timer.start();
  }

  /**
   * Shows or hides the last positions.
   */
  private void setShowingLastPositions(final boolean value) {
    for (int i = 0; i < maxTracks; i++)
      manager.applyToObject(tracks[i], new IlvApplyObject() {
        public void apply(IlvGraphic obj, Object arg) {
          ((Track) obj).setShowingLastPositions(value);
        }
      }, null, false);
    mgrview.repaint();
  }

  /**
   * Shows or hides the labels of tracks.
   */
  private void setShowingLabels(final boolean value) {
    for (int i = 0; i < maxTracks; i++)
      manager.applyToObject(tracks[i], new IlvApplyObject() {
        public void apply(IlvGraphic obj, Object arg) {
          ((Track) obj).setShowingLabels(value);
        }
      }, null, false);
    mgrview.repaint();
  }

  /**
   * Shows or hides the logos of tracks.
   */
  private void setShowingLogos(final boolean value) {
    for (int i = 0; i < maxTracks; i++)
      manager.applyToObject(tracks[i], new IlvApplyObject() {
        public void apply(IlvGraphic obj, Object arg) {
          ((Track) obj).setShowingLogo(value);
        }
      }, null, false);
    mgrview.repaint();
  }

  /**
   * Action listener used by the timer to animate the tracks.
   */
  private class Animation implements IlvApplyObject, ActionListener {
    public void actionPerformed(ActionEvent evt) {

      // Update the position of the tracks
      for (int i = 0; i < maxTracks; i++)
        manager.applyToObject(tracks[i], this, null, false);

      // Repaint the entire applet, not only the manager view,
      // to avoid delayed repaints of the other components of the applet
      TripleBuffering.this.repaint();
    }

    /**
     * The method that moves a track.
     */
    public void apply(IlvGraphic obj, Object arg) {
      Track track = (Track) obj;
      if (track.hasLanded())
        track.init(randomPoint(), randomPoint());
      else {
        IlvPoint destination = track.getDestination();
        IlvPoint position = track.getPosition();

        double d = (delta * (double) track.getSpeed()) / (double) 800;
        double X = destination.x - position.x;
        double Y = destination.y - position.y;
        double R2 = X * X + Y * Y;

        if (R2 <= d * d)
          track.land();
        else {
          double R = Math.sqrt(R2);
          double dx = (d * X) / R;
          double dy = (d * Y) / R;

          track.translatePosition((float) dx, (float) dy);
        }
      }
    }
  }

  /**
   * Item listener for check boxes.
   */
  private class CheckBoxListener implements ItemListener {
    public void itemStateChanged(ItemEvent e) {
      final boolean selected = e.getStateChange() == ItemEvent.SELECTED;
      Object source = e.getItemSelectable();

      if (source == tripleBuffer)
        mgrview.setTripleBufferedLayerCount(selected ? 1 : 0);
      else if (source == layerCache)
        mgrview.setLayerCached(2, selected);
      else if (source == showLabelsButton)
        setShowingLabels(selected);
      else if (source == showLogosButton)
        setShowingLogos(selected);
      else if (source == lastPositionButton)
        setShowingLastPositions(selected);
      else if (source == combo) {
        int value = 50;
        try {
          value = Integer.parseInt(e.getItem().toString());
        } catch (Exception ex) {
        }
        setNumberOfTracks(value);
      }
      fpsHIndex = 0;
      fpsHistoryFull = false;
    }
  }

  private JComponent createControlPanel() {
    JPanel p = new JPanel();
    p.setBorder(LineBorder.createGrayLineBorder());
    CheckBoxListener listener = new CheckBoxListener();

    lastPositionButton = new JCheckBox("Last Positions");
    lastPositionButton.setMnemonic(KeyEvent.VK_P);
    lastPositionButton.setSelected(true);
    lastPositionButton.addItemListener(listener);
    p.add(lastPositionButton);

    showLabelsButton = new JCheckBox("Labels");
    showLabelsButton.setMnemonic(KeyEvent.VK_L);
    showLabelsButton.setSelected(true);
    showLabelsButton.addItemListener(listener);
    p.add(showLabelsButton);

    showLogosButton = new JCheckBox("Logos");
    showLogosButton.setMnemonic(KeyEvent.VK_G);
    showLogosButton.setSelected(true);
    showLogosButton.addItemListener(listener);
    p.add(showLogosButton);

    JPanel trackNumber = new JPanel();
    combo = new JComboBox<String>();
    combo.addItem("50");
    combo.addItem("100");
    combo.addItem("300");
    combo.addItem("500");
    combo.addItemListener(listener);
    trackNumber.add(new JLabel("Tracks:"));
    trackNumber.add(combo);
    p.add(trackNumber);

    tripleBuffer = new JCheckBox("Triple Buffer");
    tripleBuffer.setMnemonic(KeyEvent.VK_B);
    tripleBuffer.setSelected(true);
    tripleBuffer.addItemListener(listener);
    p.add(tripleBuffer);

    layerCache = new JCheckBox("Layer Cache");
    layerCache.setMnemonic(KeyEvent.VK_C);
    layerCache.setSelected(true);
    layerCache.addItemListener(listener);
    p.add(layerCache);

    fpsLabel = new JLabel();
    fpsLabel.setForeground(Color.MAGENTA);
    fpsLabel.setBorder(new BevelBorder(1));
    fpsLabel.setPreferredSize(new Dimension(130, 20));
    p.add(fpsLabel);

    return p;
  }

  /**
   * A main method to run the demo as an application.
   */
  public static void main(String[] args) {
    // Sun recommends that to put the entire GUI initialization into the
    // AWT thread
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        JFrame frame = new JFrame("Triple Buffering Demo");
        frame.addWindowListener(new WindowAdapter() {
          public void windowClosing(WindowEvent e) {
            System.exit(0);
          }
        });
        TripleBuffering applet = new TripleBuffering();
        applet.isApplet = false;
        frame.getContentPane().add(applet);
        frame.setSize(790, 480);
        applet.init();
        applet.start();
        frame.setVisible(true);
      }
    });
  }

  /**
   * A data structure for airlines.
   */
  private static class AirLine {
    private String _name;
    private String _logoFileName;

    AirLine(String name, String logoFileName) {
      _name = name;
      _logoFileName = logoFileName;
    }

    String getName() {
      return _name;
    }

    String getLogoFileName() {
      return _logoFileName;
    }
  }
}