package be.iminds.ilabt.jfed.experimenter_gui.connectivity_tester;

import be.iminds.ilabt.jfed.connectivity_tester.ConnectivityTest;
import be.iminds.ilabt.jfed.connectivity_tester.ConnectivityTestSuite;
import be.iminds.ilabt.jfed.lowlevel.GeniUserProvider;
import com.google.common.base.Strings;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * User: twalcari
 * Date: 11/20/2014
 * Time: 13:35
 */
@Singleton
public class ConnectivityTester {
    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(ZoneId.systemDefault());
    private static final Logger LOG = LoggerFactory.getLogger(ConnectivityTester.class);

    private final ObservableList<ConnectivityTestTask> testTasks = FXCollections.observableArrayList();
    private final Map<ConnectivityTest, ConnectivityTest.ConnectivityTestResult> testResults = new ConcurrentHashMap<>();
    private final ObjectProperty<Instant> testsStartDate = new SimpleObjectProperty<>();
    private final ObjectProperty<Instant> testsEndDate = new SimpleObjectProperty<>();
    private final BooleanProperty testsActive = new SimpleBooleanProperty(false);
    private final BooleanProperty testsFinished = new SimpleBooleanProperty(false);


    private final GeniUserProvider geniUserProvider;
    private final ConnectivityTestSuite connectivityTestSuite;

    @Inject
    ConnectivityTester(GeniUserProvider geniUserProvider, ConnectivityTestSuite connectivityTestSuite) {
        this.geniUserProvider = geniUserProvider;
        this.connectivityTestSuite = connectivityTestSuite;
    }


    public ObservableList<ConnectivityTestTask> getTestTasks() {
        return testTasks;
    }

    public Map<ConnectivityTest, ConnectivityTest.ConnectivityTestResult> getTestResults() {
        return testResults;
    }

    public Instant getTestsStartDate() {
        return testsStartDate.get();
    }

    public ObjectProperty<Instant> testsStartDateProperty() {
        return testsStartDate;
    }

    public Instant getTestsEndDate() {
        return testsEndDate.get();
    }

    public ObjectProperty<Instant> testsEndDateProperty() {
        return testsEndDate;
    }

    public boolean getTestsActive() {
        return testsActive.get();
    }

    public BooleanProperty testsActiveProperty() {
        return testsActive;
    }

    public boolean startTests() {
        //tests can only be started once!
        if (testsActive.get() || testsFinished.get())
            return false;

        testsActive.setValue(true);
        testsStartDate.setValue(Instant.now());
        testsEndDate.setValue(null);

        //initialize the connectivity-tests in a new thread to improve performance
        new Thread(() -> {
            ExecutorService es = Executors.newFixedThreadPool(5,
                    new ThreadFactoryBuilder().setNameFormat("ConnectivityTester-%d").build());

            //load the tests
            List<ConnectivityTest> tests = connectivityTestSuite.getTests();
            List<ConnectivityTestTask> tasks = new ArrayList<>();

            for (final ConnectivityTest test : tests) {
                if (test.isLoginRequired() &&
                        (geniUserProvider == null ||
                                !geniUserProvider.isUserLoggedIn()))
                    continue;

                final ConnectivityTestTask ctt = new ConnectivityTestTask(test);
                ctt.setOnSucceeded(event -> testResults.put(test, ctt.getValue()));
                tasks.add(ctt);
            }

            Platform.runLater(() -> testTasks.addAll(tasks));

            LOG.info("Starting {} tests", tasks.size());
            tasks.forEach(es::submit);


            //add a last task to the Executor service to enable the restart button
            es.submit(() -> {
                LOG.info("Finished {} tests", tasks.size());
                Platform.runLater(() -> {
                            testsEndDate.setValue(Instant.now());
                            testsActive.setValue(false);
                            testsFinished.setValue(true);
                        }
                );
            });

            es.shutdown();
        }).start();


        return true;
    }

    public boolean getTestsFinished() {
        return testsFinished.get();
    }

    public BooleanProperty testsFinishedProperty() {
        return testsFinished;
    }

    public boolean restartTests() {
        if (testsActive.get())
            return false;

        testTasks.clear();
        testResults.clear();
        testsFinished.set(false);

        startTests();
        return true;
    }

    public void saveResultsToFile(File file) throws IOException {
        //change the name of the experiment definition
        try (FileWriter writer = new FileWriter(file)) {

            writer.write(String.format("========================================%n" +
                            "==  jFed Connectivity Tester Results  ==%n" +
                            "========================================%n%n"
            ));

            writer.write(String.format("Tests started on: %s%n", DATE_TIME_FORMATTER.format(ZonedDateTime.ofInstant(testsStartDate.get(), ZoneId.systemDefault()))));
            writer.write(String.format("Tests ended on: %s%n", DATE_TIME_FORMATTER.format(ZonedDateTime.ofInstant(testsEndDate.get(), ZoneId.systemDefault()))));

            for (Map.Entry<ConnectivityTest, ConnectivityTest.ConnectivityTestResult> entry : testResults.entrySet()) {

                writer.write(String.format("%n%s%n%n", Strings.repeat("-", 30)));
                writer.write(String.format("Test name: %s%n", entry.getKey().getName()));

                ConnectivityTest.ConnectivityTestResult result = entry.getValue();

                if (result != null) {

                    writer.write(String.format("Status: %s%n", result.getStatus()));
                    if (result.getMessage() != null)
                        writer.write(String.format("Message: %s%n", result.getMessage()));
                    if (result.getException() != null)
                        writer.write(String.format("Exception: %s%n", result.getMessage()));

                } else {
                    writer.write(String.format("NO RESULT BECAUSE OF INTERNAL FAILURE%n"));
                }
                writer.flush();
            }


            writer.close();
        } catch (IOException ex) {
            LOG.error("Error while saving connectivity test", ex);
            throw ex;

        }
        LOG.debug("Saved connectivity test results to {}", file);
    }
}
