package ch.epfl.isochrone.geo;

import static java.lang.Math.*;

/**
 * Un point dans le système de coordonnées OSM.
 * 
 * @author Charles Thiebaut (238935)
 * @author Arnaud Delamare (234841)
 * 
 */
public final class PointOSM {

    private final int zoom;
    private final double x;
    private final double y;

    /**
     * @param zoom
     *            Le zoom auquel la carte se situe.
     * @param x
     *            Coordonnée cartésienne du point.
     * @param y
     *            Coordonnée cartésienne du point.
     * @throws IllegalArgumentException
     *             si les différents composantes (zooom, x, y) sont negatives ou
     *             en dehors du domaine conserné.
     */
    public PointOSM(int zoom, double x, double y) {

        if (zoom < 0 || x < 0 || y < 0 || x > maxXY(zoom) || y > maxXY(zoom)) {
            throw new IllegalArgumentException();
        }

        this.zoom = zoom;
        this.x = x;
        this.y = y;

    }

    /**
     * @param zoom
     *            Le zoom auquel la carte se situe.
     * @throws IllegalArgumentException
     *             si le zoom est negatif.
     * @return la taille de la carte
     */
    public static int maxXY(int zoom) {
        if (zoom < 0) {
            throw new IllegalArgumentException();
        }

        return (int) pow(2, zoom + 8);
    }

    public int zoom() {
        return zoom;
    }

    public double x() {
        return x;
    }

    public double y() {
        return y;
    }

    /**
     * @return la position x arrondie en entier
     */
    public int roundedX() {
        return (int) Math.round(x);
    }

    /**
     * @return la position y arrondie en entier
     */
    public int roundedY() {
        return (int) Math.round(y);
    }

    /**
     * @param newZoom
     *            le nouveau zoom du point
     * 
     * @throws IllegalArgumentException
     *             si le nouveau zoom est negatif
     * 
     * @return le nouveau point en coordonnées OSM avec le nouveau zoom
     */
    public PointOSM atZoom(int newZoom) {
        if (newZoom < 0) {
            throw new IllegalArgumentException();
        }

        double f = pow(2, newZoom - this.zoom);

        return new PointOSM(newZoom, x * f, y * f);

    }

    /**
     * @return le point OSM en coordonnée WGS84
     */
    public PointWGS84 toWGS84() {
        double s = pow(2, zoom + 8);
        double longitude = (2 * PI * this.x) / s - PI;
        double latitude = atan(sinh(PI - (2 * PI * this.y) / s));

        return new PointWGS84(longitude, latitude);
    }

    /**
     * @return redéfinit la methode toString héritée la classe OBJET, et permet
     *         d'affiche le point de la manière lisible.
     */
    public String toString() {
        return "(" + zoom + ", " + x + ", " + y + ")";
    }
}
