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

import ilog.views.maps.IlvGeodeticComputation;
import ilog.views.maps.IlvLinearUnit;
import ilog.views.maps.defense.symbology.app6a.IlvApp6aSymbol;
import ilog.views.sdm.IlvSDMEngine;
import java.lang.reflect.InvocationTargetException;
import java.text.ParseException;
import java.util.TimerTask;
import java.util.Vector;
import javax.swing.SwingUtilities;

/**
 * Abstract class for symbols moving on the map.
 * 
 * @see Missile
 * @see Target
 * 
 */
public abstract class Mobile {
        static boolean paused = false;
        private static final class MobileTask extends TimerTask {
                public void run() {
                        try {         
                                SwingUtilities.invokeAndWait(new Runnable() {                                   
                                        public void run() {
                                                IlvSDMEngine symbology = null;
                                                if (allMobiles.size() > 0) {
                                                        Mobile m = (Mobile) allMobiles.get(0);
                                                        symbology = m.symbology;
                                                }
                                                if (symbology != null)
                                                        symbology.getModel().setAdjusting(true);
                                                for (int i = allMobiles.size() - 1; i >= 0; i--) {
                                                        synchronized (allMobiles) {
                                                                if (i >= allMobiles.size())
                                                                        continue;
                                                                Mobile m = (Mobile) allMobiles.get(i);
                                                                m.updateLocation();
                                                                
                                                        }
                                                }
                                                if (symbology != null)
                                                        symbology.getModel().setAdjusting(false);
                                        }}); 
                        } catch (InterruptedException e) {
                                e.printStackTrace();
                        } catch (InvocationTargetException e) {
                                e.printStackTrace();
                        }
                }

                /**
                 * Prevents changes in location
                 */
                public void pause() {
                        paused = true;                  
                }

        /**
         * Allows changes in location
         */
                public void resume() {
                        paused = false;                 
                }
  }
  final static Vector allMobiles = new Vector();
  /**
   * The symbol used to display an icon on the object.
   */
  final protected IlvApp6aSymbol symbol;
  final IlvSDMEngine symbology;
  boolean wrecked = false;
  /**
   * Returns the symbol used to display this object.
   * 
   * @return the icon of the object
   */
  public IlvApp6aSymbol getSymbol() {
    return symbol;
  }
  Mobile(String tag, IlvSDMEngine sym, String id) {
    if (tag == null) {
      symbol = new IlvApp6aSymbol(id, 0, 0);
    } else {
      symbol = new IlvApp6aSymbol(tag, id, 0, 0);
    }
    this.symbology = sym;
    sym.getModel().addObject(symbol, null, null);
//    if (allMobiles.size() == 0)
//      Mobile.start();
    // allTimer.start();
    allMobiles.add(this);
  }
  private double horizontalSpeed;
  private double verticalSpeed;
  private double direction;
  private long lastUpdateTime;
  long getLastUpdateTime() {
    return lastUpdateTime;
  }
  double getHorizontalSpeed() {
    return horizontalSpeed;
  }
  double getVerticalSpeed() {
    return verticalSpeed;
  }
  double getDirection() {
    return direction;
  }
  void setDirection(double direction) {
    this.direction = direction;
  }
  void setHorizontalSpeed(double horizontalSpeed) {
    this.horizontalSpeed = horizontalSpeed;
  }
  void setVerticalSpeed(double verticalSpeed) {
    this.verticalSpeed = verticalSpeed;
  }
  
  /**
   * Called by the scenario to update the location of the target.
   * 
   * @see Mobile#updateLocation()
   */
  public void updateLocation() {
    if (isWrecked()) {
      return;
    }
    double olon = symbol.getLongitude();
    double olat = symbol.getLatitude();
    long updateTime = System.currentTimeMillis();
    double dt;
    if (!paused) {      
        if (lastUpdateTime == 0) {
                dt = 0;
        } else {
                dt = (updateTime - lastUpdateTime) / 1000.0;
        }
        dt *= SimulationContext.getTimeAcceleration();
        // distance ran.
        double dist = dt * getHorizontalSpeed();
        // compute new lat/lon
        IlvGeodeticComputation gc = new IlvGeodeticComputation();
        gc.setLatitude1(olat);
        gc.setLongitude1(olon);
        gc.setDistance(dist);
        gc.setForwardAzimuth(getDirection());
        gc.computeGeodeticForward();
        lastUpdateTime = updateTime;
        // update location
        boolean ia=symbology.getModel().isAdjusting();
        if (!ia)symbology.getModel().setAdjusting(true);
        symbol.setLocation(symbology, gc.getLongitude2(), gc.getLatitude2());
        // simulate some altitude change 
        double dh = getVerticalSpeed() * dt;
        double altitude = getAltitude();
        setSymbolProperty(IlvApp6aSymbol.MODIFIER_ALTITUDE_OR_DEPTH, (int)(altitude + dh)+" ft", false); //$NON-NLS-1$
        // as the computing other fields take time, do not update them every cycle for all the neutral objects.
        if(symbol.getTag() != SimulationController.NEUTRAL.intern() || symbol.getProperty(SimulationController.FOLLOW) !=null){
          updateOptional();   
        }
        if(!ia)symbology.getModel().setAdjusting(false);
    }
    lastUpdateTime = updateTime;
  }
  void updateOptional()
  {
   // update direction
    setSymbolProperty(IlvApp6aSymbol.MODIFIER_DIRECTION_OF_MOVEMENT_INDICATOR, "" + Math.toDegrees(getDirection() - Math.PI / 2), false); //$NON-NLS-1$
    double altitude = getAltitude();
    if (altitude < getMinAltitude()) {
        setVerticalSpeed(Math.abs(getVerticalSpeed()));
    }
    if (altitude > getMaxAltitude()) {
        setVerticalSpeed(-Math.abs(getVerticalSpeed()));
    }
    String verticalDir = "\u25B2"; // up arrow //$NON-NLS-1$
    if (getVerticalSpeed() < 0)
        verticalDir = "\u25BC";// down arrow //$NON-NLS-1$
    setSymbolProperty(IlvApp6aSymbol.MODIFIER_LOCATION, verticalDir, false);
    double ms = getHorizontalSpeed();
    ms = ms * IlvLinearUnit.MI.fromMeters(1) * 3600;
    setSymbolProperty(IlvApp6aSymbol.MODIFIER_SPEED, ((int) ms)+" mph", true); //$NON-NLS-1$
    
  }
  /**
   * Returns the minimum altitude the mobile should reach in the simulation.
   * 
   * @return the minimum altitude (1000 by default)
   */
  public double getMinAltitude() {
    return 1000;
  }
  /**
   * Returns the maximum altitude the mobile should reach in the simulation.
   * 
   * @return the maximum altitude (3000 by default)
   */
  public double getMaxAltitude() {
    return 3000;
  }
  /**
   * Called when the target is hit by a missile.
   */
  public final void wreck() {
    allMobiles.remove(this);
    symbology.getModel().removeObject(symbol);
  }
  /**
   * Retrieves the altitude from the
   * {@link IlvApp6aSymbol#MODIFIER_ALTITUDE_OR_DEPTH} property of the symbol.
   * 
   * @return the altitude found (or 2000).
   */
  public double getAltitude() {
    double altitude = 2000;
    String sh2 = (String) getSymbol().getProperty(IlvApp6aSymbol.MODIFIER_ALTITUDE_OR_DEPTH);
    if (sh2 != null) {
      try {
        altitude = SimulationController.FS.parse(sh2).doubleValue();
      } catch (ParseException e) {// ignore
      }
    }
    return altitude;
  }
  /**
   * Sets a symbol property.
   * 
   * @param propName
   *          property name.
   * @param propValue
   *          property value.
   * @param fireChange
   *          true if this property change needs to fire the change to the
   *          engine.
   */
  protected void setSymbolProperty(String propName, String propValue, boolean fireChange) {
    if (fireChange) {
      symbology.getModel().setObjectProperty(symbol, propName, propValue);
    } else {
      symbol.setProperty(propName, propValue);
    }
  }
  boolean isWrecked() {
    return wrecked;
  }
  static java.util.Timer tt;
  static synchronized void stop() {
    if (tt != null) {
      try {
        tt.cancel();
        tt = null;
      } catch (IllegalStateException e) {
        System.err.println("cannot stop " + e);//$NON-NLS-1$
      }
    }
  }
  static synchronized void pause() {
        if (currentMobileTask != null) {
                currentMobileTask.pause();
        }
  }
  
  static synchronized void resume() {
        if (currentMobileTask != null) {
                currentMobileTask.resume();
        }
  }
  static synchronized void start() {
    stop();
    try {
      tt = new java.util.Timer();
      currentMobileTask = new MobileTask();
      tt.scheduleAtFixedRate(currentMobileTask, 1000 / SimulationContext.getTargetFPS(), 1000 / SimulationContext.getTargetFPS());
    } catch (IllegalStateException e) {
      System.err.println("cannot start " + e); //$NON-NLS-1$
    }
  }
  
  static MobileTask currentMobileTask;

}