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

import be.iminds.ilabt.jfed.experimenter_gui.events.OpenRequestRspecFromInputStreamEvent;
import be.iminds.ilabt.jfed.ui.javafx.GlyphUtils;
import be.iminds.ilabt.jfed.ui.javafx.dialogs.JFDialogs;
import be.iminds.ilabt.jfed.ui.javafx.util.StageUtils;
import com.github.rjeschke.txtmark.Configuration;
import com.github.rjeschke.txtmark.Processor;
import com.google.common.eventbus.EventBus;
import javafx.beans.InvalidationListener;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.IntegerBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.concurrent.Worker;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebHistory;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import org.controlsfx.glyphfont.FontAwesome;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.events.Event;
import org.w3c.dom.events.EventListener;
import org.w3c.dom.events.EventTarget;
import org.w3c.dom.html.HTMLAnchorElement;

import javax.annotation.Nullable;
import java.io.ByteArrayInputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Objects;

/**
 * User: twalcari
 * Date: 3/11/2016
 * Time: 15:06
 */
public class TutorialDetailView extends BorderPane {
    private static final Logger LOG = LoggerFactory.getLogger(TutorialDetailView.class);
    private static final String CLICK_EVENT = "click";
    private static final String ANCHOR_TAG = "a";

    private final EventBus eventBus;

    private final Tutorial tutorial;

    private final VBox headerTextPanel;
    private final Label titleLabel;
    private final Button homeButton;
    private final Button backButton;
    private final Button nextButton;


    private final WebView webView;
    private final WebEngine webEngine;

    private final StackPane contentPane;
    private final HBox loadingHBox;
    private final ProgressBar loadingProgressBar;
    private final Label loadingLabel;

    private final HBox filesHBox;
    private final String content;
    private final BooleanProperty tutorialIsShowing = new SimpleBooleanProperty(false);
    private final WebHistory history;


    public TutorialDetailView(Tutorial tutorial, EventBus eventBus) {
        this.eventBus = eventBus;
        this.tutorial = tutorial;

        getStylesheets().add(getClass().getResource("tutorial.css").toExternalForm());

        /*
        Top panel
         */
        this.headerTextPanel = new VBox(5);
        headerTextPanel.getStyleClass().add("header-panel");
        setTop(headerTextPanel);

        this.titleLabel = new Label();
        this.titleLabel.getStyleClass().add("title-label");
        headerTextPanel.getChildren().add(titleLabel);
        titleLabel.setText(tutorial.getTitle());


        /*
        Bottom file panel
         */

        this.filesHBox = new HBox(15);
        filesHBox.getStyleClass().add("footer-panel");
        final Label filesAvailableLabel = new Label("Available files:");
        this.filesHBox.getChildren().add(filesAvailableLabel);
        setBottom(filesHBox);

        if (tutorial.getFiles() != null && !tutorial.getFiles().isEmpty()) {
            for (Tutorial.TutFileWithContents tutFileWithContents : tutorial.getFiles()) {

                Button fileButton = new Button(tutFileWithContents.getName());
                fileButton.setGraphic(GlyphUtils.createGlyph(FontAwesome.Glyph.FOLDER_OPEN));
                fileButton.setOnAction(event -> openTutFile(tutFileWithContents));

                fileButton.setDisable(!Objects.equals(tutFileWithContents.getType(), "rspec"));

                filesHBox.getChildren().add(fileButton);
            }
        } else {
            filesAvailableLabel.setText("No files available");
        }

        this.contentPane = new StackPane();
        contentPane.setAlignment(Pos.BOTTOM_CENTER);
        setCenter(contentPane);


        webView = new WebView();
        this.webEngine = webView.getEngine();
        this.history = webEngine.getHistory();

        this.loadingHBox = new HBox(10);
        loadingHBox.setMaxHeight(20);
        loadingHBox.setAlignment(Pos.BOTTOM_LEFT);
        loadingHBox.setStyle("-fx-background-color: gray; -fx-opacity: 0.9;");

        this.loadingProgressBar = new ProgressBar();
        loadingProgressBar.setPrefWidth(150);

        this.loadingLabel = new Label("Loading ...");

        loadingHBox.getChildren().addAll(loadingProgressBar, loadingLabel);

        contentPane.getChildren().addAll(webView, loadingHBox);

        loadingProgressBar.progressProperty().bind(
                webEngine.getLoadWorker().progressProperty());

        loadingHBox.visibleProperty().bind(
                webEngine.getLoadWorker().runningProperty());

        webEngine.setConfirmHandler(param -> new Alert(Alert.AlertType.CONFIRMATION, param)
                .showAndWait()
                .filter(buttonType -> buttonType == ButtonType.YES).isPresent());

        webEngine.setOnAlert(text ->
                new Alert(Alert.AlertType.WARNING, text.toString()).show());

        webEngine.setOnError(error -> {
            LOG.info("Received ERROR from webview: {}", error.getMessage(), error.getException());

            JFDialogs.create()
                    .title("Error during tutorial")
                    .showException(error.getException());

        });

        webEngine.getLoadWorker().exceptionProperty().addListener(observable1 -> {
            final Throwable exception = webEngine.getLoadWorker().getException();
            LOG.warn("Tutorial webview has error: {}",
                    exception != null ? exception.getMessage() : null,
                    exception);

            JFDialogs.create()
                    .title("Error in webpage")
                    .message("jFed encountered an error while loading the login-webpage: " + exception.getMessage())
                    .showException(exception);
        });


        loadingLabel.textProperty().bind(webEngine.getLoadWorker().messageProperty());


        webEngine.getLoadWorker().stateProperty().addListener((observable, oldValue, newValue) -> {
            if (newValue == Worker.State.SUCCEEDED) {

                // note next classes are from org.w3c.dom domain
                EventListener listener = ev -> System.out.println(ev.getType());

                Document doc = webEngine.getDocument();
                NodeList nl = doc.getElementsByTagName(ANCHOR_TAG);
                for (int i = 0; i < nl.getLength(); i++) {
                    Node node = nl.item(i);
                    EventTarget eventTarget = (EventTarget) node;

                    eventTarget.addEventListener(CLICK_EVENT, TutorialDetailView.this::handleClick, false);
                }
            }
        });

        final String tutorialText = Processor.process(tutorial.getText(), Configuration.builder().forceExtentedProfile().build());

        content =
                "<html><head><link rel='stylesheet' href='" + getClass().getResource("markdown.css").toExternalForm() + "'/></head><body class='markdown-body'>" +
                        tutorialText +
                        "</body></html>";

        webEngine.locationProperty().addListener(obs -> {
            if (webEngine.getLocation() != null)
                tutorialIsShowing.set(false);
        });
        showTutorialText();

        HBox buttonsBox = new HBox(5);

        this.homeButton = new Button();
        homeButton.setGraphic(GlyphUtils.createGlyph(FontAwesome.Glyph.HOME).size(18));
        homeButton.setOnAction(event -> showTutorialText());
        homeButton.setPrefWidth(35);
        homeButton.setPrefHeight(35);


        this.backButton = new Button();
        backButton.setGraphic(GlyphUtils.createGlyph(FontAwesome.Glyph.ARROW_LEFT).size(18));
        backButton.disableProperty().bind(tutorialIsShowing);
        backButton.setOnAction(event -> {
            if (history.getCurrentIndex() > 0) {
                history.go(-1);
            } else {
                showTutorialText();
            }
        });
        backButton.setPrefWidth(35);
        backButton.setPrefHeight(35);

        this.nextButton = new Button();
        nextButton.setGraphic(GlyphUtils.createGlyph(FontAwesome.Glyph.ARROW_RIGHT).size(18));
        final IntegerBinding historySize = Bindings.size(history.getEntries());
        nextButton.disableProperty().bind(
                tutorialIsShowing.or(
                historySize.isEqualTo(0).or(history.currentIndexProperty().isEqualTo(historySize.subtract(1)))));
        nextButton.setOnAction(event -> history.go(1));
        nextButton.setPrefWidth(35);
        nextButton.setPrefHeight(35);

        buttonsBox.getChildren().addAll(homeButton, backButton, nextButton);
        headerTextPanel.getChildren().add(buttonsBox);


        history.currentIndexProperty().addListener(obs -> LOG.info("New index: {}", history.getCurrentIndex()));

        history.getEntries().addListener((InvalidationListener) obs -> LOG.info("New number of entries: {}, index: {}", history.getEntries().size(), history.getCurrentIndex()));
    }

    private void showTutorialText() {
        webEngine.loadContent(content);
        tutorialIsShowing.set(true);
    }

    private void openTutFile(Tutorial.TutFileWithContents tutFile) {
        byte[] contentBytes = tutFile.getContent().trim().getBytes();
        eventBus.post(new OpenRequestRspecFromInputStreamEvent(new ByteArrayInputStream(contentBytes), contentBytes.length, tutFile.getName()));
    }


    private void handleClick(Event event) {
        HTMLAnchorElement anchorElement = (HTMLAnchorElement) event.getCurrentTarget();
        String href = anchorElement.getHref();

        if (tutorial.getFiles() == null)
            return;

        try {
            URI uri = new URI(href);
            if (Objects.equals("tutfile", uri.getScheme())) {

                String schemeSpecificPart = uri.getSchemeSpecificPart();
                if (schemeSpecificPart.startsWith("//")) {
                    schemeSpecificPart = schemeSpecificPart.substring(2);
                }

                for (Tutorial.TutFileWithContents tutfile : tutorial.getFiles()) {
                    if (Objects.equals(schemeSpecificPart, tutfile.getName())) {
                        openTutFile(tutfile);
                        event.preventDefault();
                        return;
                    }
                }
            }

        } catch (URISyntaxException e) {
            LOG.error("Could not parse given URI in tutorial: {}", href, e);
        }
    }

    public void showDialog(@Nullable DialogActionsProvider dialogActionsProvider) {
        Scene scene = new Scene(this, 800, 600);

        Stage tutorialStage = new Stage();
        StageUtils.setJFedStageIcons(tutorialStage);
        tutorialStage.setTitle(tutorial.getTitle());
        tutorialStage.setScene(scene);

        if (dialogActionsProvider != null) {
            //hide tutorial descriptions when tutorial detail window is showing
            tutorialStage.setOnShowing(event -> dialogActionsProvider.hideDialog());
            //show tutorial descriptions again when the tutorial detail window is closed
            tutorialStage.setOnHidden(event -> dialogActionsProvider.showDialog());
        }

        tutorialStage.show();
    }
}
