/*
 * 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.
 */
package simulation;

import java.awt.AWTEvent;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Vector;

import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;

import ilog.views.IlvManagerView;
import ilog.views.IlvManagerViewInteractor;
import ilog.views.IlvPoint;
import ilog.views.IlvTransformer;
import ilog.views.event.InteractorChangedEvent;
import ilog.views.event.InteractorListener;
import ilog.views.interactor.IlvPermanentInteractorInterface;
import ilog.views.maps.IlvCoordinate;
import ilog.views.maps.IlvCoordinateSystemProperty;
import ilog.views.maps.srs.coordsys.IlvCoordinateSystem;
import ilog.views.maps.srs.coordsys.IlvGeographicCoordinateSystem;
import ilog.views.maps.srs.coordtrans.IlvCoordinateTransformation;
import ilog.views.maps.srs.coordtrans.IlvCoordinateTransformationException;
import ilog.views.sdm.IlvSDMEngine;
import ilog.views.sdm.event.SDMModelAdapter;
import ilog.views.sdm.event.SDMModelEvent;
import ilog.views.sdm.event.SDMModelListener;
import ilog.views.sdm.model.IlvDefaultSDMNode;
import ilog.views.sdm.model.IlvSDMNode;
import ilog.views.sdm.renderer.IlvStyleSheetRenderer;
import ilog.views.util.IlvImageUtil;

/**
 * Simulation engine class providing basic controls to choose simulation
 * parameters as wall as play, pause, stop the simulation.
 */
public class SimulationController extends JToolBar {
  final static String FOLLOW = "Follow"; //$NON-NLS-1$
  private final IlvSDMEngine symbology;
  final JButton playPauseButton = new JButton();
  final JButton stopButton = new JButton();
  final JToggleButton followSymbolButton = new JToggleButton();
  private boolean isRunning = false;
  private boolean needInit = true;
  final JComboBox<Integer> mobileCountCombo = new JComboBox<Integer>(new Integer[] { Integer.valueOf(10), Integer.valueOf(50),
      Integer.valueOf(100), Integer.valueOf(300), Integer.valueOf(1000), Integer.valueOf(1500) });
  final JComboBox<String> speedCombo = new JComboBox<String>(
      new String[] { "x1", "x10", "x25", "x50", "x75", "x100", "x250" }); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$
  final JComboBox<Integer> fpsCombo = new JComboBox<Integer>(
      new Integer[] { Integer.valueOf(5), Integer.valueOf(10), Integer.valueOf(25), Integer.valueOf(50), Integer.valueOf(100) });
  SimulationContext simulationContext;
  Icon playIcon;
  Icon pauseIcon;
  Icon stopIcon;
  Icon followSymbolIcon;
  final static int VEHICLE = 0;
  final static String VEHICLE_TAG = "Vehicle"; //$NON-NLS-1$
  final static int BOAT = 1;
  final static String BOAT_TAG = "Boat"; //$NON-NLS-1$
  final static int PLANE = 2;
  final static String PLANE_TAG = "Plane"; //$NON-NLS-1$
  final static String TYPE_PROPERTY = "type"; //$NON-NLS-1$
  FollowSymbolInteractor followInteractor = new FollowSymbolInteractor();

  /**
   * Create the replay interface.
   * 
   * @param symbology
   */
  public SimulationController(IlvSDMEngine symbology) {
    createUserInterface();
    this.symbology = symbology;
    SimulationBoxListener listener = new SimulationBoxListener();
    // add the main action listener to various toolbar buttons
    playPauseButton.addActionListener(listener);
    stopButton.addActionListener(listener);
    followSymbolButton.addActionListener(listener);
    // JV-4254
    InteractorListener interactorListener = new InteractorListener() {
      Override
      public void interactorChanged(InteractorChangedEvent event) {
        boolean isMyInteractor = (event.getNewValue() == followInteractor);
        if (followSymbolButton.isSelected() != isMyInteractor) {
          followSymbolButton.setSelected(isMyInteractor);
        }
      }
    };
    symbology.getReferenceView().addInteractorListener(interactorListener);
    // add listener to model to keep the total number of mobiles
    symbology.getModel().addSDMModelListener(new SDMModelAdapter() {
      Override
      public void objectRemoved(SDMModelEvent event) {
        if (isRunning) {
          super.objectRemoved(event);
          // find type of object and spawn a mobile of the same category
          String tag = ((IlvSDMNode) event.getObject()).getTag();
          if (VEHICLE_TAG.equals(tag)) {
            spawnMobile(VEHICLE, false);
          } else if (BOAT_TAG.equals(tag)) {
            spawnMobile(BOAT, false);
          } else if (PLANE_TAG.equals(tag)) {
            spawnMobile(PLANE, false);
          }
        }
      }
    });
  }

  private void createUserInterface() {
    speedCombo.setSelectedIndex(0);
    speedCombo.setToolTipText("Choose simulation speed factor"); //$NON-NLS-1$ (should
                                                                 // be
                                                                 // internationalized)
    SimulationContext.setTimeAcceleration(Integer.parseInt(((String) speedCombo.getSelectedItem()).substring(1)));
    speedCombo.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent e) {
        int accel = Integer.parseInt(((String) speedCombo.getSelectedItem()).substring(1));
        SimulationContext.setTimeAcceleration(accel);
      }
    });
    fpsCombo.setSelectedIndex(0);
    fpsCombo.setToolTipText("Choose target refresh rate for the display"); //$NON-NLS-1$ (should
                                                                           // be
                                                                           // internationalized)
    SimulationContext.setTargetFPS(((Integer) fpsCombo.getSelectedItem()).intValue());
    fpsCombo.addActionListener(new ActionListener() {
      Override
      public void actionPerformed(ActionEvent e) {
        SimulationContext.setTargetFPS(((Integer) fpsCombo.getSelectedItem()).intValue());
      }
    });
    Dimension buttonDim = new Dimension(26, 26);
    Dimension comboDim = new Dimension(55, 22);
    configureComponentSize(playPauseButton, buttonDim);
    playPauseButton.setSelected(false);
    add(playPauseButton);
    configureComponentSize(stopButton, buttonDim);
    add(stopButton);
    configureComponentSize(followSymbolButton, buttonDim);
    add(followSymbolButton);
    addSeparator();
    add(new JLabel("Mobiles ")); //$NON-NLS-1$ (should be internationalized)
    mobileCountCombo.setToolTipText("Choose number of mobiles"); //$NON-NLS-1$ (should
                                                                 // be
                                                                 // internationalized)
    mobileCountCombo.setSelectedIndex(1);
    configureComponentSize(mobileCountCombo, comboDim);
    add(mobileCountCombo);
    addSeparator();
    add(new JLabel("Speed ")); //$NON-NLS-1$ (should be internationalized)
    configureComponentSize(speedCombo, comboDim);
    add(speedCombo);
    addSeparator();
    add(new JLabel("Fps ")); //$NON-NLS-1$ (should be internationalized)
    configureComponentSize(fpsCombo, comboDim);
    add(fpsCombo);
    mobileCountCombo.setEnabled(true);
    // install icons
    playIcon = createIcon("right.gif"); //$NON-NLS-1$
    pauseIcon = createIcon("pause.gif"); //$NON-NLS-1$
    stopIcon = createIcon("stop.gif"); //$NON-NLS-1$
    followSymbolIcon = createIcon("target.gif"); //$NON-NLS-1$
    playPauseButton.setIcon(playIcon);
    playPauseButton.setToolTipText("Run simulation"); //$NON-NLS-1$ (should be
                                                      // internationalized)
    stopButton.setIcon(stopIcon);
    stopButton.setToolTipText("Stop simulation"); //$NON-NLS-1$ (should be
                                                  // internationalized)
    followSymbolButton.setIcon(followSymbolIcon);
    followSymbolButton.setToolTipText("Choose a symbol to follow"); //$NON-NLS-1$ (should
                                                                    // be
                                                                    // internationalized)
  }

  private void configureComponentSize(JComponent comp, Dimension buttonDim) {
    comp.setPreferredSize(buttonDim);
    comp.setMinimumSize(buttonDim);
    comp.setMaximumSize(buttonDim);
  }

  private Icon createIcon(String iconURL) {
    Image image = null;
    try {
      image = IlvImageUtil.getImageFromFile(SimulationController.class, iconURL);
    } catch (IOException e1) {
      e1.printStackTrace();
    }
    if (image != null) {
      return new ImageIcon(image);
    }
    return null;
  }

  /**
   * 
   *
   */
  private void spawnMobile(int mobileType, boolean randomLocation) {
    Mobile mobile = null;
    switch (mobileType) {
    case VEHICLE:
      mobile = new PathConstrainedMobile(VEHICLE_TAG, symbology, SimulationContext.getRandomGroundRoad(),
          randomLocation);
      mobile.setHorizontalSpeed((SimulationContext.random() * 50 + 30) * 1000 / 3600.0);
      mobile.setVerticalSpeed(0);
      mobile.setSymbolProperty(TYPE_PROPERTY, "2", true); //$NON-NLS-1$
      break;
    case BOAT:
      mobile = new PathConstrainedMobile(BOAT_TAG, symbology, SimulationContext.getRandomWaterRoad(), randomLocation);
      mobile.setHorizontalSpeed((SimulationContext.random() * 30 + 15) * 1000 / 3600.0);
      mobile.setVerticalSpeed(0);
      mobile.setSymbolProperty(TYPE_PROPERTY, "4", true); //$NON-NLS-1$
      break;
    case PLANE:
      mobile = new FlyingMobile(PLANE_TAG, symbology, randomLocation);
      mobile.setHorizontalSpeed((SimulationContext.random() * 300 + 300) * 1000 / 3600.0);
      mobile.setVerticalSpeed(0);
      mobile.setSymbolProperty(TYPE_PROPERTY, "1", true); //$NON-NLS-1$
      break;
    default:
      // unknown
      break;
    }
  }

  private final class SimulationBoxListener implements ActionListener {
    /**
     * Called when the button "Run simulation" is pressed.
     * 
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    Override
    public void actionPerformed(ActionEvent e) {
      if (e.getSource() == playPauseButton) {
        isRunning = !isRunning;
        if (isRunning) {
          if (needInit) {
            Container c = playPauseButton.getTopLevelAncestor();
            Cursor oc = null;
            if (c.isCursorSet()) {
              oc = c.getCursor();
            }
            c.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
            buildSimulation();
            // set triple buffering
            IlvStyleSheetRenderer renderer = IlvStyleSheetRenderer.getInstance(symbology);
            symbology.getReferenceView().setTripleBufferedLayerCount(renderer.getNodesLayer());
            // mobileProvider.start();
            mobileCountCombo.setEnabled(false);
            needInit = false;
            c.setCursor(oc);
          } else {
            // resume simulation
            // if (mobileProvider != null)
            // // targetProvider.start();
            Mobile.resume();
          }
          playPauseButton.setToolTipText("Pause simulation"); //$NON-NLS-1$ (should
                                                              // be
                                                              // internationalized)
          playPauseButton.setIcon(pauseIcon);
        } else {
          playPauseButton.setToolTipText("Run simulation"); //$NON-NLS-1$ (should
                                                            // be
                                                            // internationalized)
          playPauseButton.setIcon(playIcon);
          // pause simulation
          isRunning = false;
          Mobile.pause();
        }
      } else if (e.getSource() == stopButton) {
        Mobile.resume();
        isRunning = false;
        stopSimu();
        // stop triple buffering
        symbology.getReferenceView().setTripleBufferedLayerCount(0);
        mobileCountCombo.setEnabled(true);
        playPauseButton.setToolTipText("Run simulation"); //$NON-NLS-1$ (should
                                                          // be
                                                          // internationalized)
        playPauseButton.setIcon(playIcon);
        playPauseButton.setEnabled(true);
        Enumeration<?> roots = symbology.getModel().getObjects();
        Vector<Object> v = new Vector<Object>();
        while (roots.hasMoreElements()) {
          v.add(roots.nextElement());
        }
        for (int i = 0; i < v.size(); i++) {
          symbology.getModel().removeObject(v.get(i));
        }
        Mobile.allMobiles.clear();
        Mobile.currentMobileTask = null;
        needInit = true;
      } else if (e.getSource() == followSymbolButton) {
        IlvManagerView view = symbology.getReferenceView();
        if (view != null) {
          view.requestFocus();
          if (view.getInteractor() == followInteractor) {
            view.popInteractor();
          } else {
            view.pushInteractor(followInteractor);
          }
        }
      }
    }

    void stopSimu() {
      Mobile.stop();
    }

    /**
     * 
     *
     */
    void buildSimulation() {
      SimulationContext.setSeed(124);
      int mobileCount = ((Integer) (mobileCountCombo.getSelectedItem())).intValue();
      // boats.
      int boatCount = (int) (mobileCount * 0.20);
      int vehicleCount = (int) (mobileCount * 0.65);
      int planeCount = mobileCount - boatCount - vehicleCount;
      symbology.setAdjusting(true);
      for (int i = 0; i < boatCount; i++) {
        spawnMobile(BOAT, true);
      }
      for (int i = 0; i < vehicleCount; i++) {
        spawnMobile(VEHICLE, true);
      }
      for (int i = 0; i < planeCount; i++) {
        spawnMobile(PLANE, true);
      }
      symbology.setAdjusting(false);
      Mobile.start();
    }
  }

  final static java.text.DecimalFormat NS = new java.text.DecimalFormat("0 mph"); //$NON-NLS-1$
  final static java.text.DecimalFormat FS = new java.text.DecimalFormat("0 ft"); //$NON-NLS-1$

  /**
   * @author eaullas
   */
  private class FollowSymbolInteractor extends IlvManagerViewInteractor implements IlvPermanentInteractorInterface {
    // boolean permanent = false;
    /** To save cursor. */
    Cursor oldCursor = null;
    /** The cursor for this interactor. */
    Cursor myCursor = null;

    /**
     * Creates a new <code>FollowSymbolInteractor</code>.
     */
    public FollowSymbolInteractor() {
      super();
      enableEvents(AWTEvent.MOUSE_EVENT_MASK);
      myCursor = new Cursor(Cursor.CROSSHAIR_CURSOR);
    }

    Override
    public boolean isPermanent() {
      return false;
    }

    Override
    public void setPermanent(boolean permanent) {
      // this.permanent = permanent;
    }

    /**
     * This method is called when the interactor is attached to the specified
     * manager view.
     * 
     * @param v
     *          view to attach to.
     */
    Override
    protected void attach(IlvManagerView v) {
      super.attach(v);
      if (!getCursor().equals(v.getCursor())) {
        oldCursor = v.getCursor();
        v.setCursor(getCursor());
      }
    }

    /**
     * This method is called when the interactor is detached from the specified
     * manager view.
     */
    Override
    protected void detach() {
      if (oldCursor != null) {
        getManagerView().setCursor(oldCursor);
        oldCursor = null;
      }
      if (modelListener != null) {
        symbology.getModel().removeSDMModelListener(modelListener);
        modelListener = null;
      }
      modelListener = null;
      follow(null);
      super.detach();
    }

    /**
     * @return Returns the cursor used for edition.
     */
    public Cursor getCursor() {
      if (myCursor == null) {
        if (getManagerView() != null)
          return getManagerView().getCursor();
        return Cursor.getDefaultCursor();
      }
      return myCursor;
    }

    Override
    protected void processMouseEvent(MouseEvent e) {
      switch (e.getID()) {
      case MouseEvent.MOUSE_RELEASED:
        Object dataObject = symbology.getObject(new IlvPoint(e.getX(), e.getY()), getManagerView());
        // if(e.getClickCount()>1){
        // if(dataObject != null){
        // symbPanel.editSelectedSymbol();
        // }
        // }
        if (dataObject instanceof IlvDefaultSDMNode) {
          follow((IlvDefaultSDMNode) dataObject);
        } else {
          follow(null);
        }
        break;
      }
      super.processMouseEvent(e);
    }

    IlvDefaultSDMNode currentSymbol;
    SDMModelListener modelListener;

    void follow(IlvDefaultSDMNode symbol) {
      if (currentSymbol != null) {
        symbology.getModel().setObjectProperty(currentSymbol, FOLLOW, "0"); //$NON-NLS-1$
      }
      currentSymbol = symbol;
      if (currentSymbol != null) {
        symbology.getModel().setObjectProperty(currentSymbol, FOLLOW, "1");//$NON-NLS-1$
      }
      updateForCurrentSymbol();
      if (modelListener == null) {
        modelListener = new SDMModelListener() {
          Override
          public void adjustmentFinished(SDMModelEvent event) {
            if (currentSymbol != null) {
              updateForCurrentSymbol();
            }
          }

          Override
          public void dataChanged(SDMModelEvent event) {
            if (event.getObject() == currentSymbol) {
              updateForCurrentSymbol();
            }
          }

          Override
          public void linkDestinationChanged(SDMModelEvent event) {/* ignore */
          }

          Override
          public void linkSourceChanged(SDMModelEvent event) {/* ignore */
          }

          Override
          public void objectAdded(SDMModelEvent event) {/* ignore */
          }

          Override
          public void objectRemoved(SDMModelEvent event) {
            if (event.getObject() == currentSymbol) {
              currentSymbol = null;
              follow(null);
            }
          }
        };
        symbology.getModel().addSDMModelListener(modelListener);
      }
    }

    void updateForCurrentSymbol() {
      Object dir = null;
      if (currentSymbol != null) {
        dir = currentSymbol.getProperty("DIRECTION_OF_MOVEMENT_INDICATOR"); //$NON-NLS-1$
      }
      double direction = -90;
      if (dir != null) {
        direction = Double.parseDouble(dir.toString());
      }
      IlvManagerView view = getManagerView();
      IlvCoordinateSystem system = IlvCoordinateSystemProperty.GetCoordinateSystem(view.getManager());
      IlvCoordinateTransformation tr = IlvCoordinateTransformation
          .CreateTransformation(IlvGeographicCoordinateSystem.KERNEL, system);
      IlvTransformer t = view.getTransformer();
      if (currentSymbol != null) {
        IlvCoordinate c1 = new IlvCoordinate(Mobile.getLongitude(currentSymbol), -Mobile.getLatitude(currentSymbol));
        if (tr != null) {
          try {
            tr.transform(c1, c1);
          } catch (IlvCoordinateTransformationException e) {
            e.printStackTrace();
          }
        }
        IlvPoint c = new IlvPoint(c1.x, c1.y);
        t.apply(c);
        t.translate(view.getWidth() / 2 - c.x, view.getHeight() / 2 - c.y);
      }
      double angle = Math.toDegrees(Math.atan2(t.getx21(), t.getx11()));
      double dangle = -90 - direction - angle;
      t.rotate(view.getWidth() / 2, view.getHeight() / 2, dangle);
      // make sure that an almost 0 rotation is exactly 0 (performance is
      // better)
      if (Math.abs(t.getx12()) < 1E-4 && Math.abs(t.getx21()) < 1E-4) {// RAZ
        // Rotation
        // when
        // it is
        // very
        // small.
        t.setValues(t.getx11(), 0, 0, t.getx22());
      }
      view.setTransformer(t);
      view.invalidateView();
      view.repaint();
    }
  }
}