package jnet.server.probe;

import java.io.IOException;
import java.net.InetAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import jnet.lib.LogFile;
import jnet.lib.Message;
import jnet.lib.Status;
import jnet.lib.object.Event;
import jnet.lib.object.Map;
import jnet.lib.object.MapObject;
import jnet.server.Database;
import jnet.server.Server;
import jnet.server.ServerMessageParser;

public class Ping extends Thread {

    // Perioda opakování v sekundách
    private static final int PERIOD = 3;
    // Timeout pro ping (v milisekundách)
    private static final int TIMEOUT = 2000;
    // pocet pokus pro nastaveni offline statusu
    private static final int ATTEMPT = 3;

    public Ping() {
    }

    @Override
    public void start() {
        init();
    }

    private void init() {
        // ExecutorService pro paralelní provádění pingů
        ExecutorService executorService = Executors.newCachedThreadPool();

        // ScheduledExecutorService pro periodické provádění
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);

        // Naplánování periodického provádění kontrol
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            for (Map map : Server.maps) {
                for (MapObject mo : map.getObjects()) {
                    if (mo.isActive()) {
                        // Vytvoření úkolu pro pingování každé IP adresy
                        executorService.submit(() -> {
                            try {
                                long responseTime = pingWithResponseTime(mo.getIp());
                                if (responseTime >= 0) {
                                    isOnline(mo, responseTime);
                                } else {
                                    isOffline(mo);
                                }
                            } catch (Exception e) {
                                LogFile.printDebug("Chyba pri pingovani " + mo.getIp() + ": " + e.getMessage());
                            }
                        });
                    }

                }
            }
                
        }, 0, PERIOD, TimeUnit.SECONDS);
    }

    // Funkce pro kontrolu dostupnosti IP adresy s timeoutem a zobrazení času odezvy
    private static long pingWithResponseTime(String ipAddress) throws IOException, InterruptedException {
        InetAddress inet = InetAddress.getByName(ipAddress);
        long startTime = System.currentTimeMillis(); // Začátek měření času
        boolean reachable = inet.isReachable(TIMEOUT); // Timeout pro ping 5 sekund
        long endTime = System.currentTimeMillis(); // Konec měření času

        if (reachable) {
            return endTime - startTime; // Vrátí dobu odezvy v milisekundách
        } else {
            return -1; // Pokud není dostupná, vrátí -1
        }
    }

    private void isOnline(MapObject mo, long responseTime) {
        //       LogFile.printDebug(mo.getName() + "(" + mo.getIp() + ") online, latence: " + responseTime + " ms");
        // pokud neni predchozi stav ok odesle novy stav
        if (mo.getStatus() != Status.OK) {
            // pokud byl predchozi stav offline ukonci event 
            if (mo.getStatus() == Status.OFFLINE) {
                // zapise do logu konec udalosti
                Event event = Database.endEvent(mo.getId(), Event.TYPE_PING);
                for (int i = 0; i < Server.events.size(); i++) {
                    if (Server.events.get(i).getId() == event.getId()) {
                        Server.events.set(i, event);
                        ServerMessageParser.sendAll(new Message(Message.EVENT_UPDATE, (Event) event));
                        break;
                    }
                }
                // vynuluje počet pokusu o ping
                mo.setPingAttemp(0);
                // nastavi a odesle stav
                setNewState(mo, Status.OK); 
            }
            // pokud byl predchozi stav N/A
            if (mo.getStatus() == Status.NA) {
                // nastavi a odesle stav
                setNewState(mo, Status.OK);
            }
        }

    }

    private void setNewState(MapObject mo, int state) {
        mo.setStatus(state);
        // odesle stav
        int[] o = {mo.getId(), state};
        ServerMessageParser.sendAll(new Message(Message.STATUS_UPDATE, o));
    }

    private void isOffline(MapObject mo) {
        // zvysi pocet jiz probehlích pokusu o ping
        mo.pingAttemp++;

        // vyhodnoti
        if (mo.getStatus() != Status.OFFLINE && mo.getPingAttemp() >= ATTEMPT) {
            int[] o = {mo.getId(), Status.OFFLINE};
            // nastavi objektu status
            mo.setStatus(Status.OFFLINE);
            // zapise do logu

            // zapise do databaze
            Event event = Database.addEvent(mo.getId(), "offline", Event.TYPE_PING);
            Server.events.add(event);

            //odesle stav a event klientum
            ServerMessageParser.sendAll(new Message(Message.STATUS_UPDATE, o));
            ServerMessageParser.sendAll(new Message(Message.EVENT_NEW, event));
        }
    }

}
