package be.iminds.ilabt.jfed.experimenter_gui.slice.errors;

import be.iminds.ilabt.jfed.call_log_output.SerializableApiCallDetails;
import be.iminds.ilabt.jfed.experiment.tasks.TaskExecutionFinishedListener;
import be.iminds.ilabt.jfed.experimenter_gui.call_gui.TasksWindow;
import be.iminds.ilabt.jfed.experimenter_gui.util.BrowserUtil;
import be.iminds.ilabt.jfed.highlevel.controller.Task;
import be.iminds.ilabt.jfed.highlevel.controller.TaskExecution;
import be.iminds.ilabt.jfed.highlevel.tasks.StatusTask;
import be.iminds.ilabt.jfed.log.cache.ApiCallDetailsCache;
import be.iminds.ilabt.jfed.log.cache.ApiCallDetailsRef;
import be.iminds.ilabt.jfed.lowlevel.api_wrapper.ErrorDetails;
import be.iminds.ilabt.jfed.lowlevel.api_wrapper.StatusDetails;
import be.iminds.ilabt.jfed.ui.javafx.GlyphUtils;
import be.iminds.ilabt.jfed.ui.javafx.util.BindingsUtil;
import be.iminds.ilabt.jfed.util.GeniUrn;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ListChangeListener;
import javafx.concurrent.Service;
import javafx.scene.control.*;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.impl.client.DefaultHttpClient;
import org.controlsfx.glyphfont.FontAwesome;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

/**
 * User: twalcari
 * Date: 7/2/2014
 * Time: 15:49
 */
public class ErrorsView extends ListView<ErrorDetails> implements TaskExecutionFinishedListener {
    private static final Logger LOG = LoggerFactory.getLogger(ErrorsView.class);
    private static final Set<String> IGNORABLE_GENI_RESPONSE_VALUES;

    private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    static {
        IGNORABLE_GENI_RESPONSE_VALUES = new HashSet<>();
        IGNORABLE_GENI_RESPONSE_VALUES.add("");
        IGNORABLE_GENI_RESPONSE_VALUES.add("0");
    }

    private final BooleanProperty newItemsAvailable = new SimpleBooleanProperty(false);

    {
        getItems().addListener((ListChangeListener<ErrorDetails>) change -> {
            while (change.next()) {
                if (change.wasAdded()) {
                    newItemsAvailable.set(true);
                }
            }
        });

        setCellFactory(errorDetailsListView -> new ErrorDetailsListCell());

        setPrefHeight(100);
    }

    private final TasksWindow tasksWindow;
    private final ApiCallDetailsCache apiCallDetailsCache;
    private final BrowserUtil browserUtil;


    @Inject
    public ErrorsView(TasksWindow tasksWindow, ApiCallDetailsCache apiCallDetailsCache, BrowserUtil browserUtil) {
        this.tasksWindow = tasksWindow;
        this.apiCallDetailsCache = apiCallDetailsCache;
        this.browserUtil = browserUtil;
    }

    public boolean getNewItemsAvailable() {
        return newItemsAvailable.get();
    }

    public BooleanProperty newItemsAvailableProperty() {
        return newItemsAvailable;
    }

    public void setNewItemsAvailable(boolean newItemsAvailable) {
        this.newItemsAvailable.set(newItemsAvailable);
    }

    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
    @Override
    public void onTaskExecutionFinished(TaskExecution<?> taskExecution) {
        final Task task = taskExecution.getTask();
        //TODO extract sliverUrn and componentUrn from tasks
        GeniUrn sliverUrn = null;
        GeniUrn componentUrn = null;

        boolean errorFoundInCall = false;

        for (ApiCallDetailsRef apiCallDetailsRef : taskExecution.getApiCallHistory()) {
            final SerializableApiCallDetails details = apiCallDetailsCache.getApiCallDetails(apiCallDetailsRef);

            if (details.getGeniResponseCodeIsSuccess() != null
                    && !details.getGeniResponseCodeIsSuccess()
                    && details.getGeniResponseOutput() != null) {

                StringBuilder message = new StringBuilder();
                message.append(task.getName()).append(": ");

                if (details.getXmlRpcGeniResponseValue() != null &&
                        !IGNORABLE_GENI_RESPONSE_VALUES.contains(details.getXmlRpcGeniResponseValue())) {
                    message.append(details.getXmlRpcGeniResponseValue());
                    message.append(" --- ");
                }
                message.append(details.getGeniResponseOutput());


                Platform.runLater(() -> getItems().add(new ApiCallErrorDetails(
                        message.toString(), sliverUrn, componentUrn, taskExecution, apiCallDetailsRef)));
                errorFoundInCall = true;
            }
        }

        if (task instanceof StatusTask) {
            StatusTask statusTask =
                    (StatusTask) task;
            final StatusDetails statusDetails = statusTask.getStatusDetails();

            if (!statusDetails.getAllErrors().isEmpty()) {
                Platform.runLater(() -> getItems().addAll(statusDetails.getAllErrors()));
            }
        }


        if (!errorFoundInCall && taskExecution.getException() != null) {
            Platform.runLater(() -> getItems().add(new ApiCallErrorDetails(task.getName() + ": " + taskExecution.getException(), taskExecution, null)));
        }


    }

    private class ErrorDetailsListCell extends ListCell<ErrorDetails> {
        private final ErrorUrlInspectionService errorUrlInspectionService = new ErrorUrlInspectionService();

        private final Label label = new Label();
        private final Button goToErrorWebpageButton = new Button();
        private final Button goToCallButton = new Button();

        private final HBox container = new HBox(5);


        {

            label.setWrapText(true);

            goToErrorWebpageButton.managedProperty().bind(goToErrorWebpageButton.visibleProperty());
            goToErrorWebpageButton.setTooltip(new Tooltip("Go to authority error information page"));
            goToErrorWebpageButton.setGraphic(GlyphUtils.createGlyph(FontAwesome.Glyph.EYE));
            goToErrorWebpageButton.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
            goToErrorWebpageButton.setOnAction(event -> {
                assert errorUrlInspectionService.getValue() != null;
                browserUtil.openUrlInBrowser(errorUrlInspectionService.getValue());
            });
            goToErrorWebpageButton.visibleProperty().bind(errorUrlInspectionService.valueProperty().isNotNull());
            goToErrorWebpageButton.setMinWidth(35);

            goToCallButton.managedProperty().bind(goToCallButton.visibleProperty());
            goToCallButton.setTooltip(new Tooltip("View call details"));
            goToCallButton.setGraphic(GlyphUtils.createGlyph(FontAwesome.Glyph.DASHBOARD));
            goToCallButton.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
            goToCallButton.visibleProperty().bind(BindingsUtil.instanceOfBinding(itemProperty(), ApiCallErrorDetails.class));
            goToCallButton.setOnAction(event -> {
                ApiCallErrorDetails apiCallErrorDetails = (ApiCallErrorDetails) getItem();
                if (apiCallErrorDetails.getApiCallDetailsRef() != null) {
                    tasksWindow.showApiCall(apiCallErrorDetails.getApiCallDetailsRef());
                } else {
                    tasksWindow.showTaskExecution(apiCallErrorDetails.getTaskExecution());
                }
            });
            goToCallButton.setMinWidth(35);

            //container.maxWidthProperty().bind(ErrorsView.this.widthProperty());
            //container.prefWidthProperty().bind(ErrorsView.this.widthProperty().subtract(15));

            Pane fillerPane = new Pane();
            HBox.setHgrow(fillerPane, Priority.SOMETIMES);
            HBox.setHgrow(label, Priority.ALWAYS);
            HBox.setHgrow(goToErrorWebpageButton, Priority.NEVER);
            HBox.setHgrow(goToCallButton, Priority.NEVER);

            label.maxWidthProperty()
                    .bind(container.widthProperty()
                            .subtract(goToErrorWebpageButton.widthProperty())
                            .subtract(goToCallButton.widthProperty())
                            .subtract(container.spacingProperty().multiply(Bindings.size(container.getChildren())))
                            .subtract(5));

            container.getChildren().addAll(label, fillerPane, goToErrorWebpageButton, goToCallButton);

            setGraphic(container);
        }

        @Override
        protected void updateItem(ErrorDetails errorDetails, boolean b) {
            super.updateItem(errorDetails, b);

            errorUrlInspectionService.cancel();
            errorUrlInspectionService.reset();

            if (errorDetails != null) {
                StringBuilder sb = new StringBuilder();
                sb.append(DATE_FORMAT.format(new Date(errorDetails.getTimestamp())));
                sb.append(" ");
                if (errorDetails.getSliverUrn() != null) {
                    sb.append("[");
                    sb.append(errorDetails.getSliverUrn().getEncodedResourceName());
                    sb.append("] ");
                }

                if (errorDetails.getComponentUrn() != null) {
                    sb.append("{");
                    sb.append(errorDetails.getComponentUrn().getEncodedResourceName());
                    sb.append("} ");
                }

                sb.append(errorDetails.getError());


                label.setText(sb.toString());

                //determine if goToErrorWebpageButton is visible
                URL errorUrl = null;
                if (errorDetails instanceof ApiCallErrorDetails) {
                    ApiCallErrorDetails apiCallErrorDetails = (ApiCallErrorDetails) errorDetails;

                    if (apiCallErrorDetails.getApiCallDetailsRef() != null) {
                        SerializableApiCallDetails apiCallDetails =
                                apiCallDetailsCache.getApiCallDetails(apiCallErrorDetails.getApiCallDetailsRef());

                        if (apiCallDetails.getProtogeniSpewLogUrl() != null && !apiCallDetails.getProtogeniSpewLogUrl().trim().isEmpty()) {
                            try {
                                errorUrlInspectionService.setUrl(new URL(apiCallDetails.getProtogeniSpewLogUrl()));
                                errorUrlInspectionService.start();
                            } catch (MalformedURLException e) {
                                LOG.error("Cannot parse ProtogeniSpewLogUrl '{}'", apiCallDetails.getProtogeniSpewLogUrl(), e);
                            }
                        }
                    }
                }
            } else {
                label.setText("");
            }
        }

    }

    private static final class ErrorUrlInspectionService extends Service<URL> {
        private final HttpClient httpClient = new DefaultHttpClient();
        private final ObjectProperty<URL> url = new SimpleObjectProperty<>();


        public void setUrl(URL url) {
            this.url.set(url);
        }

        @Override
        protected javafx.concurrent.Task<URL> createTask() {
            return new javafx.concurrent.Task<URL>() {
                @Override
                protected URL call() throws Exception {
                    URL errorUrl = url.get();

                    try {
                        HttpHead errorHead = new HttpHead(errorUrl.toURI());
                        HttpResponse response = httpClient.execute(errorHead);
                        if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                            LOG.trace("Got status-code {} when retrieving ProtogeniSpewLogUrl '{}'. " +
                                            "Showing go-to-webpage-button.",
                                    response.getStatusLine().getStatusCode(), errorUrl);
                            return errorUrl;
                        } else {
                            LOG.info("Got status-code {} when retrieving ProtogeniSpewLogUrl '{}'. " +
                                            "Not showing go-to-webpage-button.",
                                    response.getStatusLine().getStatusCode(), errorUrl);
                            return null; //given url does not give a valid result
                        }
                    } catch (URISyntaxException | IOException e) {
                        LOG.error("Something went wrong while querying the ProtogeniSpewLogUrl", e);
                        return null;
                    }
                }
            };
        }

        public URL getUrl() {
            return url.get();
        }
    }

}
