package lac.contextnet.sddl;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;

import lac.cnclib.sddl.message.ApplicationMessage;
import lac.cnclib.sddl.serialization.Serialization;
import lac.cnet.sddl.objects.ApplicationObject;
import lac.cnet.sddl.objects.Message;
import lac.cnet.sddl.objects.PrivateMessage;
import lac.cnet.sddl.udi.core.SddlLayer;
import lac.cnet.sddl.udi.core.UniversalDDSLayerFactory;
import lac.cnet.sddl.udi.core.UniversalDDSLayerFactory.SupportedDDSVendors;
import lac.cnet.sddl.udi.core.listener.UDIDataReaderListener;

import org.ini4j.Ini;

import com.infopae.model.SDDLLocation;


public class SDDLServer implements UDIDataReaderListener<ApplicationObject> {
	
	/*the sddl vender supported*/
    private SupportedDDSVendors supportedDDSVendor;

    /*The SDDL Layer : DDS Abstraction*/
    private static SddlLayer sddlLayer;
    
    /*gateway id*/
	private static List<UUID> gatewayId  = new ArrayList<UUID>();
	
	/*mobile node id*/
	private static List<UUID> nodeId  = new ArrayList<UUID>();
	
	private static List<SDDLLocation> sendList  = new ArrayList<SDDLLocation>();

    /*Database Params*/
    private Connection connection;
    private PreparedStatement pst;
    
    private String engine;
    private String vendor;
    private String jdbcDriver;
    private String schema;
    private String username;
    private String password;
    private String ipAddress;
    private String port;
	
	private SDDLLocation loc;
	private static Timer timer;

	public SDDLServer () {
		
		System.out.println("SDDLServer: starting...");
		
		/*read configuration file*/
		System.out.println("SDDLServer: reading configuration file...");
		readConfigurationFile();
		
	    /*create the SDDL layer with a Subscriber listener*/
		System.out.println("SDDLServer: initializing DDS and SDDL...");
	    sddlLayer = UniversalDDSLayerFactory.getInstance(supportedDDSVendor);
	    sddlLayer.createParticipant(UniversalDDSLayerFactory.CNET_DOMAIN);
	    sddlLayer.createPublisher();
	    sddlLayer.createSubscriber();
	    Object receiveTopic = sddlLayer.createTopic(Message.class, Message.class.getSimpleName());
	    Object sendTopic = sddlLayer.createTopic(PrivateMessage.class, PrivateMessage.class.getSimpleName());
	    sddlLayer.createDataReader(this, receiveTopic);
	    sddlLayer.createDataWriter(sendTopic);

	    /*connect to the database from the configuration file*/
//	    System.out.println("SDDLServer: connecting to the database...");
//	    connectToDatabase();
	}
	
	
	/* MAIN */
	public static void main(String[] args) {
		new SDDLServer();
		System.out.println("SDDLServer: started successfully.");

		timer = new Timer();
		timer.scheduleAtFixedRate( 
		        new TimerTask() {
		            @Override
		            public void run() {
		            	/*create a private message*/
					    PrivateMessage pMsg = new PrivateMessage();
					    
					    /*create a application message with the MESSAGE*/
					    ApplicationMessage appMsg = new ApplicationMessage();
					    
					    appMsg.setContentObject((Serializable) sendList);
					    
					    /*assign the private message the application message to be sent to mobile node*/
					    pMsg.setMessage(Serialization.toProtocolMessage(appMsg));
					    
					    if(gatewayId.size() > 0 && sendList.size() > 0){
					    	for(int i = 0;i < gatewayId.size();i++){
							    pMsg.setGatewayId(gatewayId.get(i));
							    pMsg.setNodeId(nodeId.get(i));
							    
							    /*write topic to DDS*/
							    sddlLayer.writeTopic(PrivateMessage.class.getSimpleName(), pMsg);
					    	}
					    }
		            }
		        }, 
		        1000,
		        10000
		);
		
		try {
			while(true) {
				/*print on screen the input message*/
				System.out.print("Escreva a mensagem: ");
				
				/*create and get the input from console*/
				BufferedReader bufferRead = new BufferedReader(new InputStreamReader(System.in));
			    String inputMsg = bufferRead.readLine();
			    
			    /*create a private message*/
			    PrivateMessage pMsg = new PrivateMessage();
			    
			    /*create a application message with the MESSAGE*/
			    ApplicationMessage appMsg = new ApplicationMessage();
			    appMsg.setContentObject(inputMsg);
			    
			    /*assign the private message the application message to be sent to mobile node*/
			    pMsg.setMessage(Serialization.toProtocolMessage(appMsg));
			    
			    if(gatewayId.size() > 0){
			    	for(int i = 0;i < gatewayId.size();i++){
					    pMsg.setGatewayId(gatewayId.get(i));
					    pMsg.setNodeId(nodeId.get(i));
					    
					    /*write topic to DDS*/
					    sddlLayer.writeTopic(PrivateMessage.class.getSimpleName(), pMsg);
			    	}
			    }
			    
				
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void onNewData(ApplicationObject topicSample) {
		Message msg = null;
		if (topicSample instanceof Message) {
			msg = (Message) topicSample;
			
			Serializable s = Serialization.fromJavaByteStream(msg.getContent());
			if (s instanceof SDDLLocation) {
				loc = (SDDLLocation) s;
				int index = -1;
				for (SDDLLocation obj : sendList) {
		               if(obj.getUuid().equalsIgnoreCase(loc.getUuid())){
		                   index = sendList.indexOf(obj);
		                   break;
		               }
		        }
				if(index == -1)
					sendList.add(loc);
				else{
					sendList.remove(index);
					sendList.add(loc);
				}
				
				//insertToDatabase();
				/*debug*/
				System.out.println("\n>> "+loc.toString());
				System.out.print("Escreva a mensagem: ");
			}
			if (s instanceof String) {
				String mensagem = (String) s;
				if (mensagem.equals("ack")) {
					/*get the GatewayID and NodeID (only one is supported)*/
					gatewayId.add(msg.getGatewayId());
					nodeId.add(msg.getSenderId());
				}
				else if (mensagem.equals("disconnect")){
					int index = -1;
					for (SDDLLocation obj : sendList) {
			               if(obj.getUuid().equalsIgnoreCase(msg.getSenderId().toString())){
			                   index = sendList.indexOf(obj);
			                   break;
			               }
			        }
					
					if(index != -1)
						sendList.remove(index);
				
					gatewayId.remove(msg.getGatewayId());
					nodeId.remove(msg.getSenderId());
					System.out.println("\nMensagem: "+(String)s);
					System.out.print("Escreva a mensagem: ");					
				}
			}
			if (s instanceof UUID) {
				UUID uuid = (UUID) s;
			    String test = s.toString();
			    PrivateMessage pMsg = new PrivateMessage();
			    
			    ApplicationMessage appMsg = new ApplicationMessage();
			    
			    appMsg.setContentObject(msg.getSenderId().toString());

			    pMsg.setMessage(Serialization.toProtocolMessage(appMsg));
			    
			    int index = 0;
			    for (UUID list : nodeId) {
	                if(list.toString().equalsIgnoreCase(test))
	                    break;
	                
	                index++;
	            }
			    pMsg.setGatewayId(gatewayId.get(index));
			    pMsg.setNodeId(nodeId.get(index));
			    sddlLayer.writeTopic(PrivateMessage.class.getSimpleName(), pMsg);
			    
			}
		}
	}
	
	private void readConfigurationFile () {
		/*reading the configuration file (config.ini)*/
        try {
                File iniFile = new File("config.ini");
                Ini ini = new Ini(iniFile);

                /*get the database engine to use*/
                engine = ini.get("default", "engine");
                if (engine == null)
                        throw new Exception("Missing `engine` from [default].");
                                
                /*check if there is a section with this engine*/
                if(!ini.containsKey(engine))
                	throw new Exception("Missing engine section: ["+engine+"].");

                /*check for the sddl vendor*/
                vendor = ini.get("sddllayer", "sddl_vendor");
                if (vendor == null)
                        throw new Exception("Missing `sddl_vendor` from [sddllayer].");

                if (!vendor.equals("CoreDX")
                && !vendor.equals("RTI")
                && !vendor.equals("OpenSplice"))
                        throw new Exception("Unsupported sddl vendor: "+vendor+", choose between `CoreDX`, `RTI` or `OpenSplice`.");

                if (vendor.equals("CoreDX"))
                        supportedDDSVendor = SupportedDDSVendors.CoreDX;
                else if (vendor.equals("RTI"))
                        supportedDDSVendor = SupportedDDSVendors.RTI;
                else if (vendor.equals("OpenSplice"))
                        supportedDDSVendor = SupportedDDSVendors.OpenSplice;

                /*test for all required parameters*/
                jdbcDriver = ini.get(engine, "jdbc_driver");
                if (jdbcDriver == null)
                        throw new Exception("Missing `jdbc_driver` from engine ["+engine+"].");
                if (!jdbcDriver.equals("postgresql")
                && !jdbcDriver.equals("mysql"))
                        throw new Exception("JDBC Driver supported only `postgresql` and `mysql`.");

                schema = ini.get(engine, "schema");
                if (schema == null)
                        throw new Exception("Missing `schema` from engine ["+engine+"].");

                username = ini.get(engine, "username");
                if (username == null)
                        throw new Exception("Missing `username` from engine ["+engine+"].");

                password = ini.get(engine, "password");
                if (password == null)
                        throw new Exception("Missing `password` from engine ["+engine+"].");

                ipAddress = ini.get(engine, "ip_address");
                if (ipAddress == null)
                        throw new Exception("Missing `ip_address` from engine ["+engine+"].");

                port = ini.get(engine, "port");
                if (port == null)
                        throw new Exception("Missing `port` from engine ["+engine+"].");
                
        } catch (IOException e) {
                System.out.println("Unable to read file `config.ini`, it exists?");
                System.exit(1);
        } catch (Exception e) {
                System.out.println(e.getMessage());
                System.exit(1);
        }
	}
	
	@SuppressWarnings("unused")
	private void connectToDatabase () {
		/*reading the configuration file (config.ini)*/
        try {
                                /*connect to the database*/
                String jdbc;
                if (jdbcDriver.equals("postgresql")) {
                        jdbc = "jdbc:postgresql://"+ipAddress+":"+port+"/"+schema+"?user="+username+"&password="+password;
                }
                else if (jdbcDriver.equals("mysql")) {
                        Class.forName("com.mysql.jdbc.Driver");
                        jdbc = "jdbc:mysql://"+ipAddress+":"+port+"/"+schema+"?user="+username+"&password="+password;
                }
                else
                        throw new Exception("JDBC Driver fatal error, is null ?");
                connection = DriverManager.getConnection(jdbc);
                if (connection == null)
                        throw new Exception("Unable to connect to engine: "+engine+", is the database server running ?");

        } catch (IOException e) {
                System.out.println("Unable to read file `config.ini`, it exists?");
                System.exit(1);
        } catch (Exception e) {
                System.out.println(e.getMessage());
                System.exit(1);
        }
	}
	
	@SuppressWarnings("unused")
	private void insertToDatabase () {
		Thread t = new Thread (new Runnable() {
			@Override
			public void run() {
				/*debug*/
				String pstInsert = "insert into sddl_location("
						+ "  id"
						+ ", uuid"
						+ ", latitude"
						+ ", longitude"
						+ ", created_at"
						+ ", accuracy"
						+ ", provider"
						+ ", speed"
						+ ", bearing"
						+ ", altitude"
						+ ", connection_type"
						+ ", battery_percent"
						+ ", is_charging)"
						+ " VALUES (DEFAULT,?,?,?,?,?,?,?,?,?,?,?,?);";
				try {
					/*prepare the statement to be inserted into the database*/
					pst = connection.prepareStatement(pstInsert);
					pst.setString   (1, loc.getUuid());
					pst.setDouble   (2, loc.getLatitude());
					pst.setDouble   (3, loc.getLongitude());
					pst.setTimestamp(4, new Timestamp(loc.getDatetime().getTime()));
					pst.setFloat    (5, loc.getAccuracy());
					pst.setString   (6, loc.getProvider());
					pst.setFloat    (7, loc.getSpeed());
					pst.setFloat    (8, loc.getBearing());
					pst.setDouble   (9, loc.getAltitude());
					pst.setString   (10,loc.getConnectionType());
					pst.setDouble   (11, loc.getBatteryPercent());
					pst.setBoolean  (12, loc.isCharging());
					int result = pst.executeUpdate();
					/*check if it was inserted*/
					if (result > 0) {
						System.out.println("Line inserted into database...");
					}
					else {
						System.out.println("NOT Inserted into database...");
					}
				} catch (SQLException e) {
					e.printStackTrace();
					Logger lgr = Logger.getLogger(SDDLServer.class.getName());
					lgr.log(Level.SEVERE, e.getMessage(), e);
				}
			}
		});
		t.start();
	}
}
