package nxm.sys.test;

import nxm.sys.inc.InternalUseOnly;
import nxm.sys.lib.BaseFile;
import nxm.sys.lib.Data;
import nxm.sys.lib.DataFile;
import nxm.sys.lib.Position;
import nxm.sys.lib.Shell;
import nxm.sys.lib.Vector3D;

/** Rotate viewer and target vectors about a vector perpendicular the viewer
 *  to create an 'orbit' type pattern
 *
 *  let vx  = The vector to be rotated (where the magnitude of vx is 0)
 *  let vx' = vx AFTER rotation (i.e. any vector perpendicular to vy)
 *  let vy  = The vector to be rotated about (where the magnitude of vy is 0)
 *  let vz  = (vx x vy)/magnitude(vy) (perpendicular to both vx and vy)
 *  let theta = angle in which to rotate (shift) vx about vy
 *
 *  vx' is perpendicular to vx. vz is also perpendicular to vx, but vz is also perpendicular to vy as well.
 *  Because vx' lies in the same place as vx and vz, vx' can be expressed as a linear combination of vx and vz:
 *    vx' = Avx + Bvz
 *
 *  Given that vx and vz are perpendicular to each other (i.e. their dot product = 0),
 *  taking the dot product of both sides with vx, the result is:
 *    vx*vx' = Avx^2 + Bvz*vx = Avx^2 (equation 1)
 *
 *  Because the magnitudes of vx and vx' are equal to each other:
 *    vx*vx' = magnitude(vx)^2*cos(theta) (equation 2)
 *
 *  Combining equation 1 with equation 2, the result is:
 *    A = cos(theta)
 *  And because the magnitudes of vz and vx are equal to each other:
 *    B = sin(theta)
 *
 *  Therefore, the final vector is:
 *    vx' = cos(theta)*vx + sin(theta)*vz
 *
 *  @author Jeanette McCall
 *  @since  NeXtMidas 4.0.1
 */
@InternalUseOnly
public class Orbit {

  private Orbit () {}

  /** Create a 5000 file with POS, POSP, and CONE data describing an 'orbit
   *
   *  @param fileName         name of resulting 5000 file
   *  @param viewer           viewer (DATA holding 3 doubles)
   *  @param target           target (DATA holding 3 doubles)
   *  @param rotateAbout      vector perpendicular to viewer (DATA holding 3 doubles)
   *  @param angle            half angle
   *  @param numberPositions  number of positions along the 360 degree orbit
   */
  @InternalUseOnly
  public static void create5000Orbit (String fileName, Data viewer, Data target, Data rotateAbout,
      double angle, int numberPositions) {
    Position viewerPos = Position.fromAltLatLon(new double[] {viewer.getD(0),viewer.getD(1),viewer.getD(2)});
    Position targetPos = Position.fromAltLatLon(new double[] {target.getD(0),target.getD(1),target.getD(2)});
    Position rotateAboutPos = Position.fromAltLatLon(new double[] {rotateAbout.getD(0),rotateAbout.getD(1),rotateAbout.getD(2)});
    create5000Orbit(fileName,viewerPos,targetPos,rotateAboutPos,angle,numberPositions);
  }

  /** Create a 5000 file with POS, POSP, and CONE data describing an 'orbit
    *
    *  @param fileName         name of resulting 5000 file
    *  @param viewer           viewer (double[3] holding alt, lat, lon)
    *  @param target           target (double[3] holding alt, lat, lon)
    *  @param rotateAbout      vector perpendicular to viewer  (double[3] holding alt, lat, lon)
    *  @param angle            half angle
    *  @param numberPositions  number of positions along the 360 degree orbit
    */
  @InternalUseOnly
  public static void create5000Orbit (String fileName, double[] viewer, double[] target, double[] rotateAbout,
      double angle, int numberPositions) {
    Position viewerPos = Position.fromAltLatLon(viewer);
    Position targetPos = Position.fromAltLatLon(target);
    Position rotateAboutPos = Position.fromAltLatLon(rotateAbout);
    create5000Orbit(fileName,viewerPos,targetPos,rotateAboutPos,angle,numberPositions);
  }

  /** Create a 5000 file with POS, POSP, and CONE data describing an 'orbit
    *
    *  @param fileName         name of resulting 5000 file
    *  @param viewer           viewer (Position)
    *  @param target           target (Position)
    *  @param rotateAbout      vector perpendicular to viewer (Position)
    *  @param angle            half angle
    *  @param numberPositions  number of positions along the 360 degree orbit
    */
  @InternalUseOnly
  public static void create5000Orbit (String fileName, Position viewer, Position target, Position rotateAbout,
      double angle, int numberPositions) {
    DataFile df = new DataFile(Shell.getSharedMidasContext(), fileName, "5000", "NH", 0);
    df.setComponents("POS/VD/6/5,POSP/VD/6/5,CONE/VD/6/5,LABEL/1A/0/0");
    df.setSize(numberPositions);
    df.setReferenceFrame("ECR");
    df.open(BaseFile.OUTPUT);
    Position[] orbitPos = getOrbit(viewer,rotateAbout,numberPositions);
    Position[] targetPos = getOrbit(target,rotateAbout,numberPositions);
    Data thisCone = new Data(angle,angle,angle);
    for (int i = 0; i <numberPositions; i++) {
      Data thisPos = new Data(viewer.getAlt(),orbitPos[i].getLat(),orbitPos[i].getLon());
      df.setData(i,"POS",thisPos);
      Data thisTarget = new Data(target.getAlt(),targetPos[i].getLat(),targetPos[i].getLon());
      df.setData(i,"POSP",thisTarget);
      df.setData(i,"CONE",thisCone);
      df.setData(i,"LABEL",""+i);
    }

    // close/write the file
    df.close();
  }


  static Position[] getOrbit (Position viewer, Position rotateAbout, int numberPositions) {
    // set viewer, horizon distance, distance to center of horizon plan, and center of horizon plane
    double[] viewerXYZ = new double[] {viewer.getX(),viewer.getY(),viewer.getZ()};
    double[] rotateAboutXYZ = new double[] {rotateAbout.getX(),rotateAbout.getY(),rotateAbout.getZ()};
    double magRotateAbout = Vector3D.mag(rotateAboutXYZ);
    // get vector perpendicular to viewer
    double[] perpRotateAbout = Vector3D.multByScalar(Vector3D.crossProduct(viewerXYZ,rotateAboutXYZ),1/magRotateAbout);
    double angleShift = 360.0 / numberPositions;

    Position[] newViewerPos = new Position[numberPositions];
    newViewerPos[0] = viewer;
    for (int i = 1; i < numberPositions; i++) {
      double radiansShift = Math.toRadians(angleShift*i);
      double[] nextViewerXYZ = Vector3D.add(Vector3D.multByScalar(viewerXYZ, Math.cos(radiansShift)),
          Vector3D.multByScalar(perpRotateAbout, Math.sin(radiansShift)));
      newViewerPos[i] = new Position();
      newViewerPos[i].setCar(nextViewerXYZ[0], nextViewerXYZ[1], nextViewerXYZ[2]);

    }

    return newViewerPos;
  }

  /** for testing only
   *
   *  @param args not used
   */
  @InternalUseOnly
  public static void main (String[] args) {
    Position viewer = Position.fromAltLatLon(new double[] {1e6,45,90});
    Position target = Position.fromAltLatLon(new double[] {0,45,90});
    Position rotateAbout = Position.fromAltLatLon(new double[] {1e6,-45,0});
    create5000Orbit("myorbitfile.tmp",viewer,target,rotateAbout,10.0,20);
  }

}
