package info.thomazo.findgas.web.api;

import com.github.davidmoten.geo.GeoHash;
import com.github.davidmoten.geo.LatLong;
import info.thomazo.findgas.web.config.ElasticConfig;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.geogrid.GeoHashGrid;
import org.geojson.Feature;
import org.geojson.GeoJsonObject;
import org.geojson.Point;
import org.ocpsoft.prettytime.PrettyTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.time.Instant;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;

import static org.springframework.web.bind.annotation.RequestMethod.GET;

@RestController
@RequestMapping("/api/stations")
public class StationsCtrl {

	@Autowired
	private Client esClient;

	@Autowired
	private ElasticConfig esConfig;

	@RequestMapping(method = GET)
	public List<GeoJsonObject> list(@RequestParam double n, @RequestParam double s, @RequestParam double w, @RequestParam double e, @RequestParam int z) {

		if (z < 10) {
			return listAggregate(n, s, w, e, z);
		}

		SearchResponse res = esClient.prepareSearch(esConfig.getIndexName()).setTypes(esConfig.getStationType())
				.setQuery(QueryBuilders.boolQuery().filter(QueryBuilders.geoBoundingBoxQuery("location").topLeft(n, w).bottomRight(s, e)))
				.setSize(100)
				.execute()
				.actionGet();

		if (res.getHits().getTotalHits() > 100) {
			return listAggregate(n, s, w, e, z);
		}

		List<GeoJsonObject> resStations = new ArrayList<>((int) res.getHits().totalHits());
		PrettyTime pt = new PrettyTime(Locale.FRANCE);

		res.getHits().forEach(h -> {
			Feature f = new Feature();
			f.setProperty("id", h.getId());
			setProperty(h, f, "address");
			setProperty(h, f, "cp");
			setProperty(h, f, "city");
			setProperty(h, f, "last_name");
			setProperty(h, f, "comment");
			setProperty(h, f, "gas");
			f.setGeometry(getPoint(h.getSource().get("location").toString()));

			Object lastUpdate = h.getSource().get("last_update");
			if (lastUpdate instanceof String) { //null is not an instance
				f.setProperty("last_update", lastUpdate);
				f.setProperty("last_ago", pt.format(Date.from(Instant.parse((String) lastUpdate))));
			}

			resStations.add(f);
		});

		return resStations;
	}


	private List<GeoJsonObject> listAggregate(double n, double s, double w, double e, int z) {
		int zoom = z - 4;
		if (zoom < 5) zoom = 5;

		SearchResponse res = esClient.prepareSearch(esConfig.getIndexName()).setTypes(esConfig.getStationType())
				.setQuery(QueryBuilders.boolQuery().filter(QueryBuilders.geoBoundingBoxQuery("location").topLeft(n, w).bottomRight(s, e)))
				.addAggregation(AggregationBuilders.geohashGrid("stations").field("location").precision(zoom))
				.setSize(100)
				.execute()
				.actionGet();


		GeoHashGrid stations = res.getAggregations().get("stations");
		List<GeoHashGrid.Bucket> buckets = stations.getBuckets();
		List<GeoJsonObject> resStations = new ArrayList<>(buckets.size());

		for (GeoHashGrid.Bucket bucket : buckets) {
			Feature f = new Feature();

			LatLong latLong = GeoHash.decodeHash(bucket.getKeyAsString());
			f.setGeometry(new Point(latLong.getLon(), latLong.getLat()));

			f.setProperty("nb", bucket.getDocCount());
			f.setProperty("radius", getRadiusFromGeohash(bucket.getKeyAsString()));

			resStations.add(f);
		}

		return resStations;
	}

	private double getRadiusFromGeohash(String geohash) {
		switch (geohash.length()) {
			case 1: return 2500 / 2;
			case 2: return 630 / 2;
			case 3: return 78 / 2;
			case 4: return 20 / 2;
			case 5: return 2.4 / 2;
			case 6: return 0.61 / 2;
			case 7: return 0.076 / 2;
			case 8: return 0.019 / 2;
		}
		return 0;
	}

	private GeoJsonObject getPoint(String location) {
		int idx = location.indexOf(',');
		if (idx == -1) return null;

		double lat = Double.parseDouble(location.substring(0, idx));
		double lng = Double.parseDouble(location.substring(idx + 2));

		return new Point(lng, lat);
	}

	private void setProperty(SearchHit hit, Feature feature, String fieldName) {
		Object value = hit.getSource().get(fieldName);
		if (value != null) {
			feature.setProperty(fieldName, value);
		}
	}
}

@@ -1425,16 +1425,217 @@
 int z) %7B
+%0A%09%09//limit bounds%0A%09%09if (n %3C -90) n = -90; if (n %3E 90) n = 90;%0A%09%09if (s %3C -90) s = -90; if (s %3E 90) s = 90;%0A%09%09if (w %3C -180) w = -180; if (w %3E 180) s = 180;%0A%09%09if (e %3C -180) e = -180; if (e %3E 180) e = 180;
 %0A%0A%09%09if (

set bounds to input coords