/*******************************************************************************
 *
 *    Copyright (C) 2015-2020 the BBoxDB project
 *
 *    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.
 *
 *******************************************************************************/
package org.bboxdb.network.query.filter;

import org.bboxdb.storage.entity.Tuple;
import org.json.JSONObject;

import com.esri.core.geometry.MapOGCStructure;
import com.esri.core.geometry.Operator;
import com.esri.core.geometry.OperatorFactoryLocal;
import com.esri.core.geometry.OperatorImportFromGeoJson;
import com.esri.core.geometry.WktImportFlags;
import com.esri.core.geometry.ogc.OGCGeometry;
import com.esri.core.geometry.ogc.OGCPoint;

public class UserDefinedGeoJsonSpatialFilter implements UserDefinedFilter {
	
	private OGCGeometry customGeomety = null;
	
	private final static double MAX_OVERLAPPING_POINT_DISTANCE = 0.0001;

	/**
	 * Perform a real filter based on the geometry of the data
	 */
	@Override
	public boolean filterTuple(final Tuple tuple, final byte[] customData) {
		
		// No custom geometry is passed
		if(customData == null) { 
			return true;
		}
		
		// Cache the custom geometry between method calls
		if(customGeomety == null) {
			final String customString = new String(customData);
			customGeomety = geoJoinToGeomety(customString);
		}
		
		final String geoJsonString = new String(tuple.getDataBytes());
		final OGCGeometry geometry = extractGeometry(geoJsonString);

        return geometry.intersects(customGeomety);
	}
	
	/**
	 * Perform a real join based on the geometry of the data
	 */
	@Override
	public boolean filterJoinCandidate(final Tuple tuple1, final Tuple tuple2, final byte[] customData) {
		
		final String geoJsonString1 = new String(tuple1.getDataBytes());
		final String geoJsonString2 = new String(tuple2.getDataBytes());

		// Full text search on string (if provided)
		if(customData != null) {
			final String customDataString = new String(customData);
			if(! geoJsonString1.contains(customDataString) && 
					! geoJsonString2.contains(customDataString)) {
				return false;
			}
		}
		
		final OGCGeometry geometry1 = extractGeometry(geoJsonString1);
		final OGCGeometry geometry2 = extractGeometry(geoJsonString2);
		
		if(geometry1 instanceof OGCPoint) {
			final double geometryDistrance = geometry1.distance(geometry2);
			return geometryDistrance < MAX_OVERLAPPING_POINT_DISTANCE;
		} else {
		    return geometry1.intersects(geometry2);
		}
	}
	
	/**
	 * Extract the geometry from the tuple
	 * @param tuple
	 * @return
	 */
	private OGCGeometry extractGeometry(final String geoJsonString) {

		final JSONObject jsonObject = new JSONObject(geoJsonString);
		
		// Extract geometry (if exists)
		final JSONObject geometryObject = jsonObject.optJSONObject("geometry");
		
		if(geometryObject != null) {
			return geoJoinToGeomety(geometryObject.toString());
		}
		
		return geoJoinToGeomety(geoJsonString);
	}

	/**
	 * Convert the geojson element to a ESRI geometry
	 * @param jsonString
	 * @return
	 */
	private OGCGeometry geoJoinToGeomety(String jsonString) {
		final OperatorImportFromGeoJson op = (OperatorImportFromGeoJson) OperatorFactoryLocal
		        .getInstance().getOperator(Operator.Type.ImportFromGeoJson);
				
	    final MapOGCStructure structure = op.executeOGC(WktImportFlags.wktImportDefaults, jsonString, null);

	    return OGCGeometry.createFromOGCStructure(structure.m_ogcStructure,
	    		structure.m_spatialReference);
	}

}