package org.gcube.portlets.user.gisviewer.server.datafeature.wfs;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.gcube.portlets.user.gisviewer.client.Constants;
import org.gcube.portlets.user.gisviewer.client.commons.beans.LayerItem;
import org.gcube.portlets.user.gisviewer.client.commons.utils.URLMakers;
import org.gcube.portlets.user.gisviewer.server.util.CSVReader;
import org.gcube.portlets.user.gisviewer.server.util.JSONUtil;
import org.gcube.portlets.user.gisviewer.server.util.URLParserUtil;
import org.gcube.portlets.user.gisviewer.shared.CSVFile;
import org.gcube.portlets.user.gisviewer.shared.CSVRow;
import org.gcube.portlets.user.gisviewer.shared.FeatureExportViewConfig;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;


/**
 * The Class WFSExporter.
 *
 * @author Francesco Mangiacrapa at ISTI-CNR Pisa (Italy)
 * May 4, 2020
 */
public class WFSExporter {
	
	private static Logger log = Logger.getLogger(WFSExporter.class);
	
	public static enum WFSGetFeatureFields{type,id,geometry_type,geometry_coordinates,geometry_name};
	
	public static enum WFS_TO {VIEW, EXPORT};
	
	public Map<WFS_TO,FeatureExporterFileConfig> mapWFSConfig = new HashMap<WFSExporter.WFS_TO, FeatureExporterFileConfig>();
	
	/**
	 * Instantiates a new WFS exporter.
	 */
	public WFSExporter() {
		
		WFSExporterSystemDir wfsSystemDir = new WFSExporterSystemDir();
		try {
			CSVReader toWFSExport = wfsSystemDir.getCSVFileConfig(WFS_TO.EXPORT);
			FeatureExporterFileConfig toWFSExportFileConfig = getConfig(toWFSExport);
			toWFSExportFileConfig.setWfsTo(WFS_TO.EXPORT);
			mapWFSConfig.put(WFS_TO.EXPORT, toWFSExportFileConfig);
			log.info("To WFS export file config has the map: "+toWFSExportFileConfig.getMap());
		} catch (FileNotFoundException e) {
			log.error("Error on reading WFS config for "+WFS_TO.EXPORT, e);
		}
		
		try {
			CSVReader toWFSView = wfsSystemDir.getCSVFileConfig(WFS_TO.VIEW);
			FeatureExporterFileConfig toWFSViewFileConfig = getConfig(toWFSView);
			toWFSViewFileConfig.setWfsTo(WFS_TO.VIEW);
			mapWFSConfig.put(WFS_TO.VIEW, toWFSViewFileConfig);
			log.info("To WFS view file config has the map: "+toWFSViewFileConfig.getMap());
		} catch (FileNotFoundException e) {
			log.error("Error on reading WFS config for "+WFS_TO.VIEW, e);
		}
	}
	
	/**
	 * Gets the config.
	 *
	 * @param reader the reader
	 * @return the config
	 */
	private FeatureExporterFileConfig getConfig(CSVReader reader) {
		
		CSVFile file = reader.getCsvFile();
		log.debug("Building config from: "+file.getFileName());
		FeatureExporterFileConfig fileConfig = new FeatureExporterFileConfig();
		fileConfig.setFile(file);
		Map<String, FeatureExportViewConfig> map = new HashMap<String, FeatureExportViewConfig>(file.getValueRows().size());
		for (CSVRow row : file.getValueRows()) {
			List<String> values = row.getListValues();
			String featureName = values.get(0).replace("\"", "");
			boolean export = true;
			try {
				export = Boolean.parseBoolean(values.get(1).replace("\"", ""));
			}catch (Exception e) {
				log.error("Error on parsing as Boolean the property: "+values.get(1), e);
			}
			String exportAs = values.get(2).replace("\"", "");
			FeatureExportViewConfig featureExportViewConfig = new FeatureExportViewConfig(featureName, export, exportAs);
			log.trace("Adding key: "+featureName + " with config: "+featureExportViewConfig);
			map.put(featureName, featureExportViewConfig);
		}
		fileConfig.setMap(map);
		
		return fileConfig;
	}
	

	/**
	 * Gets the table from json to export.
	 *
	 * @param layerItem the layer item
	 * @param bbox the bbox
	 * @param maxWFSFeature the max WFS feature
	 * @param toViewExport the to view export
	 * @return the table from json to export
	 */
	public CSVFile getTableFromJsonToExport(LayerItem layerItem, String bbox, int maxWFSFeature, WFS_TO toViewExport) {
		
		toViewExport = toViewExport == null ? WFS_TO.VIEW : toViewExport; //default is to VIEW
		
		FeatureExporterFileConfig toWFSFileConfig = mapWFSConfig.get(toViewExport); //reading the right config
		return getTableFromJsonToExport(layerItem, bbox, maxWFSFeature, toWFSFileConfig);
	}
	
	/**
	 * Gets the table from json to export.
	 *
	 * @param layerItem the layer item
	 * @param bbox the bbox
	 * @param maxWFSFeature the max WFS feature
	 * @param exporterConfig the exporter config
	 * @return the table from json to export
	 */
	@SuppressWarnings("unchecked")
	private CSVFile getTableFromJsonToExport(LayerItem layerItem, String bbox, int maxWFSFeature, FeatureExporterFileConfig exporterConfig) {
		CSVFile tableFile = new CSVFile();
		tableFile.setFileName(layerItem.getName());
		CSVRow headerRow = new CSVRow();
		headerRow.setListValues(new ArrayList<String>()); //to manage export as false on all WFS fields
		tableFile.setHeaderRow(headerRow);
		List<CSVRow> valueRows = new ArrayList<CSVRow>();
		tableFile.setValueRows(valueRows);

		log.info("getTableFromJson -> Creating WfsTable to layerItem:  "+layerItem.getLayer());
		InputStream is = null;
		try {
			String url = URLMakers.getWfsFeatureUrl(layerItem, bbox, maxWFSFeature, Constants.JSON);
			String cqlFilterValue = URLParserUtil.extractValueOfParameterFromURL(URLMakers.CQL_FILTER_PARAMETER, url);
			log.info("Found CQL filter value into query string: "+cqlFilterValue);
			
			if(cqlFilterValue!=null) {
				String notEncodedCQLFilter = String.format("%s=%s",URLMakers.CQL_FILTER_PARAMETER,cqlFilterValue);
				//log.info("Found CQL filter: "+notEncodedCQLFilter);
				String toEncodeCQLFilter = String.format("%s=%s",URLMakers.CQL_FILTER_PARAMETER,URLEncoder.encode(cqlFilterValue,"UTF-8"));
				log.debug("Encoded CQL filter: "+toEncodeCQLFilter);
				url = url.replace(notEncodedCQLFilter, toEncodeCQLFilter);
			}
			
			log.info("getTableFromJson -> WFS URL:  "+url);
			is = new URL(url).openStream();
			String jsonTxt = IOUtils.toString(is);

			if(jsonTxt==null || jsonTxt.isEmpty()){
				jsonTxt = "{\"type\":\"FeatureCollection\",\"features\":[]}";
			}
			
			//validating export map config
			Map<String, FeatureExportViewConfig> exporterMapConfig = (exporterConfig == null || exporterConfig.getMap() == null)?
					new HashMap<String, FeatureExportViewConfig>():exporterConfig.getMap();
			
			log.debug("Using config to tranfrom: "+exporterMapConfig);
			List<WFSGetFeatureFields> toTransform = new ArrayList<WFSExporter.WFSGetFeatureFields>();
			
			for (WFSGetFeatureFields fieldName : WFSGetFeatureFields.values()) {
				
				FeatureExportViewConfig toHeader = exporterMapConfig.get(fieldName.name());
				log.debug("Searched field '"+fieldName.name()+"' into map has value: "+toHeader);
				if(toHeader!=null && toHeader.isExport()) {
					String exportAs = (toHeader.getExportAs() != null && !toHeader.getExportAs().isEmpty())
							? toHeader.getExportAs()
							: fieldName.name();
					headerRow.addValue(exportAs);
					toTransform.add(fieldName);
				}
			}
			
			log.debug("Detected the WFS fields to view/export: "+toTransform);

			// get json object
			JSONObject json = new JSONObject(jsonTxt);
			// iterate features
			JSONArray features = json.getJSONArray("features");
			if(features.length()==0) {
				log.info("No features detected in the response, returning empty csv file");
				return tableFile;
			}

			WFS_TO toViewExport = exporterConfig.getWfsTo();
			
			for (int i=0; i<features.length(); i++) {
				final CSVRow row = new CSVRow();
				JSONObject theFeature = ((JSONObject)features.get(i));
				log.debug("Building at index: "+i);
				log.trace("the feature: "+theFeature);
				for (WFSGetFeatureFields featureToExport : toTransform) {
					switch (featureToExport) {
					case geometry_type:
						JSONObject geometry = theFeature.getJSONObject("geometry");
						String typeValue = geometry.getString("type");
						//is a string, so adding double quotes in case of export
						if(toViewExport.equals(WFS_TO.EXPORT)) {
							//adding double quotes in case of export
							typeValue = String.format("%s%s%s", JSONUtil.DOUBLE_QUOTES, typeValue, JSONUtil.DOUBLE_QUOTES);
						}
						row.addValue(typeValue);
						break;
					case geometry_coordinates:
						JSONObject geometry2 = theFeature.getJSONObject("geometry");
						JSONArray coordinates = geometry2.getJSONArray("coordinates");
						
						String toCoordinates = coordinates.toString();
						//is a string, so adding double quotes in case of export
						if(toViewExport.equals(WFS_TO.EXPORT)) {
							//adding double quotes in case of export
							toCoordinates = String.format("%s%s%s", JSONUtil.DOUBLE_QUOTES, toCoordinates, JSONUtil.DOUBLE_QUOTES);
						}
						
						row.addValue(toCoordinates);
						break;
					default:
						String valueString = theFeature.getString(featureToExport.name());
						//adding double quotes in case of export
						String theValue = toViewExport.equals(WFS_TO.EXPORT)?JSONUtil.stringToValueQuoted(valueString):valueString;
						row.addValue(theValue);
						log.debug("Added value: "+theValue+ " to row");
					}
				}
				
				//parse the properties
				FeatureExportViewConfig defaultBehav = exporterMapConfig.get("properties_*");
				
				//if the default config "properties_* is missing I will export all properties found
				boolean exportProperties = defaultBehav==null?true:defaultBehav.isExport();
				log.debug("Default behaviour to export/view the properties is add: "+exportProperties);
	
//				// iterate properties
				JSONObject properties = theFeature.getJSONObject("properties");
				@SuppressWarnings("unchecked")
				Iterator<String> ii = properties.keys();
				while (ii.hasNext()) {
					String key = ii.next();
					String value = properties.optString(key,"");
					String propertykeyConfig = String.format("%s_%s", "properties",key);
					log.trace("Searching config for property: "+propertykeyConfig);
					FeatureExportViewConfig thePropertyConfig = exporterMapConfig.get(propertykeyConfig);
					boolean toAdd = true;
					if(thePropertyConfig!=null) {
						//read specific configuration for the property
						toAdd = thePropertyConfig.isExport();
						log.trace("Specific config for the property "+propertykeyConfig+" is export/view: "+toAdd);
					}else {
						//applying default behavior to export/view the properties
						toAdd = exportProperties;
						log.trace("No specific config found for the property: "+propertykeyConfig+". Applying default export/view: "+toAdd);
					}
					
					if(toAdd) {
//						//Adding the properties name to header row only if it is not already inserted
						if(!headerRow.getListValues().contains(key))
							headerRow.addValue(key);
						
						//adding double quotes in case of export
						String theValue = toViewExport.equals(WFS_TO.EXPORT)?JSONUtil.stringToValueQuoted(value):value;
						row.addValue(theValue);
						log.debug("Added couple ("+key+","+theValue+") to exported properties");
					}
				}
				
				tableFile.addValueRow(row);
				log.info("Added row "+row+" to exported properties. Number of values: "+row.getListValues().size());
			}
			
			log.info("Added header row "+headerRow+". Number of values: "+headerRow.getListValues().size());
			tableFile.setHeaderRow(headerRow);


		} catch (IOException e) {
			log.error("IOException in getTableFromJson for layerItem UUID: "+layerItem.getUUID() + ", name: "+layerItem.getName(),e);
		} catch (JSONException e) {
			log.error("JSONException in getTableFromJson for layerItem UUID: "+layerItem.getUUID() + ", name: "+layerItem.getName(),e);
		}finally{
			IOUtils.closeQuietly(is);
		}

		return tableFile;
	}
	
	/*
	private static void printMap(Map<String, FeatureExportViewConfig> theMap) {
		for (String key : theMap.keySet()) {
			System.out.println("Key: "+key + " - value: "+theMap.get(key));
			
		}
	}*/
	
	/**
	 * Gets the WFS exporter config to.
	 *
	 * @param wfsTo the wfs to
	 * @return the WFS exporter config to
	 */
	public FeatureExporterFileConfig getWFSExporterConfigTo(WFS_TO wfsTo){
		return mapWFSConfig.get(wfsTo);
	}

}
