package skyview.geometry.csys;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import skyview.geometry.Converter;
import skyview.geometry.CoordinateSystem;
import skyview.geometry.TransformationException;
import skyview.geometry.Util;

import static java.lang.Math.*;

/**
 * This class implements an algorithm that calculates the offsets between the
 * position angle expressed with respect to the poles in two different coordinate
 * systems.
 * 
 * If we transform between coordinate systems we are likely to
 * want to express position angles with reference to the poles in
 * the new coordinates, rather than the pole in the old coordinate system.
 * 
 * @author tmcglynn
 */
public class PositionAngleConverter {
    
    /** This variable stores the transformation between the input and output coordinate systems */
    private Converter conv;
    public PositionAngleConverter(CoordinateSystem input, CoordinateSystem output) 
       throws TransformationException {
        
        // Store the transformation in conv
        conv = new Converter();
        if (input.getRotater() != null) {
            conv.add(input.getRotater().inverse());
        } 
        if (input.getSphereDistorter() != null) {
            conv.add(input.getSphereDistorter().inverse());
        }
        if (output.getSphereDistorter() != null) {
            conv.add(output.getSphereDistorter());
        }
        if (output.getRotater() != null) {
            conv.add(output.getRotater());
        }
    }
    
    /** Main program for testing.
     *  Usage:   
     *        PositionAngleConverter         -- Defaults to J2000 to Galactic
     *           or
     *        PositionAngleConverter  output -- input defaults to J2000
     *           or
     *        PositionAngleConverter  input output
     */
    public static void main(String[] args) throws Exception {
        if (args.length == 0) {
            args = new String[]{"J2000", "Galactic"};
        }
        if (args.length == 1) {
            args = new String[]{"J2000", args[0]};
        }
        CoordinateSystem input = CoordinateSystem.factory(args[0]);
        CoordinateSystem output = CoordinateSystem.factory(args[1]);
        
        PositionAngleConverter conv = new PositionAngleConverter(input,output);
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        
        // Allow user to enter positions where the offset angle will be computed.
        // Inputs and outputs in degrees.
        while (true) {
            String line = br.readLine();
            if (line == null) {
                break;
            }
            line = line.replaceAll("(^ *| *$)","");
            String[] flds = line.split("( +|\\s*,\\s*)");
            double lon = Double.parseDouble(flds[0]);
            double lat = Double.parseDouble(flds[1]);
            double[] coords = new double[]{toRadians(lon), toRadians(lat)};
            double[] unit = Util.unit(coords);
            
            double delta = conv.getOffsetAngle(unit);
            System.out.printf("Input position: %10.5f %10.5f -> %8.4f\n", lon, lat, toDegrees(delta));
        }
    }
    
    /** Calculate the offset between the position angles in the two
     *  coordinate systems at the point defined by the input unit vector 
     *  (presumed to be in the input coordinate system).
     */
    public double getOffsetAngle(double[] unitIn) {
        double[] unitOut = new double[3];
        conv.transform(unitIn, unitOut);
        
        // Get the X unit vector in the tangent plane for
        // the input and ouput coordinate systems in their native coordinate system
        double[] axisIn   = convertToAxis(unitIn);
        double[] axisOutN = convertToAxis(unitOut);
        
        // Convert the output system unit vector to the input coordinate system.
        // E.g., if input is J2000 and output is Galactic, then axisOutN
        // is currently a vector pointing in the direction of increasing Galactic longitude
        // expressed in Galactic coordinates.  We want the same vector, but
        // expressed in J2000 coordinates.
        double[] axisOut = new double[3];
        conv.inverse().transform(axisOutN, axisOut);
        
        // First compute dot product to get magnitude of angle between these two
        // vectors.
        double dot     = 0;
        double normIn  = 0;
        double normOut = 0;
        for (int i=0; i<3; i += 1) {
            dot     += axisIn[i] * axisOut[i];
            normIn  += axisIn[i] * axisIn[i];
            normOut += axisOut[i]* axisOut[i];
        }
        
        double cosDelta = dot/sqrt(normIn*normOut);
        
        // Roundoff errors...
        if (cosDelta > 1) {
            cosDelta = 1;
        } else if (cosDelta < -1) {
            cosDelta = -1;
        }
        
        double angle = acos(cosDelta);
        
        // Now use cross product to get sign.
        // We just need X-component of cross-product.
        double crossX = axisIn[1]*axisOut[2] - axisIn[2]*axisOut[1];
        
        // Compare sign of X component of cross-product with radius unit vector.
        if (crossX * unitIn[0] < 0) {
            angle = -angle;
        }
        return angle;
    }
    
    double[] convertToAxis(double[] unit) {
        return new double[]{-unit[1], unit[0], 0};
    }
}
