/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package skyview.geometry.projecter;

import static java.lang.Math.sin;
import static java.lang.Math.cos;
import static java.lang.Math.PI;
import static java.lang.Math.abs;
import static java.lang.Math.sqrt;
import static java.lang.Math.atan2;
import static java.lang.Math.asin;
import skyview.geometry.Deprojecter;
import skyview.geometry.Projecter;
import skyview.geometry.Transformer;

/**
 *
 * @author lmmcdona
 */
public class Mol extends Projecter {

    /**
     * The name of the Component
     */
    public String getName() {
        return "Mol";
    }

    /**
     * A description of the component
     */
    public String getDescription() {
        return "Project to a MollWeide projection";
    }

    /**
     * Get the associated deprojecter
     */
    public Deprojecter inverse() {
        return new Mol.MolDeproj();
    }

    /**
     * Is this the inverse of another transformation?
     */
    public boolean isInverse(Transformer trans) {
        return trans.getName().equals("MolDeproj");
    }

    public final void transform(double[] sphere, double[] plane) {
        if (Double.isNaN(sphere[2])) {
            plane[0] = Double.NaN;
            plane[1] = Double.NaN;
        } else {
            forwardTransform(sphere, plane);
        }
    }

    public static void forwardTransform(double[] sphere, double[] plane) {
        
        double a = gamma(sphere[2]);
        
        double cos_b = sqrt(1 - sphere[2] * sphere[2]);
        double cos_l = 0;

        if (1 - abs(sphere[2]) > 1.e-10) {
            // Not at a pole
            cos_l = sphere[0] / cos_b;
        }
        double l = atan2(sphere[1], sphere[0]);
        plane[0] = 2 * sqrt(2) / PI * l * cos(a);
        plane[1] = sqrt(2) * sin(a);
    }
    
    
    private static double gamma(double sin_b) {
        double a = sin_b;
        int recurs = 0;
        while (recurs < 20){
            double ax  = a - ( 2*a + sin(2*a) - PI*sin_b ) / ( 2+2*cos(2*a) );
            
            if (abs(ax - a) < 1.e-12) {
                break;
            }
            recurs += 1;
            a = ax;
        }
        if (recurs >= 20) {
            System.err.println("No convergence gamma recursion, Mollweide projection");
        }
        return a;
    }

    public static void reverseTransform(double[] plane, double[] sphere) {

        // Use Calabretta and Greisen fomulae
        double temp  = sqrt(2-plane[1]*plane[1]);
        
        double l     = PI*plane[0] /  (2*temp);
        
        double sin_b = 2/PI * asin(plane[1]/sqrt(2)) + plane[1]/PI * temp;
        double cos_b = sqrt(1-sin_b*sin_b);
        sphere[0] = cos(l)* cos_b;
        sphere[1] = sin(l)* cos_b;
        sphere[2] = sin_b;
    }

    public boolean validPosition(double[] plane) {
        // Allow tiny extension beyond ellipse to accommodate roundoff.
        // Should be OK because there is no singularity here (maybe not true in Mollweide?)
        return super.validPosition(plane)
                && plane[0] * plane[0] / 8 + plane[1] * plane[1] / 2 <= (1 + 1.e-10);
    }

    public boolean straddleable() {
        return false;
    }


    public class MolDeproj extends Deprojecter {

        /**
         * Name of component
         */
        public String getName() {
            return "MolDeproj";
        }

        /**
         * Description of component
         */
        public String getDescription() {
            return "Deproject from a MollWeide ellipse back to the sphere.";
        }

        /**
         * Get the inverse transformation
         */
        public Projecter inverse() {
            return Mol.this;
        }

        /**
         * Is this the inverse of another transformation?
         */
        public boolean isInverse(Transformer trans) {
            return trans.getName().equals("Mol");
        }

        /**
         * Deproject a point from the plane to the sphere.
         *
         * @param plane The input position in the projection plane.
         * @param sphere A preallocated 3-vector to hold the unit vector result.
         */
        public final void transform(double[] plane, double[] sphere) {
            if (!validPosition(plane)) {
                sphere[0] = Double.NaN;
                sphere[1] = Double.NaN;
                sphere[2] = Double.NaN;
            } else {
                reverseTransform(plane, sphere);
            }
        }
    }

    public static void main(String[] args) {
        Mol p = new Mol();

        double ra = Double.parseDouble(args[0]);
        double dec = Double.parseDouble(args[1]);
        System.err.printf("Decimal degrees: %10.5f %10.5f\n", ra, dec);

        double rra = Math.toRadians(ra);        
        double rdec = Math.toRadians(dec);        
        System.err.printf("Radians:         %10.5f %10.5f\n", rra, rdec);
        double[] coor = new double[]{rra,rdec};
                
        double[] unit = skyview.geometry.Util.unit(coor);
        System.err.printf("Unit vector:     %10.5f %10.5f %10.5f\n", unit[0], unit[1], unit[2]);
        
        double a = gamma(unit[2]);
        System.err.println("  The intermediate angle is:"+a);
        
        double[] pnt = new double[2];
        p.transform(unit, pnt);

        double[] shadow = p.shadowPoint(pnt[0], pnt[1]);
        System.err.printf("Map position:    %10.5f %10.5f\n", pnt[0], pnt[1]);
        System.err.printf("Shadow position: %10.5f %10.5f\n", shadow[0], shadow[1]);
        p.inverse().transform(pnt, unit);
        System.err.printf("Rev Unit vector: %10.5f %10.5f %10.5f\n", unit[0], unit[1], unit[2]);
        double[] coords = skyview.geometry.Util.coord(unit);
        System.err.printf("Radians:         %10.5f %10.5f\n", coords[0], coords[1]);
        System.err.printf("Decimal degrees: %10.5f %10.5f\n", Math.toDegrees(coords[0]), Math.toDegrees(coords[1]));        
    }
}
