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

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.ILoggingEvent;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.css.PseudoClass;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.text.Text;
import javafx.util.Duration;

import java.text.SimpleDateFormat;

import static ch.qos.logback.classic.spi.ThrowableProxyUtil.asString;

/**
 * User: twalcari
 * Date: 7/1/2015
 * Time: 10:58
 */
class LogView extends ListView<ILoggingEvent> {
    private static final int MAX_ENTRIES = 10_000;

    private final static PseudoClass trace = PseudoClass.getPseudoClass("trace");
    private final static PseudoClass debug = PseudoClass.getPseudoClass("debug");
    private final static PseudoClass info = PseudoClass.getPseudoClass("info");
    private final static PseudoClass warn = PseudoClass.getPseudoClass("warn");
    private final static PseudoClass error = PseudoClass.getPseudoClass("error");

    private final static SimpleDateFormat timestampFormatter = new SimpleDateFormat("HH:mm:ss.SSS");

    private final BooleanProperty showTimestamp = new SimpleBooleanProperty(true);
    private final BooleanProperty showLevel = new SimpleBooleanProperty(false);
    private final BooleanProperty showThreadname = new SimpleBooleanProperty(false);
    private final ObjectProperty<Level> filterLevel = new SimpleObjectProperty<>(null);
    private final StringProperty textFilter = new SimpleStringProperty();
    private final BooleanProperty tail = new SimpleBooleanProperty(false);
    private final BooleanProperty paused = new SimpleBooleanProperty(false);
    private final DoubleProperty refreshRate = new SimpleDoubleProperty(60);

    private final ObservableList<ILoggingEvent> logItems = FXCollections.observableArrayList();

    public BooleanProperty showTimeStampProperty() {
        return showTimestamp;
    }

    public boolean getShowLevel() {
        return showLevel.get();
    }

    public BooleanProperty showLevelProperty() {
        return showLevel;
    }

    public boolean getShowThreadname() {
        return showThreadname.get();
    }

    public BooleanProperty showThreadnameProperty() {
        return showThreadname;
    }

    public boolean getShowTimestamp() {
        return showTimestamp.get();
    }

    public BooleanProperty showTimestampProperty() {
        return showTimestamp;
    }

    public ObjectProperty<Level> filterLevelProperty() {
        return filterLevel;
    }

    public String getTextFilter() {
        return textFilter.get();
    }

    public StringProperty textFilterProperty() {
        return textFilter;
    }

    public void setTextFilter(String textFilter) {
        this.textFilter.set(textFilter);
    }

    public BooleanProperty tailProperty() {
        return tail;
    }

    public BooleanProperty pausedProperty() {
        return paused;
    }

    public DoubleProperty refreshRateProperty() {
        return refreshRate;
    }

    public LogView(LogViewAppender logViewAppender) {
        getStyleClass().add("log-view");

        Timeline logTransfer = new Timeline(
                new KeyFrame(
                        Duration.seconds(1),
                        event -> {
                            logViewAppender.drainTo(logItems);

                            if (logItems.size() > MAX_ENTRIES) {
                                logItems.remove(0, logItems.size() - MAX_ENTRIES);
                            }

                            if (tail.get()) {
                                scrollTo(logItems.size());
                            }
                        }
                )
        );
        logTransfer.setCycleCount(Timeline.INDEFINITE);
        logTransfer.rateProperty().bind(refreshRateProperty());

        this.pausedProperty().addListener((observable, oldValue, newValue) -> {
            if (newValue && logTransfer.getStatus() == Animation.Status.RUNNING) {
                logTransfer.pause();
            }

            if (!newValue && logTransfer.getStatus() == Animation.Status.PAUSED && getParent() != null) {
                logTransfer.play();
            }
        });

        this.parentProperty().addListener((observable, oldValue, newValue) -> {
            if (newValue == null) {
                logTransfer.pause();
            } else {
                if (!paused.get()) {
                    logTransfer.play();
                }
            }
        });

        filterLevel.addListener(observable -> refreshView());
        textFilter.addListener(observable -> refreshView());
        filterLevel.set(Level.DEBUG);

        setCellFactory(param -> new ListCell<ILoggingEvent>() {

            final Text text = new Text();

            {
                showTimestamp.addListener(observable -> updateItem(this.getItem(), this.isEmpty()));

                text.getStyleClass().add("log-event");
                text.wrappingWidthProperty().bind(widthProperty());

                setGraphic(text);

                prefWidthProperty().bind(LogView.this.widthProperty().subtract(3));
            }

            @Override
            protected void updateItem(ILoggingEvent item, boolean empty) {
                super.updateItem(item, empty);

                if (item == null || empty) {
                    text.setText(null);
                    return;
                }

                StringBuilder sb = new StringBuilder();

                if (showTimestamp.get()) {
                    sb.append(timestampFormatter.format(item.getTimeStamp())).append(" ");
                }

                if (showThreadname.get()) {
                    sb.append("[").append(item.getThreadName()).append("] ");
                }

                if (showLevel.get()) {
                    sb.append(item.getLevel().toString()).append("\t ");
                }

                if (item.getLoggerName() != null) {
                    sb.append(item.getLoggerName()).append(" ");
                }

                sb.append(item.getFormattedMessage());

                if (item.getThrowableProxy() != null) {
                    sb.append("\n").append(asString(item.getThrowableProxy()));
                }

                text.setText(sb.toString());


                text.pseudoClassStateChanged(trace, item.getLevel() == Level.TRACE);
                text.pseudoClassStateChanged(debug, item.getLevel() == Level.DEBUG);
                text.pseudoClassStateChanged(info, item.getLevel() == Level.INFO);
                text.pseudoClassStateChanged(warn, item.getLevel() == Level.WARN);
                text.pseudoClassStateChanged(error, item.getLevel() == Level.ERROR);
            }
        });

        showTimestamp.addListener(observable -> refreshView());
        showThreadname.addListener(observable -> refreshView());
        showLevel.addListener(observable -> refreshView());
    }

    private void refreshView() {
        setItems(null);
        setItems(
                new FilteredList<>(
                        logItems,
                        loggingEvent -> {
                            //check level

                            if (!loggingEvent.getLevel().isGreaterOrEqual(filterLevel.get()))
                                return false;

                            if (textFilter.get().isEmpty()) {
                                return true;
                            } else {
                                return loggingEvent.getLoggerName().toLowerCase().contains(textFilter.get().toLowerCase())
                                        || loggingEvent.getFormattedMessage().toLowerCase().contains(textFilter.get().toLowerCase());
                            }
                        }
                )
        );
    }
}
