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) {
            throw new IllegalArgumentException("Zoom negatif !");
        }
        if (x < 0) {
            throw new IllegalArgumentException("x negatif !");
        }
        if (y < 0) {
            throw new IllegalArgumentException("y negatif !");
        }
        if (x > maxXY(zoom)) {
            throw new IllegalArgumentException("x est trop grand");
        }
        if (y > maxXY(zoom)) {
            throw new IllegalArgumentException("y est trop grand");
        }

        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("Le zoom doit être positif");
        }

        return (int) pow(2, zoom + 8);
    }
    
    /**
     * @return le zoom du point
     */
    public int zoom() {
        return zoom;
    }
    
    /**
     * @return la position x
     */
    public double x() {
        return x;
    }
    
    /**
     * @return la position y
     */
    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("Le nouveau zoom doit être positif");
        }

        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() {
        StringBuilder sb = new StringBuilder();
        sb.append('(');
        sb.append(toDegrees(this.zoom));
        sb.append(',');
        sb.append(toDegrees(this.x));
        sb.append(')');
        sb.append(toDegrees(this.y));
        sb.append(')');
        return sb.toString();
    }
}
