/*
 * Copyright (c) 2005-2010 KOM – Multimedia Communications Lab
 *
 * This file is part of PeerfactSim.KOM.
 * 
 * PeerfactSim.KOM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 * 
 * PeerfactSim.KOM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with PeerfactSim.KOM.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package de.tud.kom.p2psim.impl.topology.movement.smarter;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

import de.tud.kom.p2psim.impl.topology.movement.modularosm.GPSCalculation;
import de.tud.kom.p2psim.impl.topology.movement.modularosm.attraction.IAttractionGenerator;
import de.tud.kom.p2psim.impl.topology.util.PositionVector;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.location.IAttractionPoint;
import de.tudarmstadt.maki.simonstrator.api.util.XMLConfigurableConstructor;

public class DataConverter {
	
	@XMLConfigurableConstructor({ "sqlitefolderPath", "tracefileFolder", "tracefilePrefix" })
	public DataConverter(String sqlitefolderPath, String tracefileFolder, String tracefilePrefix) {
		this.sqliteFolderPath = sqlitefolderPath;
		this.tracefileFolder = tracefileFolder;
		this.tracefilePrefix = tracefilePrefix;
		
		readTraceDatabases();
	}
	
	public static String tracefileFolder = "smarter/tracefiles/";
	public static String tracefilePrefix = "smarterTraceFile-";
	public static String sqliteFolderPath = "smarter/databases/";
	
	public static long start = 10 * 3600 + 30 * 60; // 10:30
	public static long end = 15 * 3600 + 30 * 60; // 15:30
	
	public static void readTraceDatabases() {
		
		List<String> filePaths = DataConverter.getFilePaths(sqliteFolderPath);
		
		try {
			System.out.println(Class.forName("org.sqlite.JDBC"));
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		for (String filePath : filePaths) {			
			createTraceFile(filePath);
		}			
		
	}	
	
	private static void createTraceFile(String filePath) {
		DataGrabber grabber = new DataGrabber();		
		grabber.connect(filePath);
		List<SmarterDBEntry> listOfEntries = grabber.select("SELECT * FROM gps_data");
		grabber.disconnect();		
		
		boolean active = false;
		
		int fileID = Integer.parseInt(filePath.split("\\.")[3]);

		if(listOfEntries.isEmpty()) {
			System.out.println("DataConverter: " + fileID + " has no entries.");
			return;
		}
		
		try {			
			String tracefilePath = tracefileFolder + tracefilePrefix + fileID + ".if";
			
			File yourFile = new File(tracefilePath);
			if(yourFile.exists()) {
				System.out.println("DataConverter: " + tracefilePath + " exists - overwrite");
			}
			
			PrintWriter writer = new PrintWriter(tracefilePath,"UTF-8");
			long lastSimulationtimestamp = -1;
			
			for (SmarterDBEntry entry : listOfEntries) {				
				
				String timestamp = entry.getTimestamp().split(" ")[1];			
				
				String[] values = timestamp.split(":");
				int hours = Integer.parseInt(values[0]);
				int minutes = Integer.parseInt(values[1]);					
				int seconds = Integer.parseInt(values[2]);
				
				// Example: 13:42:10 = 3*3600s + 42*60s + 10*1s
				long simulationtimestamp = hours * 3600 + minutes * 60 + seconds * 1;		
				
				// filter double entries
				if(simulationtimestamp == lastSimulationtimestamp) {					
					continue;
				}
				else {
					lastSimulationtimestamp = simulationtimestamp;
				}
				
				if(simulationtimestamp >= start && simulationtimestamp <= end) {
					double lat = entry.getLatitude();
					double lon = entry.getLongitude();
			
					// if the position in the database is within the simulated area, parse the position to a simulation position and save it 
					if(GPSCalculation.isWithinGPSBoundaries(lat, lon)) {
						PositionVector p = GPSCalculation.transformGPSWindowToOwnWorld(lat, lon);
						
						if(!active) {
							for (IAttractionPoint ap : IAttractionGenerator.attractionPoints) {
								if(p.distanceTo(ap) <= ap.getRadius()) {
									active = true;
								}
							}
						}
						
						if(active) {
							// truncate the date from the timestamp, separate entries and parse to a timestamp containing only the seconds. 
							// as the test started around 10:00, reduce the timestamp by 10 hours						
							if(simulationtimestamp >= start && simulationtimestamp <= end) {								
								simulationtimestamp -= start;							
								writer.println(simulationtimestamp + " " + lat + " " + lon + " " + p.getX() + " " + p.getY());
							}						
						}											
					}	
				}								
			}			
			writer.close();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		
		
	}
	
	/**
	 * Returns a list of full file paths of a given directory
	 * 
	 * @param directory
	 *            The
	 * @return
	 */
	public static ArrayList<String> getFilePaths(String directory) {
		File folder = new File(directory);
		File[] listOfFiles = folder.listFiles();

		ArrayList<String> filePaths = new ArrayList<String>();

		if (listOfFiles.length == 0) {
			System.out.println("No files in directory");
			return null;
		}

		for (int i = 0; i < listOfFiles.length; i++) {
			if (listOfFiles[i].isFile()) {
				
				if(listOfFiles[i].getName().contains(".db")) {
					filePaths.add(directory + listOfFiles[i].getName());
				}
				
			}
		}
		return filePaths;
	}
	
	/*
	 * @author Marcel Verst
	 */
	private static class SmarterDBEntry {
		// All columns of the row
		public int _id;
		double longitude;
		double latitude;
		double altitude;
		double accuracy;
		String timestamp;

		/**
		 * Creates SmarterDBEntry object
		 * 
		 * @param _id
		 *            the id entry
		 * @param longitude
		 *            the longitude entry
		 * @param latitude
		 *            the latitude entry
		 * @param altitude
		 *            the altitude entry
		 * @param accuracy
		 *            the accuracy entry
		 * @param timestamp
		 *            the timestamp entry
		 */
		public SmarterDBEntry(int _id, double longitude, double latitude, double altitude, double accuracy, String timestamp) {
			super();
			this._id = _id;
			this.longitude = longitude;
			this.latitude = latitude;
			this.altitude = altitude;
			this.accuracy = accuracy;
			this.timestamp = timestamp;
		}

		/**
		 * Print entry
		 * 
		 * @return String containing all entries of the row
		 */
		@Override
		public String toString() {
			return "DBEntry [_id=" + _id + ", longitude=" + longitude + ", latitude=" + latitude + ", altitude=" + altitude
					+ ", accuracy=" + accuracy + ", timestamp=" + timestamp + "]";
		}

		/**
		 * Gets the ID
		 * 
		 * @return _id
		 */
		public int get_id() {
			return _id;
		}

		/**
		 * Gets the longitude
		 * 
		 * @return longitude
		 */
		public double getLongitude() {
			return longitude;
		}
		
		/**
		 * Gets the latitude
		 * 
		 * @return latitude
		 */
		public double getLatitude() {
			return latitude;
		}

		/**
		 * Gets the altitude
		 * 
		 * @return altitude
		 */
		public double getAltitude() {
			return altitude;
		}
		
		/**
		 * Gets the accuracy
		 * 
		 * @return accuracy of GPS signal
		 */
		public double getAccuracy() {
			return accuracy;
		}
	
		/**
		 * Gets the timestamp
		 * 
		 * @return timestamp
		 */
		public String getTimestamp() {
			return timestamp;
		}

		
	}
	
	/**
	 * DataGrabber class is responsible for establishing a connection to a database
	 * and to query the data
	 * 
	 * @author Marcel Verst
	 * @version 26.05.2018
	 */
	private static class DataGrabber {
		// The connection to the database.
		private Connection conn;

		/**
		 * DataGrabber constructor, initializes the connection
		 */
		public DataGrabber() {
			conn = null;
		}

		/**
		 * Connect to a database specified in params.properties Code from
		 * "http://www.sqlitetutorial.net/sqlite-java/sqlite-jdbc-driver/"
		 * 
		 * @throws SQLException
		 *             if the connection cannot be established
		 */
		public void connect(String filePath) {
			try {
				// db parameters
				String url = "jdbc:sqlite:" + filePath;
				// create a connection to the database				
				conn = DriverManager.getConnection(url);
			} catch (SQLException e) {
				System.out.println(e.getMessage());
			}
		}

		/**
		 * Disconnects database
		 * 
		 * @throws SQLException
		 *             if disconnection failed
		 */
		public void disconnect() {
			try {
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}

		/**
		 * Queries the database with a given query string, stores the values within an
		 * ArrayList and returns it for further analyzes
		 * 
		 * @param selection
		 *            The SQL query
		 * @return listOfEntries ArrayList object containing DBEntry types
		 * @throws SQLException
		 *             if there are problems with connecting to the database
		 */
		public ArrayList<SmarterDBEntry> select(String selection) {
			ArrayList<SmarterDBEntry> listOfEntries = new ArrayList<SmarterDBEntry>();
			try (Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(selection)) {

				// loop through the result set
				while (rs.next()) {
					int _id = rs.getInt("_id");
					double longitude = rs.getDouble("longitude");
					double latitude = rs.getDouble("latitude");
					double altitude = rs.getDouble("altitude");
					double accuracy = rs.getDouble("accuracy");
					String timestamp = rs.getString("timestamp");

					SmarterDBEntry entry = new SmarterDBEntry(_id, longitude, latitude, altitude, accuracy, timestamp);
					listOfEntries.add(entry);
				}
			} catch (SQLException e) {
				System.out.println(e.getMessage());
			}
			return listOfEntries;
		}
	}
}
