/*
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

	http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.

 For additional information, contact:
 Environmental Systems Research Institute, Inc.
 Attn: Contracts Dept
 380 New York Street
 Redlands, California, USA 92373

 email: contracts@esri.com
 */

package com.esri.core.geometry;

import com.esri.core.geometry.ogc.*;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import junit.framework.TestCase;
import org.junit.Test;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class TestGeomToGeoJson extends TestCase {
	OperatorFactoryLocal factory = OperatorFactoryLocal.getInstance();

	@Override
	protected void setUp() throws Exception {
		super.setUp();
	}

	@Override
	protected void tearDown() throws Exception {
		super.tearDown();
	}

	@Test
	public void testPoint() {
		Point p = new Point(10.0, 20.0);
		OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson);
		String result = exporter.execute(p);
		assertEquals("{\"type\":\"Point\",\"coordinates\":[10,20]}", result);
	}

	@Test
	public void testEmptyPoint() {
		Point p = new Point();
		OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson);
		String result = exporter.execute(p);
		assertEquals("{\"type\":\"Point\",\"coordinates\":[]}", result);
	}

	@Test
	public void testPointGeometryEngine() {
		Point p = new Point(10.0, 20.0);
		String result = GeometryEngine.geometryToGeoJson(p);
		assertEquals("{\"type\":\"Point\",\"coordinates\":[10,20]}", result);
	}

	@Test
	public void testOGCPoint() {
		Point p = new Point(10.0, 20.0);
		OGCGeometry ogcPoint = new OGCPoint(p, null);
		String result = ogcPoint.asGeoJson();
		assertEquals("{\"type\":\"Point\",\"coordinates\":[10,20],\"crs\":null}", result);
	}

	@Test
	public void testMultiPoint() {
		MultiPoint mp = new MultiPoint();
		mp.add(10.0, 20.0);
		mp.add(20.0, 30.0);
		OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson);
		String result = exporter.execute(mp);
		assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":[[10,20],[20,30]]}", result);
	}

	@Test
	public void testEmptyMultiPoint() {
		MultiPoint mp = new MultiPoint();
		OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson);
		String result = exporter.execute(mp);
		assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":[]}", result);
	}

	@Test
	public void testMultiPointGeometryEngine() {
		MultiPoint mp = new MultiPoint();
		mp.add(10.0, 20.0);
		mp.add(20.0, 30.0);
		String result = GeometryEngine.geometryToGeoJson(mp);
		assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":[[10,20],[20,30]]}", result);
	}

	@Test
	public void testOGCMultiPoint() {
		MultiPoint mp = new MultiPoint();
		mp.add(10.0, 20.0);
		mp.add(20.0, 30.0);
		OGCMultiPoint ogcMultiPoint = new OGCMultiPoint(mp, null);
		String result = ogcMultiPoint.asGeoJson();
		assertEquals("{\"type\":\"MultiPoint\",\"coordinates\":[[10,20],[20,30]],\"crs\":null}", result);
	}

	@Test
	public void testPolyline() {
		Polyline p = new Polyline();
		p.startPath(100.0, 0.0);
		p.lineTo(101.0, 0.0);
		p.lineTo(101.0, 1.0);
		p.lineTo(100.0, 1.0);
		OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson);
		String result = exporter.execute(p);
		assertEquals("{\"type\":\"LineString\",\"coordinates\":[[100,0],[101,0],[101,1],[100,1]]}", result);
	}

	@Test
	public void testEmptyPolyline() {
		Polyline p = new Polyline();
		OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson);
		String result = exporter.execute(p);
		assertEquals("{\"type\":\"LineString\",\"coordinates\":[]}", result);
	}

	@Test
	public void testPolylineGeometryEngine() {
		Polyline p = new Polyline();
		p.startPath(100.0, 0.0);
		p.lineTo(101.0, 0.0);
		p.lineTo(101.0, 1.0);
		p.lineTo(100.0, 1.0);
		String result = GeometryEngine.geometryToGeoJson(p);
		assertEquals("{\"type\":\"LineString\",\"coordinates\":[[100,0],[101,0],[101,1],[100,1]]}", result);
	}

	@Test
	public void testOGCLineString() {
		Polyline p = new Polyline();
		p.startPath(100.0, 0.0);
		p.lineTo(101.0, 0.0);
		p.lineTo(101.0, 1.0);
		p.lineTo(100.0, 1.0);
		OGCLineString ogcLineString = new OGCLineString(p, 0, null);
		String result = ogcLineString.asGeoJson();
		assertEquals("{\"type\":\"LineString\",\"coordinates\":[[100,0],[101,0],[101,1],[100,1]],\"crs\":null}", result);
	}

	@Test
	public void testOGCMultiLineStringCRS() throws IOException {
		Polyline p = new Polyline();
		p.startPath(100.0, 0.0);
		p.lineTo(101.0, 0.0);
		p.lineTo(101.0, 1.0);
		p.lineTo(100.0, 1.0);

		OGCMultiLineString multiLineString = new OGCMultiLineString(p, SpatialReference.create(4326));

		String result = multiLineString.asGeoJson();
		assertEquals("{\"type\":\"MultiLineString\",\"coordinates\":[[[100,0],[101,0],[101,1],[100,1]]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}", result);
	}

	@Test
	public void testPolygon() {
		Polygon p = new Polygon();
		p.startPath(100.0, 0.0);
		p.lineTo(101.0, 0.0);
		p.lineTo(101.0, 1.0);
		p.lineTo(100.0, 1.0);
		p.closePathWithLine();
		OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson);
		String result = exporter.execute(p);
		assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[100,1],[101,1],[101,0],[100,0]]]}", result);
	}

	@Test
	public void testPolygonWithHole() {
		Polygon p = new Polygon();

		//exterior ring - has to be clockwise for Esri
		p.startPath(100.0, 0.0);
		p.lineTo(100.0, 1.0);
		p.lineTo(101.0, 1.0);
		p.lineTo(101.0, 0.0);
		p.closePathWithLine();

		//hole - counterclockwise for Esri
		p.startPath(100.2, 0.2);
		p.lineTo(100.8, 0.2);
		p.lineTo(100.8, 0.8);
		p.lineTo(100.2, 0.8);
		p.closePathWithLine();

		OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson);
		String result = exporter.execute(p);
		assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[101,0],[101,1],[100,1],[100,0]],[[100.2,0.2],[100.2,0.8],[100.8,0.8],[100.8,0.2],[100.2,0.2]]]}", result);
	}

	@Test
	public void testPolygonWithHoleReversed() {
		Polygon p = new Polygon();

		//exterior ring - has to be clockwise for Esri
		p.startPath(100.0, 0.0);
		p.lineTo(100.0, 1.0);
		p.lineTo(101.0, 1.0);
		p.lineTo(101.0, 0.0);
		p.closePathWithLine();

		//hole - counterclockwise for Esri
		p.startPath(100.2, 0.2);
		p.lineTo(100.8, 0.2);
		p.lineTo(100.8, 0.8);
		p.lineTo(100.2, 0.8);
		p.closePathWithLine();

		p.reverseAllPaths();//make it reversed. Exterior ring - ccw, hole - cw.

		OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson);
		String result = exporter.execute(p);
		assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[100,1],[101,1],[101,0],[100,0]],[[100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2]]]}", result);
	}

	@Test
	public void testMultiPolygon() throws IOException {
		JsonFactory jsonFactory = new JsonFactory();

		//String geoJsonPolygon = "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-100,-100],[-100,100],[100,100],[100,-100],[-100,-100]],[[-90,-90],[90,90],[-90,90],[90,-90],[-90,-90]]],[[[-10.0,-10.0],[-10.0,10.0],[10.0,10.0],[10.0,-10.0],[-10.0,-10.0]]]]}";
		String esriJsonPolygon = "{\"rings\": [[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[-90, -90], [90, 90], [-90, 90], [90, -90], [-90, -90]], [[-10, -10], [-10, 10], [10, 10], [10, -10], [-10, -10]]]}";

		JsonParser parser = jsonFactory.createParser(esriJsonPolygon);
		MapGeometry parsedPoly = GeometryEngine.jsonToGeometry(parser);
		//MapGeometry parsedPoly = GeometryEngine.geometryFromGeoJson(jsonPolygon, 0, Geometry.Type.Polygon);

		Polygon poly = (Polygon) parsedPoly.getGeometry();

		OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson);
		//String result = exporter.execute(parsedPoly.getGeometry());
		String result = exporter.execute(poly);
		assertEquals("{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-100,-100],[100,-100],[100,100],[-100,100],[-100,-100]],[[-90,-90],[90,-90],[-90,90],[90,90],[-90,-90]]],[[[-10,-10],[10,-10],[10,10],[-10,10],[-10,-10]]]]}", result);
	}

	@Test
	public void testOGCMultiPolygonCRS() throws IOException {
		JsonFactory jsonFactory = new JsonFactory();

		String esriJsonPolygon = "{\"rings\": [[[-100, -100], [-100, 100], [100, 100], [100, -100], [-100, -100]], [[-90, -90], [90, 90], [-90, 90], [90, -90], [-90, -90]], [[-10, -10], [-10, 10], [10, 10], [10, -10], [-10, -10]]]}";

		JsonParser parser = jsonFactory.createParser(esriJsonPolygon);
		MapGeometry parsedPoly = GeometryEngine.jsonToGeometry(parser);

		parsedPoly.setSpatialReference(SpatialReference.create(4326));
		Polygon poly = (Polygon) parsedPoly.getGeometry();
		OGCMultiPolygon multiPolygon = new OGCMultiPolygon(poly, parsedPoly.getSpatialReference());


		String result = multiPolygon.asGeoJson();
		assertEquals("{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-100,-100],[100,-100],[100,100],[-100,100],[-100,-100]],[[-90,-90],[90,-90],[-90,90],[90,90],[-90,-90]]],[[[-10,-10],[10,-10],[10,10],[-10,10],[-10,-10]]]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}", result);
	}


	@Test
	public void testEmptyPolygon() {
		Polygon p = new Polygon();
		OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson);
		String result = exporter.execute(p);
		assertEquals("{\"type\":\"Polygon\",\"coordinates\":[]}", result);

		MapGeometry imported = OperatorImportFromGeoJson.local().execute(0, Geometry.Type.Unknown, result, null);
		assertTrue(imported.getGeometry().isEmpty());
		assertTrue(imported.getGeometry().getType() == Geometry.Type.Polygon);
	}

	@Test
	public void testPolygonGeometryEngine() {
		Polygon p = new Polygon();
		p.startPath(100.0, 0.0);
		p.lineTo(101.0, 0.0);
		p.lineTo(101.0, 1.0);
		p.lineTo(100.0, 1.0);
		p.closePathWithLine();
		String result = GeometryEngine.geometryToGeoJson(p);
		assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[100,1],[101,1],[101,0],[100,0]]]}", result);
	}

	@Test
	public void testOGCPolygon() {
		Polygon p = new Polygon();
		p.startPath(100.0, 0.0);
		p.lineTo(101.0, 0.0);
		p.lineTo(101.0, 1.0);
		p.lineTo(100.0, 1.0);
		p.closePathWithLine();
		OGCPolygon ogcPolygon = new OGCPolygon(p, null);
		String result = ogcPolygon.asGeoJson();
		assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[100,1],[101,1],[101,0],[100,0]]],\"crs\":null}", result);
	}

	@Test
	public void testPolygonWithHoleGeometryEngine() {
		Polygon p = new Polygon();

		p.startPath(100.0, 0.0);//clockwise exterior
		p.lineTo(100.0, 1.0);
		p.lineTo(101.0, 1.0);
		p.lineTo(101.0, 0.0);
		p.closePathWithLine();

		p.startPath(100.2, 0.2);//counterclockwise hole
		p.lineTo(100.8, 0.2);
		p.lineTo(100.8, 0.8);
		p.lineTo(100.2, 0.8);
		p.closePathWithLine();

		String result = GeometryEngine.geometryToGeoJson(p);
		assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[101,0],[101,1],[100,1],[100,0]],[[100.2,0.2],[100.2,0.8],[100.8,0.8],[100.8,0.2],[100.2,0.2]]]}", result);
	}

	@Test
	public void testPolylineWithTwoPaths() {
		Polyline p = new Polyline();

		p.startPath(100.0, 0.0);
		p.lineTo(100.0, 1.0);

		p.startPath(100.2, 0.2);
		p.lineTo(100.8, 0.2);

		String result = GeometryEngine.geometryToGeoJson(p);
		assertEquals("{\"type\":\"MultiLineString\",\"coordinates\":[[[100,0],[100,1]],[[100.2,0.2],[100.8,0.2]]]}", result);
	}

	@Test
	public void testOGCPolygonWithHole() {
		Polygon p = new Polygon();

		p.startPath(100.0, 0.0);
		p.lineTo(100.0, 1.0);
		p.lineTo(101.0, 1.0);
		p.lineTo(101.0, 0.0);
		p.closePathWithLine();

		p.startPath(100.2, 0.2);
		p.lineTo(100.8, 0.2);
		p.lineTo(100.8, 0.8);
		p.lineTo(100.2, 0.8);
		p.closePathWithLine();

		OGCPolygon ogcPolygon = new OGCPolygon(p, null);
		String result = ogcPolygon.asGeoJson();
		assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[101,0],[101,1],[100,1],[100,0]],[[100.2,0.2],[100.2,0.8],[100.8,0.8],[100.8,0.2],[100.2,0.2]]],\"crs\":null}", result);
	}

	@Test
	public void testOGCPolygonWithHoleCRS() {
		Polygon p = new Polygon();

		p.startPath(100.0, 0.0);
		p.lineTo(100.0, 1.0);
		p.lineTo(101.0, 1.0);
		p.lineTo(101.0, 0.0);
		p.closePathWithLine();

		p.startPath(100.2, 0.2);
		p.lineTo(100.8, 0.2);
		p.lineTo(100.8, 0.8);
		p.lineTo(100.2, 0.8);
		p.closePathWithLine();

		SpatialReference sr = SpatialReference.create(4326);

		OGCPolygon ogcPolygon = new OGCPolygon(p, sr);
		String result = ogcPolygon.asGeoJson();
		assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[101,0],[101,1],[100,1],[100,0]],[[100.2,0.2],[100.2,0.8],[100.8,0.8],[100.8,0.2],[100.2,0.2]]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}", result);
	}

	@Test
	public void testGeometryCollection() {
		SpatialReference sr = SpatialReference.create(4326);

		StringBuilder geometrySb = new StringBuilder();
		geometrySb
				.append("{\"type\" : \"GeometryCollection\", \"geometries\" : [");

		OGCPoint point = new OGCPoint(new Point(1.0, 1.0), sr);
		assertEquals("{\"x\":1,\"y\":1,\"spatialReference\":{\"wkid\":4326}}",
				point.asJson());
		assertEquals(
				"{\"type\":\"Point\",\"coordinates\":[1,1],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}",
				point.asGeoJson());
		geometrySb.append(point.asGeoJson()).append(", ");

		OGCLineString line = new OGCLineString(new Polyline(
				new Point(1.0, 1.0), new Point(2.0, 2.0)), 0, sr);
		assertEquals(
				"{\"paths\":[[[1,1],[2,2]]],\"spatialReference\":{\"wkid\":4326}}",
				line.asJson());
		assertEquals(
				"{\"type\":\"LineString\",\"coordinates\":[[1,1],[2,2]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}",
				line.asGeoJson());
		geometrySb.append(line.asGeoJson()).append(", ");

		Polygon p = new Polygon();
		p.startPath(1.0, 1.0);
		p.lineTo(2.0, 2.0);
		p.lineTo(3.0, 1.0);
		p.lineTo(2.0, 0.0);

		OGCPolygon polygon = new OGCPolygon(p, sr);
		assertEquals(
				"{\"rings\":[[[1,1],[2,2],[3,1],[2,0],[1,1]]],\"spatialReference\":{\"wkid\":4326}}",
				polygon.asJson());
		assertEquals(
				"{\"type\":\"Polygon\",\"coordinates\":[[[1,1],[2,0],[3,1],[2,2],[1,1]]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}",
				polygon.asGeoJson());
		geometrySb.append(polygon.asGeoJson()).append("]}");

		List<OGCGeometry> geoms = new ArrayList<OGCGeometry>(3);
		geoms.add(point);
		geoms.add(line);
		geoms.add(polygon);
		OGCConcreteGeometryCollection collection = new OGCConcreteGeometryCollection(
				geoms, sr);
		String s2 = collection.asGeoJson();
		
		assertEquals("{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"Point\",\"coordinates\":[1,1]},{\"type\":\"LineString\",\"coordinates\":[[1,1],[2,2]]},{\"type\":\"Polygon\",\"coordinates\":[[[1,1],[2,0],[3,1],[2,2],[1,1]]]}],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}", collection.asGeoJson());
	}

	@Test
	public void testEmptyGeometryCollection() {
		SpatialReference sr = SpatialReference.create(4326);
		OGCConcreteGeometryCollection collection = new OGCConcreteGeometryCollection(
				new ArrayList<OGCGeometry>(), sr);
		String s2 = collection.asGeoJson();
		assertEquals(
				"{\"type\":\"GeometryCollection\",\"geometries\":[],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}",
				collection.asGeoJson());
	}

	//Envelope is exported as a polygon (we don't support bbox, as it is not a GeoJson geometry, but simply a field)!
	@Test
	public void testEnvelope() {
		Envelope e = new Envelope();
		e.setCoords(-180.0, -90.0, 180.0, 90.0);
		String result = OperatorExportToGeoJson.local().execute(e);
		assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[-180,-90],[180,-90],[180,90],[-180,90],[-180,-90]]]}", result);
	}

	@Test
	public void testEmptyEnvelope() {
		Envelope e = new Envelope();
		String result = OperatorExportToGeoJson.local().execute(e);
		assertEquals("{\"type\":\"Polygon\",\"coordinates\":[]}", result);
	}

	@Test
	public void testEnvelopeGeometryEngine() {
		Envelope e = new Envelope();
		e.setCoords(-180.0, -90.0, 180.0, 90.0);
		String result = GeometryEngine.geometryToGeoJson(e);
		assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[-180,-90],[180,-90],[180,90],[-180,90],[-180,-90]]]}", result);
	}

	@Test
	public void testOldCRS() {
		String inputStr = "{\"type\":\"Polygon\",\"coordinates\":[[[-180,-90],[180,-90],[180,90],[-180,90],[-180,-90]]], \"crs\":\"EPSG:4267\"}";
		MapGeometry mg = OperatorImportFromGeoJson.local().execute(GeoJsonImportFlags.geoJsonImportDefaults, Geometry.Type.Unknown, inputStr, null);
		String result = GeometryEngine.geometryToGeoJson(mg.getSpatialReference(), mg.getGeometry());
		assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[-180,-90],[180,-90],[180,90],[-180,90],[-180,-90]]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4267\"}}}", result);
	}
	
	// bbox is not supported anymore.
	//    @Test
	//    public void testEnvelope() {
	//        Envelope e = new Envelope();
	//        e.setCoords(-180.0, -90.0, 180.0, 90.0);
	//        OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson);
	//        String result = exporter.execute(e);
	//        assertEquals("{\"bbox\":[-180.0,-90.0,180.0,90.0]}", result);
	//    }
	//
	//    @Test
	//    public void testEmptyEnvelope() {
	//        Envelope e = new Envelope();
	//        OperatorExportToGeoJson exporter = (OperatorExportToGeoJson) factory.getOperator(Operator.Type.ExportToGeoJson);
	//        String result = exporter.execute(e);
	//        assertEquals("{\"bbox\":null}", result);
	//    }
	//
	//    @Test
	//    public void testEnvelopeGeometryEngine() {
	//        Envelope e = new Envelope();
	//        e.setCoords(-180.0, -90.0, 180.0, 90.0);
	//        String result = GeometryEngine.geometryToGeoJson(e);
	//        assertEquals("{\"bbox\":[-180.0,-90.0,180.0,90.0]}", result);
	//    }

}
