/*
 * 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.
 */

package demo.triplebuffering;

import java.net.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;

import javax.swing.*;
import javax.swing.border.*;

import ilog.views.*;
import ilog.views.swing.*;
import ilog.views.util.*;
import ilog.views.util.swing.*;

/**
 * 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 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();
    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;
    }
  }
}