package graphtea.library.util;
/**
handles angles in various units (degrees, grades, dms, radians, semicircles).
author    hehl@tfh-berlin.de
version   17-May-2005
*/
public final class Angle {

    /**
     * global constant (2 * Pi) = 6.283185307179586...
     */
    public static final double TWOPI = 2. * Math.PI;
    /**
     * global constant 'rho' in degrees
     */
    public static final double RHODEG = 180. / Math.PI;
    /**
     * global constant 'rho' in grades
     */
    public static final double RHOGON = 200. / Math.PI;
    /**
     * global constant 'rho' in 'semi circles' (Trimble)
     */
    public static final double RHOSEMI = 1. / Math.PI;
    /**
     * unit  'radians' [-2pi..2pi]
     */
    public static final int RAD = 0;
    /**
     * unit  'degrees' [-360..+360]
     */
    public static final int DEG = 1;
    /**
     * unit  'grades' [-400..+400]
     */
    public static final int GON = 2; // grades (german: gon)
    /**
     * unit  'grades' [-400..+400]
     */
    public static final int GRA = 2; // grades
    /**
     * unit  'degrees minutes seconds'
     */
    public static final int DMS = 3;
    /**
     * unit  'semicircles' [-2..+2]
     */
    public static final int SEMI = 4;

    /**
     * private data
     */
    private double _radians; // angle in radians
    private final int _unit; // defining unit on angle

    /**
     * constructs an angle 0 rad
     */
    public Angle() {
        _radians = 0.0;
        _unit = RAD;
    }

    /**
     * constructs an angle in default unit 'grades'.
     *
     * @param x angle in grades
     * @throws Msg The message
     */
    public Angle(double x) {
        _radians = (x / RHOGON) % TWOPI;
        _unit = GON;
    }

    /**
     * constructs an angle with given unit RAD, DEG, GON, etc.
     *
     * @param x    value of the angle
     * @param unit unit of: RAD, DEG, GON, DMS or SEMI
     * @throws Msg The message
     */
    public Angle(double x, int unit) throws Msg {
        String r = "Angle.constructor";
        switch (unit) {
            case RAD:
                _radians = x;
                break;
            case DEG:
            case DMS:
                _radians = x / RHODEG;
                break;
            case GON:
                _radians = x / RHOGON;
                break;
            case SEMI:
                _radians = x / RHOSEMI;
                break;
            default:
                throw new Msg(r, "invalid angle unit " + unit);
        }
        _radians %= TWOPI;
        _unit = unit;
    }

    /**
     * constructs an angle in (degrees, minutes, seconds) format
     *
     * @param d degrees in [-359..359]
     * @param m minutes [0..59]
     * @param s seconds [0.0 to 59.999999999999]
     * @throws Msg
     */
    public Angle(int d, int m, double s) throws Msg {
        this((double) d + (double) m / 60. + s / 3600.0, DMS);
    }

    /**
     * Returns value of current angle in radians
     *
     * @return value of current angle in radians
     */
    public double rad() {
        return _radians;
    }

    /**
     * Returns value of current angle in degrees.
     *
     * @return value of current angle in degrees
     */
    public double deg() {
        return rad() * RHODEG;
    }

    /**
     * Returns value of current angle in grades.
     *
     * @return value of current angle in grades
     */
    public double gon() {
        return rad() * RHOGON;
    }

    /**
     * Returns value of current angle in grades.
     *
     * @return value of current angle in grades
     */
    public double gra() {
        return gon();
    }

    /**
     * /**
     * Returns value of current angle in milligrades.
     *
     * @return value of current angle in milligrades
     */
    public double mgon() {
        return gon() * 1000.0;
    }

    /**
     * Returns value of current angle in seconds of arc.
     *
     * @return value of current angle in seconds of arc
     */
    public double arcsec() {
        return deg() * 3600.0;
    }

    /**
     * Returns value of current angle in semi-circles (Trimble).
     *
     * @return value of current angle in semi-circles
     */
    public double semi() {
        return rad() * RHOSEMI;
    }

    /**
     * Computes the sine of the argument.
     *
     * @param x an angle
     * @return sine of the argument
     */
    public static double sin(Angle x) {
        return Math.sin(x.rad());
    }

    /**
     * Computes the cosine of the argument.
     *
     * @param x an angle
     * @return cosine of the argument
     */
    public static double cos(Angle x) {
        return Math.cos(x.rad());
    }

    /**
     * Computes the sine of current angle.
     *
     * @return sine of current angle
     */
    public double sin() {
        return Math.sin(this.rad());
    }

    /**
     * Computes the cosine of current angle.
     *
     * @return cosine of current angle
     */
    public double cos() {
        return Math.cos(this.rad());
    }

    /**
     * @return angle in degrees, minutes, and seconds in String respresentation
     */
    public String toDMS() {
        double x = deg();
        boolean negative = (x < 0.0);
        if (negative) x = -x;
        int d = (int) x;
        x = (x - d) * 60.;
        int m = (int) x;
        double s = (x - m) * 60.0;
        String result = String.format("%3dd %02dm %012.9fs", d, m, s);
        if (negative) result = "-" + result;
        return result;
    }

    /**
     * @return angle in same unit as in construction
     */
    public String toString() {
        String result;
        switch (_unit) {
            case RAD:
                result = "[" + rad() + " rad]";
                break;
            case DEG:
                result = String.format("[%12.9f deg]", deg());
                break;
            case GON:
                result = "[" + gon() + " gon]";
                break;
            case DMS:
                result = "[" + toDMS() + "]";
                break;
            case SEMI:
                result = "[" + semi() + " semi circles]";
                break;
            default:
                result = "[** invalid **]";
        }
        return result;
    }

    /**
     * adds the right hand side to current angle and returns their sum
     *
     * @param rhs right hand side angle
     * @return sum of current and rhs angle
     */
    public Angle add(Angle rhs) {
        _radians += rhs.rad();
        _radians %= TWOPI;
        return this;
    }

    /**
     * demonstrates functionality of class 'Angle'
     */
    public static void demo() {
        try {
            System.out.println("*****************************");
            System.out.println("*** Test of Class 'Angle' ***");
            System.out.println("*****************************");
            System.out.println();

            Angle alpha = new Angle(Math.PI / 2., Angle.RAD); // in radians
            Angle beta = new Angle(400.0, Angle.GON);      // in grades
            Angle gamma = new Angle(45., Angle.DEG);        // in degrees
            Angle delta = new Angle(52, 30, 0.0);            // in DMS
            Angle eps = new Angle(100.);                 // in grades

            System.out.println("alpha= " + alpha);
            System.out.println("beta = " + beta);
            System.out.println();
            System.out.println("sin" + gamma + "= " + Math.sin(gamma.rad()));
            System.out.println("sin" + gamma + "= " + gamma.sin());
            System.out.println("sin" + gamma + "= " + Angle.sin(gamma));
            System.out.println();
            System.out.println("delta= " + delta);
            System.out.println("eps  = " + eps);
            System.out.println("anonymous angle= " + new Angle(47.43, Angle.DEG));
        } catch (Exception ignored) {
        }
    }
} // end class Angle
