/*
 * 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.sdm.IlvSDMEngine;
import ilog.views.sdm.model.IlvDefaultSDMNode;

/**
 * Abstract class for symbols moving on the map.
 * 
 */
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 IlvDefaultSDMNode symbol;
  final IlvSDMEngine symbology;
  boolean wrecked = false;
  int status = 0;

  /**
   * Returns the symbol used to display this object.
   * 
   * @return the icon of the object
   */
  public IlvDefaultSDMNode getSymbol() {
    return symbol;
  }

  /**
   * 
   * @param tag
   * @param sym
   * @param id
   */
  Mobile(String tag, IlvSDMEngine sym) {
    symbol = new IlvDefaultSDMNode(tag);
    lastStatusUpdateTime = System.currentTimeMillis() - 40000;
    this.symbology = sym;
    symbol.setProperty("status", "" + status); //$NON-NLS-1$ //$NON-NLS-2$
    sym.getModel().addObject(symbol, null, null);
    allMobiles.add(this);
  }

  private double horizontalSpeed;
  private double verticalSpeed;
  private double direction;
  /**
   * 
   */
  protected 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;
  }

  static double getLongitude(IlvDefaultSDMNode node) {
    Double lon = (Double) node
        .getProperty(/* JGP TEMP IlvMapRenderer.LONGITUDE_PROPERTY */"longitude");//$NON-NLS-1$
    return lon.doubleValue();
  }

  static double getLatitude(IlvDefaultSDMNode node) {
    Double lat = (Double) node
        .getProperty(/* JGP TEMP IlvMapRenderer.LATITUDE_PROPERTY */"latitude");//$NON-NLS-1$
    return lat.doubleValue();
  }

  /**
   * 
   * @param node
   * @param engine
   * @param lon
   * @param lat
   */
  protected void setLocation(IlvDefaultSDMNode node, IlvSDMEngine engine, double lon, double lat) {
    if (engine == null) {
      node.setProperty(/* JGP TEMP IlvMapRenderer.LONGITUDE_PROPERTY */"longitude", Double.valueOf(lon));//$NON-NLS-1$
      node.setProperty(/* JGP TEMP IlvMapRenderer.LATITUDE_PROPERTY */"latitude", Double.valueOf(lat));//$NON-NLS-1$
    } else {
      boolean ia = engine.getModel().isAdjusting();
      if (!ia)
        engine.getModel().setAdjusting(true);
      engine.getModel().setObjectProperty(node, "longitude", Double.valueOf(lon)); //$NON-NLS-1$
      engine.getModel().setObjectProperty(node, "latitude", Double.valueOf(lat));//$NON-NLS-1$
      if (!ia)
        engine.getModel().setAdjusting(false);
    }
  }

  private long lastStatusUpdateTime;
  private long timeWhenPaused = -1;
  private long timeWhenResumed = -1;

  /**
   * 
   */
  protected void updateStatus() {
    long currentTime = System.currentTimeMillis();
    if (paused) {
      if (timeWhenPaused == -1) {
        timeWhenPaused = currentTime;
        timeWhenResumed = -1;
      }
    } else {
      if (timeWhenResumed == -1) {
        timeWhenResumed = currentTime;
        lastStatusUpdateTime += (timeWhenResumed - timeWhenPaused);
        timeWhenPaused = -1;
      }
    }
    if (!paused) {
      long diff = currentTime - lastStatusUpdateTime;
      double timeDifference = diff * SimulationContext.getTimeAcceleration();
      double decision = timeDifference / (timeDifference + 20000 * (Math.random() + 0.5));
      if (decision > 0.8) {
        // change status
        decision = Math.random();
        int newStatus = 0; // (might remain the same)
        if (decision > 0.8) {
          newStatus = 2;
        } else if (decision > 0.5) {
          newStatus = 1;
        }
        if (newStatus != status) {
          setSymbolProperty("status", "" + newStatus, true); //$NON-NLS-1$ //$NON-NLS-2$
          status = newStatus;
          lastStatusUpdateTime = currentTime;
        }
      }
    }
  }

  /**
   * Called by the scenario to update the location of the target.
   * 
   * @see Mobile#updateLocation()
   */
  public void updateLocation() {
    if (isWrecked()) {
      return;
    }
    double olon = getLongitude(symbol);
    double olat = getLatitude(symbol);
    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);
      setLocation(symbol, symbology, gc.getLongitude2(), gc.getLatitude2());
      // simulate some altitude change
      double dh = getVerticalSpeed() * dt;
      double altitude = getAltitude();
      setSymbolProperty(MODIFIER_ALTITUDE_OR_DEPTH, (int) (altitude + dh) + " ft", false); //$NON-NLS-1$
      setSymbolProperty(MODIFIER_DIRECTION_OF_MOVEMENT_INDICATOR, "" + Math.toDegrees(getDirection() - Math.PI / 2), //$NON-NLS-1$
          false);
      if (!ia) {
        symbology.getModel().setAdjusting(false);
      }
    }
    lastUpdateTime = updateTime;
    updateStatus();
  }

  /**
   * 
   */
  protected static final String MODIFIER_ALTITUDE_OR_DEPTH = "ALTITUDE_OR_DEPTH";//$NON-NLS-1$
  /**
   * 
   */
  protected static final String MODIFIER_DIRECTION_OF_MOVEMENT_INDICATOR = "DIRECTION_OF_MOVEMENT_INDICATOR";//$NON-NLS-1$
  /**
   * 
   */
  protected static final String MODIFIER_LOCATION = "LOCATION";//$NON-NLS-1$
  /**
   * 
   */
  protected static final String MODIFIER_SPEED = "SPEED";//$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 #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(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;
}