/*
 * ROMAN DIGITAL - Autor: Américo Patetta
 */
package com.teltonikaServer.network;

import com.teltonikaServer.lib.Utils;
import com.teltonikaServer.persistence.AppLogger;
import com.teltonikaServer.persistence.Persistor;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.logging.Level;

public class TCPServer {

    public static int IMM_GATEWAY = 1;

    public static int VERBOSITY = 1;
    public static final int VERB_SEVERE = 1;
    public static final int VERB_WARNING = 2;
    public static final int VERB_INFO = 4;
    public static final int VERB_FINE = 8;

    public static final int PAUSE = 200;

    // Red
    private static int PORT = 2020;
    private static int NUM_THREADS = 128;
    public static int BUFFER_SIZE = 4096;
    static int CLIENT_SOCKET_READ_TIMEOUT = 15000;
    private static int SERVER_SOCKET_READ_TIMEOUT = 15000;

    // DBMS
    private static String HOST = "";
    private static String SCHEMA = "";
    private static String USER = "";
    private static String PASS = "";
    public static Persistor persistor;
    // Commands

    public static Map<String, CommandList> commands = new HashMap<>();
    private static int messageId = -1;
    public static FileWriter errorLogFile;
    public static FileWriter serverLogFile;

    public static int threadCount = 0;

    // MISC
    // private static final String ERRORLOG = "DELETEerrorlog.txt";
    static String ORIGIN;
    public static final int METER = 1;
    public static final int KILOMETER = 1000;

    // LOG
    public static AppLogger serverLog = new AppLogger("server.log");
    static String SIGNATURE = "DEF01";

    public static void main(String[] args)
            throws ClassNotFoundException, SQLException, InstantiationException, IllegalAccessException, IOException {

        
        System.out.println("TeltonikaServerIotMetrix");
        System.out.println("Version: 7.0.1");
        
        
        
        
        serverLog.logEvent(Level.INFO, "Application start");
        String configFile = (args.length == 1) ? args[0] : "config.txt";
        loadConfig(configFile);
        System.out.println("Listeninig port: "+PORT);
        openPersistor();

        // GESTIÓN DE CONEXIONES TCP/IP
        ThreadPoolExecutor executorService = (ThreadPoolExecutor) Executors.newFixedThreadPool(NUM_THREADS);
        try (ServerSocket serverSocket = new ServerSocket(PORT)) {
            TCPServer.serverLog.logEvent(Level.SEVERE, "Server started on port " + PORT);
            while (true) {
                try {
                    Socket clientSocket = serverSocket.accept();
                    if (executorService.getActiveCount() < NUM_THREADS) {
                        try {
                            incThreadCnt();
                            executorService.submit(new ThreadHandler(clientSocket, executorService.getActiveCount()));
                        } catch (Exception exception) {

                            TCPServer.serverLog.logEvent(Level.SEVERE,
                                    "Error al disparar thread " + exception.getMessage() + "/"
                                            + exception.getCause());

                        }
                    } else {

                        TCPServer.serverLog.logEvent(Level.WARNING,
                                "Cuota de conexiones superada (" + NUM_THREADS + ")");

                        clientSocket.close();
                    }
                } catch (IOException io) {
                    TCPServer.serverLog.logEvent(Level.SEVERE, "ThreadCnt: " + executorService.getActiveCount());
                    persistor.checkConnection();
                }
            }
        } catch (IOException e) {
            TCPServer.serverLog.logEvent(Level.SEVERE,
                    "ERROR 001: NO ES POSIBLE ABRIR UN SERVER SOCKET" + e.getMessage());
        }
        TCPServer.errorLogFile.close();
    }

    private static void openPersistor() {
        try {
            persistor = new Persistor(HOST, SCHEMA, USER, PASS);
        } catch (ClassNotFoundException classNotFoundException) {
            classNotFoundException.printStackTrace();
            System.exit(-1);
        } catch (IllegalAccessException illegalAccessException) {
            illegalAccessException.printStackTrace();
            System.exit(-2);
        } catch (InstantiationException instantiationException) {
            instantiationException.printStackTrace();
            System.exit(-3);
        } catch (SQLException sqlException) {
            sqlException.printStackTrace();
            System.exit(-4);
        }
    }

    public static int getPort() {
        return TCPServer.PORT;
    }

    public static String getOrigin() {
        return ORIGIN + ":" + PORT;
    }

    private static void loadConfig(String configFile) {

        String aux;
        FileInputStream inputStream;

        try {
            inputStream = new FileInputStream(configFile);
            Properties properties = new Properties();
            properties.load(inputStream);

            // Misc
            try {
                aux = properties.getProperty("Verbosity").trim();
                TCPServer.VERBOSITY = (aux == null) ? TCPServer.VERBOSITY : Integer.parseInt(aux);
            } catch (Exception e) {
                TCPServer.serverLog.logEvent(Level.SEVERE, "ATENCIÓN: No es posible establecer el valor de VERBOSITY");
            }

            // Puerto
            try {
                aux = properties.getProperty("Port").trim();
                TCPServer.PORT = (aux == null) ? TCPServer.PORT : Integer.parseInt(aux);
            } catch (Exception e) {
                TCPServer.serverLog.logEvent(Level.SEVERE,
                        "ATENCIÓN: No es posible establecer el valor del puerto de escucha (Port) a través del archivo de configuración");
            }

            // Timeout del servidor (Tiempo de espera para una nueva conexión).
            try {
                aux = properties.getProperty("ServerTimeout").trim();
                TCPServer.SERVER_SOCKET_READ_TIMEOUT = (aux == null) ? TCPServer.SERVER_SOCKET_READ_TIMEOUT
                        : Integer.parseInt(aux);
            } catch (Exception e) {
                TCPServer.serverLog.logEvent(Level.SEVERE,
                        "ATENCIÓN: No es posible establecer el valor de tiempo de espera del servidor (ServerTimeout) a través del archivo de configuración");
            }

            // Timeout del cliente (Tiempo de espera del thread para leer)
            try {
                aux = properties.getProperty("ClientTimeout").trim();
                TCPServer.CLIENT_SOCKET_READ_TIMEOUT = (aux == null) ? TCPServer.CLIENT_SOCKET_READ_TIMEOUT
                        : Integer.parseInt(aux);
            } catch (Exception e) {
                TCPServer.serverLog.logEvent(Level.SEVERE,
                        "ATENCIÓN: No es posible establecer el valor de tiempo de espera del cliente (ClientTimeout) a través del archivo de configuración");
            }

            // Tamaño del buffer
            try {
                aux = properties.getProperty("BufferSize").trim();
                TCPServer.BUFFER_SIZE = (aux == null) ? TCPServer.BUFFER_SIZE : Integer.parseInt(aux);
            } catch (Exception e) {
                TCPServer.serverLog.logEvent(Level.SEVERE,
                        "ATENCIÓN: No es posible establecer el tamaño del buffer (BufferSize) a través del archivo de configuración");
            }

            // Cantidad de threads
            try {
                aux = properties.getProperty("Threads").trim();
                TCPServer.NUM_THREADS = (aux == null) ? TCPServer.NUM_THREADS : Integer.parseInt(aux);
            } catch (Exception e) {
                TCPServer.serverLog.logEvent(Level.SEVERE,
                        "ATENCIÓN: No es posible establecer la cantidad de hilos (Threads) a través del archivo de configuración");
            }
            try {
                aux = properties.getProperty("Origin").trim();
                TCPServer.ORIGIN = (aux == null) ? TCPServer.ORIGIN : aux;
            } catch (Exception e) {
                TCPServer.serverLog.logEvent(Level.SEVERE,
                        "ATENCIÓN: No es posible establecer el origen a través del archivo de configuración");
            }

            // DBMS
            try {
                aux = properties.getProperty("Host").trim();
                TCPServer.HOST = (aux == null) ? TCPServer.HOST : aux;
            } catch (Exception e) {
                TCPServer.serverLog.logEvent(Level.SEVERE,
                        "ATENCIÓN: No es posible establecer el host a través del archivo de configuración");
            }
            try {
                aux = properties.getProperty("Schema").trim();
                TCPServer.SCHEMA = (aux == null) ? TCPServer.SCHEMA : aux;
            } catch (Exception e) {
                TCPServer.serverLog.logEvent(Level.SEVERE,
                        "ATENCIÓN: No es posible establecer el esquema a través del archivo de configuración");
            }
            try {
                aux = properties.getProperty("User").trim();
                TCPServer.USER = (aux == null) ? TCPServer.USER : aux;
            } catch (Exception e) {
                TCPServer.serverLog.logEvent(Level.SEVERE,
                        "ATENCIÓN: No es posible establecer la cantidad de hilos (Threads) a través del archivo de configuración");
            }
            try {
                aux = properties.getProperty("Password").trim();
                TCPServer.PASS = (aux == null) ? TCPServer.PASS : aux;
            } catch (Exception e) {
                TCPServer.serverLog.logEvent(Level.SEVERE,
                        "ATENCIÓN: No es posible establecer la contraseña a través del archivo de configuración");
            }
            try {
                aux = properties.getProperty("Signature").trim();
                TCPServer.SIGNATURE = (aux == null) ? TCPServer.SIGNATURE : aux;
            } catch (Exception e) {
                TCPServer.serverLog.logEvent(Level.SEVERE,
                        "ATENCIÓN: No es posible establecer la firma (signature) a través del archivo de configuración");
            }

            try {
                inputStream.close();
            } catch (IOException ex) {
                TCPServer.serverLog.logEvent(Level.SEVERE,
                        "ATENCIÓN: No es posible establecer la contraseña a través del archivo de configuración");
            }

        } catch (IOException e) {
            TCPServer.serverLog.logEvent(Level.SEVERE, "ATENCIÓN: No es posible encontrar el archivo de configuración '"
                    + configFile + "' se asumen los valores por defecto");
        }
    }

    public static String getMessageId() {
        messageId = (messageId < 0xFFFF) ? messageId + 1 : 0;
        return Utils.zeroPad(Integer.toHexString(messageId), 4);
    }

    public static long incThreadCnt() {
        threadCount++;
        return threadCount;
    }

    public static long decThreadCnt() {
        threadCount--;
        return threadCount;
    }

    public static long getThreadCnt() {
        return threadCount;
    }
}
