/*
 * 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.lang.reflect.InvocationTargetException;
import java.text.ParseException;
import java.util.TimerTask;
import java.util.Vector;

import javax.swing.SwingUtilities;

import ilog.views.maps.IlvGeodeticComputation;
import ilog.views.maps.IlvLinearUnit;
import ilog.views.maps.defense.symbology.app6a.IlvApp6aSymbol;
import ilog.views.sdm.IlvSDMEngine;

/**
 * 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 {
    Override
    public void run() {
      try {
        SwingUtilities.invokeAndWait(new Runnable() {
          Override
          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<Mobile> allMobiles = new Vector<Mobile>();
  /**
   * 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;

}