/*
 * 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 demo.triplebuffering;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.image.ImageObserver;

import ilog.views.IlvGraphic;
import ilog.views.IlvGraphicUtil;
import ilog.views.IlvPoint;
import ilog.views.IlvRect;
import ilog.views.IlvTransformer;

/**
 * A graphic object showing the track of a plane.
 */
public class Track extends IlvGraphic implements ImageObserver {
  /**
   * Name of the plane.
   */
  private String name;

  /**
   * The logo of the company.
   */
  private Image image;

  /**
   * Speed of the plane.
   */
  private int speed;

  /**
   * Altitude of the plane.
   */
  private int altitude;

  /**
   * Position of the plane in manager's coordinate system.
   */
  private IlvPoint position;

  /**
   * Position of the speed vector of the plane.
   */
  private IlvPoint speedPosition;

  /**
   * Position of the destination of the plane.
   */
  private IlvPoint destination;

  /**
   * Are last positions displayed.
   */
  private boolean showLastPositions = true;

  /**
   * Are last positions displayed using alpha composition.
   */
  public static boolean transparentLastPositions = true;

  /**
   * Are last positions displayed using antialising.
   */
  public static boolean antialiasedLastPositions = true;

  /**
   * Is the speed vector displayed.
   */
  private boolean showSpeed = true;

  /**
   * Are the labels displayed.
   */
  private boolean showLabels = true;

  /**
   * Is the logo displayed.
   */
  private boolean showLogo = true;

  /**
   * Is the route of the plane displayed.
   */
  private boolean showRoute;

  /**
   * The warning distance.
   */
  private double warningDistance;

  /**
   * A cache for the width of the name.
   */
  private int nameWidth;

  /**
   * A cache for the width of the speed label.
   */
  private int speedLabelWidth;

  /**
   * A cache for the width of the altitude label.
   */
  private int altitudeLabelWidth;

  /**
   * Number of positions of the track displayed.
   */
  private static final int TRACKMAXLASTPOSITION = 6;

  /**
   * Last positions of the plane.
   */
  private IlvPoint[] lastPositions = new IlvPoint[TRACKMAXLASTPOSITION];

  /**
   * Font to display labels.
   */
  private Font font;

  /**
   * A cache for the height of the font.
   */
  private double fontHeight;

  /**
   * Colors of the plane.
   */
  private Color color, takeOffColor, landingColor;

  /**
   * The label for altitude.
   */
  private String altitudeStr = "0";

  /**
   * The label for speed.
   */
  private String speedStr = "0";

  /**
   * Highquality rendering for the logo image. Set to false because it seems
   * this is useful mainly if the image gets resized or rotated, which is not
   * the case here.
   */
  private static final boolean HIGHQUALITY_IMAGE = false;

  /**
   * Last positions drawn as rectangles.
   */
  private static final int LAST_POSITION_RECT = 0;

  /**
   * Last positions drawn as ovals.
   */
  private static final int LAST_POSITION_OVAL = 1;

  /**
   * The drawing style for showing the last positions.
   */
  private int _lastPositionDrawingStyle = LAST_POSITION_OVAL;

  /**
   * A factor used when computing alpha transparency.
   */
  private static final float LAST_POS_ALFA_FACTOR = (1.f / TRACKMAXLASTPOSITION) * .8f;

  /**
   * Distance between successive /** Creates an initializes a Track.
   */
  public Track(String name, Image image, IlvPoint position, IlvPoint destination, Font font, Color color,
      Color takeOffColor, Color landingColor) {
    this.name = name;
    this.image = image;
    setFont(font);
    this.color = color;
    this.takeOffColor = takeOffColor;
    this.landingColor = landingColor;
    init(position, destination);
  }

  /**
   * Initializes the positions.
   */
  public void init(IlvPoint position, IlvPoint destination) {
    this.position = new IlvPoint(position);
    for (int i = 0; i < TRACKMAXLASTPOSITION; ++i)
      lastPositions[i] = new IlvPoint(position);
    speedPosition = new IlvPoint(position);
    this.destination = new IlvPoint(destination);
  }

  /**
   * Returns the name of the plane.
   */
  public String getTrackName() {
    return name;
  }

  /**
   * Changes the name of the plane.
   */
  public void setTrackName(String s) {
    name = s;
    IlvRect r = IlvGraphicUtil.GetStringBounds(name, font, false);
    nameWidth = (int) r.width;
  }

  /**
   * Returns the current position of the plane.
   */
  public IlvPoint getPosition() {
    return new IlvPoint(position);
  }

  /**
   * Changes the position of the plane.
   */
  public void setPosition(double x, double y) {
    // Shift last positions
    int i;
    for (i = 0; i < TRACKMAXLASTPOSITION - 1; ++i)
      lastPositions[i].move(lastPositions[i + 1].x, lastPositions[i + 1].y);
    speedPosition.move(x, y);

    speedPosition.translate(position.x - lastPositions[TRACKMAXLASTPOSITION - 1].x,
        position.y - lastPositions[TRACKMAXLASTPOSITION - 1].y);

    lastPositions[i].move(position.x, position.y);
    position.move(x, y);
  }

  /**
   * Translates the plane's position.
   */
  public void translatePosition(double dx, double dy) {
    setPosition(position.x + dx, position.y + dy);
  }

  /**
   * Returns the destination of the plane.
   */
  public IlvPoint getDestination() {
    return new IlvPoint(destination);
  }

  /**
   * Returns the position of the speed vector of the plane.
   */
  public IlvPoint getSpeedPosition() {
    return new IlvPoint(speedPosition);
  }

  /**
   * Indicates wheither last positions are displayed.
   */
  public boolean isShowingLastPositions() {
    return showLastPositions;
  }

  /**
   * Displays or hides the last positions of the plane.
   */
  public void setShowingLastPositions(boolean value) {
    showLastPositions = value;
  }

  /**
   * Indicates wheither the speed is displayed.
   */
  public boolean isShowingSpeed() {
    return showSpeed;
  }

  /**
   * Displays or hides the speed vector of the plane.
   */
  public void setShowingSpeed(boolean value) {
    showSpeed = value;
  }

  /**
   * Indicates wheither the labels are displayed.
   */
  public boolean isShowingLabels() {
    return showLabels;
  }

  /**
   * Displays or hides the labels of the plane.
   */
  public void setShowingLabels(boolean value) {
    showLabels = value;
  }

  /**
   * Indicates wheither the logo is displayed.
   */
  public boolean isShowingLogo() {
    return showLogo;
  }

  /**
   * Displays or hides the logo of the plane.
   */
  public void setShowingLogo(boolean value) {
    showLogo = value;
  }

  /**
   * Indicates wheither the route is displayed.
   */
  public boolean isShowingRoute() {
    return showRoute;
  }

  /**
   * Displays or hides the route of the plane.
   */
  public void setShowingRoute(boolean value) {
    showRoute = value;
  }

  /**
   * Returns the speed of the plane.
   */
  public int getSpeed() {
    return speed;
  }

  /**
   * Changes the speed of the plane.
   */
  public void setSpeed(int val) {
    speed = val;
    speedStr = String.valueOf(speed);
    IlvRect r = IlvGraphicUtil.GetStringBounds(speedStr, font, false);
    speedLabelWidth = (int) r.width;
  }

  /**
   * Returns the altitude of the plane.
   */
  public int getAltitude() {
    return altitude;
  }

  /**
   * Changes the altitude of the plane.
   */
  public void setAltitude(int val) {
    altitude = val;
    altitudeStr = String.valueOf(altitude);
    IlvRect r = IlvGraphicUtil.GetStringBounds(altitudeStr, font, false);
    altitudeLabelWidth = (int) r.width;
  }

  /**
   * Returns the warning distance of the plane.
   */
  public double getWarningDistance() {
    return warningDistance;
  }

  /**
   * Changes the warning distance of the plane.
   */
  public void setWarningDistance(double val) {
    warningDistance = val;
  }

  /**
   * Returns the font of the labels.
   */
  public Font getFont() {
    return font;
  }

  /**
   * Changes the font of the labels.
   */
  public void setFont(Font font) {
    this.font = font;
    IlvRect r = IlvGraphicUtil.GetStringBounds(name, font, false);
    nameWidth = (int) r.width;
    fontHeight = r.height;

    r = IlvGraphicUtil.GetStringBounds(speedStr, font, false);
    speedLabelWidth = (int) r.width;
    r = IlvGraphicUtil.GetStringBounds(altitudeStr, font, false);
    altitudeLabelWidth = (int) r.width;
  }

  /**
   * Returns the color used during take off.
   */
  public Color getTakeOffColor() {
    return takeOffColor;
  }

  /**
   * Changes the color used during take off.
   */
  public void setTakeOffColor(Color color) {
    takeOffColor = color;
  }

  /**
   * Returns the color used when landing.
   */
  public Color getLandingColor() {
    return landingColor;
  }

  /**
   * Changes the color used when landing.
   */
  public void setLandingColor(Color pal) {
    landingColor = pal;
  }

  /**
   * Returns true if the plane has landed.
   */
  public boolean hasLanded() {
    return position.equals(destination);
  }

  /**
   * Sets the position to the destination.
   */
  public void land() {
    setPosition(destination.x, destination.y);
  }

  private static final int DELTA = 5;
  private static final int DELTAX = 10;
  private static final int DELTAY = 0;

  /**
   * Draws the track.
   */
  Override
  public void draw(Graphics g, IlvTransformer t) {
    Graphics2D dst = (Graphics2D) g;

    Color color = this.color;

    if (lastPositions[0].equals(lastPositions[1]))
      color = takeOffColor;
    else if (CloseTo(position, destination, warningDistance))
      color = landingColor;

    IlvPoint p = new IlvPoint();

    IlvPoint point = new IlvPoint(position);

    // Draw position
    if (t != null)
      t.apply(point);
    IlvRect rect = new IlvRect(point.x - DELTA, point.y - DELTA, 2 * DELTA, 2 * DELTA);

    dst.setColor(color);
    dst.drawOval((int) rect.x, (int) rect.y, (int) rect.width, (int) rect.height);

    // Draw last positions
    if (isShowingLastPositions()) {
      if (antialiasedLastPositions)
        dst.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
      double d = 2;
      for (int i = 0; i < TRACKMAXLASTPOSITION; ++i, ++d) {
        p.move(lastPositions[i].x, lastPositions[i].y);
        if (t != null)
          t.apply(p);

        rect.reshape(p.x - d, p.y - d, 2 * d, 2 * d);

        if (transparentLastPositions) {
          float alpha = .2f + i * LAST_POS_ALFA_FACTOR;
          dst.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
        }
        if (_lastPositionDrawingStyle == LAST_POSITION_RECT)
          dst.draw(rect);
        else // LAST_POSITION_OVAL (slower, but nicer)
          dst.drawOval(rect.xFloor(), rect.yFloor(), rect.widthFloor(), rect.heightFloor());
      }
      if (transparentLastPositions)
        dst.setComposite(AlphaComposite.SrcOver);
      if (antialiasedLastPositions)
        dst.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
    }

    // Draw Logo
    if (image != null && isShowingLogo()) {
      rect.move(point.x - rect.width / 2, point.y - rect.height / 2);
      IlvGraphicUtil.DrawImage(dst, rect, image, null, this, HIGHQUALITY_IMAGE);
    }

    // Draw Speed
    if (isShowingSpeed()) {
      p.move(speedPosition.x, speedPosition.y);
      if (t != null)
        t.apply(p);
      dst.drawLine((int) point.x, (int) point.y, (int) p.x, (int) p.y);
    }

    // Draw Labels
    if (isShowingLabels()) {

      double h = fontHeight;

      p.move(point.x + DELTAX, point.y);

      dst.setFont(font);

      dst.drawString(name, (int) p.x, (int) p.y);
      p.y += DELTAY + h;

      dst.drawString(speedStr, (int) p.x, (int) p.y);
      p.y += DELTAY + h;

      dst.drawString(altitudeStr, (int) p.x, (int) p.y);
    }

    // Draw the rest of the route
    if (isShowingRoute() && !hasLanded()) {
      p.move(destination.x, destination.y);
      if (t != null)
        t.apply(p);
      dst.setColor(landingColor);
      dst.drawLine((int) point.x, (int) point.y, (int) p.x, (int) p.y);
    }
  }

  /**
   * Redraws the object when the image is fully loaded. Implementation of the
   * method of ImageObserver.
   */
  Override
  public boolean imageUpdate(Image img, int flags, int x, int y, int w, int h) {
    if (img == image) {
      if ((flags & ERROR) != 0) {
        image = null;
        reDraw();
      }
      if ((flags & ALLBITS) != 0)
        reDraw();
      if ((flags & FRAMEBITS) != 0)
        reDraw();

      return (flags & (ALLBITS | ERROR)) == 0;

    } else
      return false;
  }

  /**
   * Returns the bounding box of the object.
   */
  Override
  public IlvRect boundingBox(IlvTransformer t) {
    IlvRect bbox;
    IlvPoint p = new IlvPoint(position);
    // Add Position
    if (t != null)
      t.apply(p);
    IlvRect rect = new IlvRect(p.x - DELTA, p.y - DELTA, 2 * DELTA, 2 * DELTA);
    bbox = new IlvRect(rect);
    // Add Last Positions
    int d = 2;
    if (isShowingLastPositions()) {
      for (int i = 0; i < TRACKMAXLASTPOSITION; ++i, ++d) {
        p.move(lastPositions[i].x, lastPositions[i].y);
        if (t != null)
          t.apply(p);
        rect.move(p.x - d, p.y - d);
        rect.resize((2 * d), (2 * d));
        bbox.add(rect);
      }
    }
    // Add Speed
    if (isShowingSpeed()) {
      p.move(speedPosition.x, speedPosition.y);
      if (t != null)
        t.apply(p);
      bbox.add(p);
    }
    // Add Labels
    if (isShowingLabels()) {
      int h = (int) fontHeight;
      p.move(position.x, position.y);
      if (t != null)
        t.apply(p);
      p.translate(DELTAX, 0);
      rect.move(p.x, p.y - h + d);
      rect.resize(nameWidth, h);
      bbox.add(rect);
      p.translate(0, DELTAY + h);
      rect.move(p.x, p.y - h + d);
      rect.resize(speedLabelWidth, h);
      bbox.add(rect);
      p.translate(0, DELTAY + h);
      rect.move(p.x, p.y - h + d);
      rect.resize(altitudeLabelWidth, h);
      bbox.add(rect);
    }
    // Add destination
    if (isShowingRoute()) {
      p.move(destination.x, destination.y);
      if (t != null)
        t.apply(p);
      bbox.add(p);
    }
    return bbox;
  }

  /**
   * Returns false, this object is not fully zoomable.
   */
  Override
  public boolean zoomable() {
    return false;
  }

  /**
   * Applies a transformation to the object.
   */
  Override
  public void applyTransform(IlvTransformer t) {
    t.apply(position);

    for (int i = 0; i < TRACKMAXLASTPOSITION; ++i)
      t.apply(lastPositions[i]);

    t.apply(speedPosition);
    t.apply(destination);
  }

  Override
  public IlvGraphic copy() {
    return null;
  }

  /**
   * Returns true if the specified points are close to each other.
   */
  static private boolean CloseTo(IlvPoint pos, IlvPoint dest, double limit) {
    double X = dest.x - pos.x;
    double Y = dest.y - pos.y;
    return ((X * X + Y * Y) <= limit * limit);
  }
}