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

import be.iminds.ilabt.jfed.call_log_output.SerializableApiCallDetails;
import be.iminds.ilabt.jfed.highlevel.controller.JavaFXTaskThread;
import be.iminds.ilabt.jfed.log.cache.AllApiCallsList;
import be.iminds.ilabt.jfed.ui.javafx.log_gui.LogHistoryPanel;
import be.iminds.ilabt.jfed.ui.javafx.util.TimeUtils;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.Parent;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;

import javax.inject.Inject;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * TasksPanel
 */
public class TasksPanelController {
    private static final org.slf4j.Logger LOG = org.slf4j.LoggerFactory.getLogger(TasksPanelController.class);

    @FXML
    private LogHistoryPanel logPanel;
    @FXML
    private TaskList allTaskList;
    @FXML
    private VBox taskDetailsPane;
    @FXML
    private TaskList dependingOnTaskList;
    @FXML
    private TaskList dependersTaskList;
    @FXML
    private TextField taskNameField;
    @FXML
    private Parent taskExceptionBox;
    @FXML
    private TextArea taskExceptionArea;
    @FXML
    private TextField taskStateField;
    @FXML
    private TextField startTimeField;
    @FXML
    private TextField stopTimeField;
    @FXML
    private Label durationLabel;
    @FXML
    private Label taskCallsLabel;

    private final JavaFXTaskThread javaFXTaskThread;
    private final AllApiCallsList allApiCallsList;


    private final ObjectProperty<JavaFXTaskThread.FXTaskExecution> selectedSingleTask =
            new SimpleObjectProperty<>(null);
    private ChangeListener<Throwable> updateListener = null;

    @Inject
    TasksPanelController(JavaFXTaskThread javaFXTaskThread, AllApiCallsList allApiCallsList){
        this.javaFXTaskThread = javaFXTaskThread;
        this.allApiCallsList = allApiCallsList;
    }

    @FXML
    private void initialize() {
        taskExceptionBox.managedProperty().bind(taskExceptionBox.visibleProperty());
        taskDetailsPane.visibleProperty().bind(allTaskList.getSelectionModel().selectedItemProperty().isNotNull());

        doAllInit();
    }

    /**
     * init after SfaModel set, and fxml loaded.
     */
    private void doAllInit() {
        assert logPanel != null;

        ObservableList<JavaFXTaskThread.FXTaskExecution> allTasks = javaFXTaskThread.getAllTasks();
        allTaskList.setItems(allTasks);

        final ChangeListener<Date> showTimeListener = (observableValue, date, date1) -> showTime();

        allTaskList.getSelectionModel().selectedItemProperty().addListener((observableValue, oldFXTaskExecution, newFXTaskExecution) -> {
            if (oldFXTaskExecution != null) {
                taskStateField.textProperty().unbind();
                oldFXTaskExecution.runStartDateProperty().removeListener(showTimeListener);
                oldFXTaskExecution.runStopDateProperty().removeListener(showTimeListener);
            }

            selectedSingleTask.set(newFXTaskExecution);

            if (updateListener != null && oldFXTaskExecution != null) {
                oldFXTaskExecution.getException().removeListener(updateListener);
                updateListener = null;
            }

            if (newFXTaskExecution != null) {
                taskNameField.textProperty().set(newFXTaskExecution.getName());
                taskStateField.textProperty().bind(Bindings.convert(newFXTaskExecution.stateProperty()));
                dependingOnTaskList.setItems(newFXTaskExecution.getObservableDependsOn());
                dependersTaskList.setItems(newFXTaskExecution.getObservableDependingOnThis());
                logPanel.setApiCallHistory(newFXTaskExecution.getApiCallHistory());

                updateListener = (observableValue1, oldThrowable, newThrowable) -> updateException(newFXTaskExecution);
                newFXTaskExecution.getException().addListener(updateListener);
                updateException(newFXTaskExecution);

                newFXTaskExecution.runStartDateProperty().addListener(showTimeListener);
                newFXTaskExecution.runStopDateProperty().addListener(showTimeListener);
                showTime();
                taskCallsLabel.setText("Task calls");
            } else {
                taskExceptionBox.setVisible(false);
                dependingOnTaskList.setItems(null);
                dependersTaskList.setItems(null);
                logPanel.clearApiCallHistory();
                logPanel.setApiCallHistory(allApiCallsList.getObservablSerializableCalls());
                taskCallsLabel.setText("All calls");
            }
        });

        if (!allTasks.isEmpty())
            allTaskList.getSelectionModel().selectFirst();
    }

    public void showAllCalls() {
        allTaskList.getSelectionModel().clearSelection();
    }

    public void updateException(JavaFXTaskThread.FXTaskExecution FXTaskExecution) {
        if (FXTaskExecution.getException().get() == null) {
            taskExceptionBox.setVisible(false);
            taskExceptionArea.setText("");
        } else {
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            FXTaskExecution.getException().get().printStackTrace(pw);
            pw.close();
            final String stacktrace = sw.getBuffer().toString();

            taskExceptionBox.setVisible(true);
            taskExceptionArea.setText(stacktrace);
        }
    }

    public void printStackTrace() {
        System.err.println("Stacktrace:\n" + taskExceptionArea.getText());
        LOG.info("User requested print of stacktrace:\n" + taskExceptionArea.getText());
    }

    public void showTime() {
        JavaFXTaskThread.FXTaskExecution task = selectedSingleTask.get();
        if (task != null) {
            Date start = task.runStartDateProperty().get();

            if (start != null) {
                startTimeField.setText(start + "");
            }

            Date stop = task.runStopDateProperty().get();
            if (stop != null) {
                assert (start != null);
                stopTimeField.setText(stop + "");

                long durationMillis = stop.getTime() - start.getTime();
                durationLabel.setText(TimeUtils.formatMillis(durationMillis, TimeUnit.DAYS, TimeUnit.MILLISECONDS));
            }
        }
    }

    public void showTaskExecution(be.iminds.ilabt.jfed.highlevel.controller.TaskExecution taskExecution) {
        JavaFXTaskThread.FXTaskExecution fxTask = taskExecution.getFXTaskExecution();

        allTaskList.getSelectionModel().select(fxTask);
    }

    public boolean showApiCall(int callId) {
        for (JavaFXTaskThread.FXTaskExecution taskExecution : allTaskList.getItems()) {
            for (SerializableApiCallDetails taskApiCall : taskExecution.getApiCallHistory()) {
                if(taskApiCall.getId() == callId){
                    allTaskList.getSelectionModel().select(taskExecution);
                    logPanel.showApiCall(taskApiCall);
                    return true;
                }
            }
        }
        return false;
    }
}
