/* ----------------------------------------
 * Origin  : GFI INFORMATIQUE 
 * Project : CDPP Propagation Tool 
 * ----------------------------------------
 * History 
 * Version : 01/05/2015 : SCA : V2.1
 * Creation 
 * Version : 24/03/2016 : SCA : V2.2 (PSWS-H2020)
 * Add comets
 * Version : 05/05/2021 : LBE
 * DM-ID : Redmine#7059 : 05/05/2021 : store timeValues
 * FA-ID : Redmine#9410 : 04/06/2021 : store timeFile to use when requesting orbit
 * Version : 05/08/2024 : LBE : V2.11
 * DM-ID : Redmine#l0763 : 05/08/2024 : Field lines improvments
 * Version : V2.13
 * FA-ID : Redmine#12166 : 04/04/2025 : equals added on Naif Id
 * ----------------------------------------
 */
package fr.tlse.irap.spaceElement;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;

import jakarta.xml.bind.annotation.XmlSeeAlso;
import jakarta.xml.bind.annotation.XmlType;

import fr.tlse.irap.pos.Position;
import java.util.Arrays;
import java.util.logging.Logger;

/**
 * This object present a space element in the ecliptic plane.
 *
 */
@XmlSeeAlso({Sun.class, Probe.class, Planet.class, Comet.class})
@XmlType
public class SpaceElement implements Serializable {

    private static final long serialVersionUID = 1L;
    private static final Logger logger = Logger.getLogger(SpaceElement.class.getCanonicalName());

    private String name; 		// name of the element (Sun, Stereo A ...)
    private String longName; 	// long name of the element
    private int id;			// Naif ID
    private int size; 		// display size 
    private String color;		// display color
    private int step;		// step in seconds between 2 orbit positions
    private int dataSize;		// number of orbit positions
    private boolean defined;	// indicates if object is defined with its coordinates

    // HAE coordinates 
    private Position pos = new Position(); 		// position in km
    private Double radius; 						// radial distance in km 
    private double attitude[] = new double[2];  // attitude coordinates in HAE 
    //private Matrix3D matAtt3D;  // attitude coordinates in HAE 

    // orbit positions
    private long startDateTime = 0;
    /**
     * list of dates for each position
     */
    private List<Date> timeValues = null;
    /**
     * list of positions. Number depends on dataSize
     */
    private List<Position> positions = null;
    /**
     * orbit files to use for requesting position
     */
    private List<String> orbTimeFiles = null;
    // transformations to be applied to x,y,z coordinates to obtain display coordinates
    private static double rotate; // in radians
    private static double translateX;
    private static double translateY;
    private static double translateZ;
    private static double scale;

    private static GregorianCalendar calendar = new GregorianCalendar();

    public SpaceElement() {
    }

    public SpaceElement(String name) {
        setName(name);
        setLongName(name);
    }

    /**
     * Generic constructor
     *
     * @param name
     * @param longName
     * @param id
     * @param size
     * @param color
     * @param step
     * @param dataSize
     */
    public SpaceElement(String name, String longName, int id, int size, String color, int step, int dataSize) {
        super();
        setName(name);
        setLongName(longName);
        setId(id);
        setSize(size);
        setColor(color);
        setStep(step);
        setDataSize(dataSize);
    }

    public SpaceElement(String name, int id, int size, String color, int step, int dataSize) {
        this(name, name, id, size, color, step, dataSize);
    }

    @Override
    public String toString() {
        return (name + ": long name=" + longName + ": id=" + id + ",x=" + pos.getX() + ",y=" + pos.getY() + ",z=" + pos.getZ() + ": longitude="
                + attitude[0] + ",latitude=" + attitude[1] + ",size=" + size + ",color=" + color);
    }

    @Override
    public boolean equals(Object obj) {
        if(obj instanceof SpaceElement) {
            SpaceElement lSe = (SpaceElement)obj;
            if(lSe.getId()==getId()) return true;
        }
        return false;
    }

    
    
    
    /**
     * Apply transformation (rotate+translate+scale) from x,y coordinates
     *
     * @return display X coordinate
     */
    public Double getDisplayX() {
        return computeDisplayXCoordinate(pos.getX(), pos.getY());
    }

    /**
     * Apply transformation (rotate+translate+scale) from x,y coordinates
     *
     * @return display Y coordinate
     */
    public Double getDisplayY() {
        return computeDisplayYCoordinate(pos.getX(), pos.getY());
    }

    /**
     * Apply transformation (translate+scale) from x,y coordinates
     *
     * @return display Z coordinate
     */
    public Double getDisplayZ() {
        return computeDisplayZCoordinate(pos.getZ());
    }

    public double computeDisplayXCoordinate(double x, double y) {
        return ((x * Math.cos(getRotate()) - y * Math.sin(getRotate())) * getTranslateX() * getScale());
    }

    public double computeDisplayYCoordinate(double x, double y) {
        return ((x * Math.sin(getRotate()) + y * Math.cos(getRotate())) * getTranslateY() * getScale());
    }

    public double computeDisplayZCoordinate(double z) {
        return (z * getTranslateZ() * getScale());
    }

    /**
     * Compute list of display positions using current transformation (used to
     * display planet and comets orbits)
     *
     * @return
     */
    public List<Position> getDisplayPositions() {
        List<Position> displayPos = new ArrayList<Position>();
        for (Position p : positions) {
            Position dp = new Position();
            dp.setX(computeDisplayXCoordinate(p.getX(), p.getY()));
            dp.setY(computeDisplayYCoordinate(p.getX(), p.getY()));
            dp.setZ(computeDisplayZCoordinate(p.getZ()));
            displayPos.add(dp);
        }
        return displayPos;
    }

    /**
     * get start date
     *
     * @return
     */
    public Date getStartDate() {
        calendar.setTimeInMillis(startDateTime * 1000);
        return calendar.getTime();
    }

    /**
     * get end date
     *
     * @return
     */
    public Date getEndDate() {
        calendar.setTimeInMillis(getEndTime() * 1000);
        return calendar.getTime();
    }

    /**
     * Get the start time in seconds
     *
     * @return
     */
    public long getStartTime() {
        return startDateTime;
    }

    /**
     * Set the start time in seconds
     */
    public void setStartTime(long startTime) {
        this.startDateTime = startTime;
    }

    /**
     * Get the end time in seconds computed from start time, step and dataSize
     * of the space object. For VEX and MEX, if end time is on next month,
     * return the last day of start date month
     *
     * @return
     */
    public long getEndTime() {
        long endTime = startDateTime + (long) step * (dataSize - 1);
        return endTime;
    }

    /**
     * Set start date in seconds from a date
     *
     * @param startDate
     */
    public void setStartDate(Date startDate) {
        this.startDateTime = startDate.getTime() / 1000;
    }

    public Position getPosition() {
        return pos;
    }

    public void setPosition(Position pos) {
        this.pos = pos;
    }

    public void setPosition(double x, double y, double z) {
        pos.setX(x);
        pos.setY(y);
        pos.setZ(z);
    }

    public Double getX() {
        return pos.getX();
    }

    public void setX(Double x) {
        pos.setX(x);
    }

    public Double getY() {
        return pos.getY();
    }

    public void setY(Double y) {
        pos.setY(y);
    }

    public Double getZ() {
        return pos.getZ();
    }

    public void setZ(Double z) {
        pos.setZ(z);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Double getRadius() {
        return radius;
    }

    public void setRadius(Double radius) {
        this.radius = radius;
    }

    public int getSize() {
        return size;
    }

    public void setSize(int size) {
        this.size = size;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public static double getRotate() {
        return rotate;
    }

    public static void setRotate(double rotate) {
        SpaceElement.rotate = rotate;
    }

    public static double getScale() {
        return scale;
    }

    public static void setScale(double scale) {
        SpaceElement.scale = scale;
    }

    public static double getTranslateX() {
        return translateX;
    }

    public static void setTranslateX(double translateX) {
        SpaceElement.translateX = translateX;
    }

    public static double getTranslateY() {
        return translateY;
    }

    public static void setTranslateY(double translateY) {
        SpaceElement.translateY = translateY;
    }

    public double[] getAttitude() {
        return attitude;
    }

    public void setAttitude(double attitude[]) {
        this.attitude = attitude;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getStep() {
        return step;
    }

    public void setStep(int step) {
        this.step = step;
    }

    public int getDataSize() {
        return dataSize;
    }

    public void setDataSize(int dataSize) {
        this.dataSize = dataSize;
    }

    public void setPositions(List<Position> positions) {
        this.positions = positions;
        if (positions != null && !positions.isEmpty()) {
            //on update compute new dataSize. Step in changed when setting time values
            //System.out.println("************" + name + " old datasize: " + this.dataSize + " => " + positions.size());
            this.dataSize = positions.size();
        }
    }

    public List<Position> getPositions() {
        return positions;
    }

    public boolean isDefined() {
        return defined;
    }

    public void setDefined(boolean defined) {
        this.defined = defined;
    }

    public static double getTranslateZ() {
        return translateZ;
    }

    public static void setTranslateZ(double translateZ) {
        SpaceElement.translateZ = translateZ;
    }

    public String getLongName() {
        if (longName.isEmpty()) {
            return name;
        } else {
            return longName;
        }
    }

    public void setLongName(String longName) {
        this.longName = longName;
    }

//	public Matrix3D getMatAtt3D() {
//		return matAtt3D;
//	}
//
//	public void setMatAtt3D(Matrix3D matAtt3D) {
//		this.matAtt3D = matAtt3D;
//	}
    /**
     * list of dates for each position
     *
     * @return the timeValues
     */
    public List<Date> getTimeValues() {
        return timeValues;
    }

    /**
     * list of dates for each position
     *
     * @param timeValues the timeValues to set
     */
    public void setTimeValues(List<Date> timeValues) {
        this.timeValues = timeValues;
        if (timeValues != null && !timeValues.isEmpty()) {
            this.dataSize = timeValues.size();
            long lNewStep = (long) ((timeValues.get(timeValues.size() - 1).getTime() / 1000l - timeValues.get(0).getTime() / 1000l) / (long) (timeValues.size() - 1));
            /*System.out.println("*********"+name+" old timevalues: currStep:"+step+" newStep:"+lNewStep+"/"+((long)lNewStep)
                    +" nbTimes:"+timeValues.size()+" time:"+Arrays.toString(timeValues.toArray(new Date[timeValues.size()])));
            */
            this.step = (int) (lNewStep);
        }
    }

    /**
     * orbit files to use for requesting position
     *
     * @return the orbTimeFiles
     */
    public List<String> getOrbTimeFiles() {
        return orbTimeFiles;
    }

    /**
     * orbit files to use for requesting position
     *
     * @param orbTimeFiles the orbTimeFiles to set
     */
    public void setOrbTimeFiles(List<String> orbTimeFiles) {
        this.orbTimeFiles = orbTimeFiles;
    }
}
