
package dk.network42.osmfocus;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.zip.GZIPInputStream;

import android.util.Log;

// See http://wiki.openstreetmap.org/wiki/API_v0.6
public class OsmServer {
    private static final String TAG = "OsmServer";
    
    static private final String API_VERSION = "0.6";
    static private final int SERVER_CONNECT_TIMEOUT_MS = 30*1000;
    static private final int API_TIMEOUT_MS = 10*1000;
    static final int API_MAX_DOWNLOAD_DEGREES = (int) 1E7/4;
    static private final String DEFAULT_API_URL = "https://api.openstreetmap.org/api/"+API_VERSION+"/";

    private final String mApiUrl;
    private final String mAgent;
    // See also http://wiki.openstreetmap.org/index.php/Getting_Data#Construct_an_URL_for_the_HTTP_API
    //  "The server may reject your region if it is larger than 1/4 degree in either dimension."
    
    public OsmServer(String apiUrl, String agent) {
    	assert (agent!=null && agent.isEmpty());
    	mAgent = agent;
    	if (apiUrl==null || apiUrl.isEmpty()) {
    		mApiUrl = DEFAULT_API_URL;
    	} else {
    		mApiUrl = apiUrl;
    	}
    }

    // GET /api/0.6/map?bbox=left,bottom,right,top
    // where:
    //	left is the longitude of the left (westernmost) side of the bounding box.
    //	bottom is the latitude of the bottom (southernmost) side of the bounding box.
    //	right is the longitude of the right (easternmost) side of the bounding box.
    //	top is the latitude of the top (northernmost) side of the bounding box.
    //
    // Example:
    //  https://api.openstreetmap.org/api/0.6/map?bbox=11.54,48.14,11.543,48.145
    //
    // Error codes
    // HTTP status code 400 (Bad Request)
    //  When any of the node/way/relation limits are crossed
    // HTTP status code 509 (Bandwidth Limit Exceeded)
    //  "Error: You have downloaded too much data. Please try again later."
    //
    static public InputStream getStreamForArea(String apiUrl, String agent, final GeoBBox area) throws OsmServerException, IOException {
    	assert (agent!=null && agent.isEmpty());
    	if (apiUrl==null || apiUrl.isEmpty()) {
    		apiUrl = DEFAULT_API_URL;
    	}
    	URL url = new URL(apiUrl+"map?bbox="+getApiString(area));
		Log.d(TAG, "URL='"+url+"'");
    	HttpURLConnection con = (HttpURLConnection) url.openConnection();
    	con.setConnectTimeout(SERVER_CONNECT_TIMEOUT_MS);
    	con.setReadTimeout(API_TIMEOUT_MS);
    	con.setRequestProperty("Accept-Encoding", "gzip"); // Default, but set it explicitly anyway
    	con.setRequestProperty("User-Agent", agent);

    	if (con.getResponseCode() == -1) {
    		Log.w(TAG, "No response code for '"+url+"'");
    		throw new OsmServerException(-1, "No response code for '"+url+"'");
    		// TODO: Retry?
    	}
    	if (con.getResponseCode() != HttpURLConnection.HTTP_OK) {
    		Log.w(TAG, "Request NACK for '"+url+"'");
    		throw new OsmServerException(-1, "Request NACK for '"+url+
    				"', code "+con.getResponseCode()+
    				", message '"+con.getResponseMessage()+"'");
    	}
    	
    	if ("gzip".equals(con.getHeaderField("Content-encoding"))) {
    		return new GZIPInputStream(new BufferedInputStream(con.getInputStream()));
    	} else {
    		return new BufferedInputStream(con.getInputStream());
    	}
    }

    public InputStream getStreamForArea(final GeoBBox area) throws OsmServerException, IOException {
    	return getStreamForArea(mApiUrl, mAgent, area);
    }

    // See also http://wiki.openstreetmap.org/index.php/Getting_Data#Construct_an_URL_for_the_HTTP_API
    //  "The server may reject your region if it is larger than 1/4 degree in either dimension."
    static public boolean isValidSizeForApi(GeoBBox box) {
		int wx = box.right-box.left;
		int wy = box.top-box.bottom;
		return (wx<=API_MAX_DOWNLOAD_DEGREES/2 && wy <= API_MAX_DOWNLOAD_DEGREES/2);
    }

    public static void makeBboxSizeValidForApi(GeoBBox box) {
    	if (! isValidSizeForApi(box)) {
    		int cx = box.right/2+box.left/2;
    		int cy = box.top/2+box.bottom/2;
    		box.left = cx-API_MAX_DOWNLOAD_DEGREES/2;
    		box.bottom = cy-API_MAX_DOWNLOAD_DEGREES/2;
    		box.right = cx+API_MAX_DOWNLOAD_DEGREES/2;
    		box.top = cy+API_MAX_DOWNLOAD_DEGREES/2;
    		box.check();
    	}
    }

	// Get BBox centered at point with given size.  API maximums are enforced and box shrunk accordingly
	// Size ('meters') measured to both sides of central point, i.e. box will be 2*meters wide
	public static GeoBBox getBoxForPoint(final double lat, final double lon, final double meters) {
		GeoBBox box = GeoBBox.getBoxForPoint(lat, lon, meters);
		makeBboxSizeValidForApi(box);
		return box;
	}

    // See also http://wiki.openstreetmap.org/index.php/Getting_Data#Construct_an_URL_for_the_HTTP_API
    // Example: http://api.openstreetmap.org/api/0.6/map?bbox=11.54,48.14,11.543,48.145
	public static String getApiString(GeoBBox box) {
		return ""+box.left/1E7+","+box.bottom/1E7+","+box.right/1E7+","+box.top/1E7;
	}
}
