/*******************************************************************************
 * Copyright 2016 Jalian Systems Pvt. Ltd.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package net.sourceforge.marathon.fx.display;

import java.text.DateFormat;
import java.util.function.Predicate;
import java.util.logging.Logger;

import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.geometry.NodeOrientation;
import javafx.geometry.Orientation;
import javafx.geometry.Side;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Separator;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.control.TextArea;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.ToolBar;
import javafx.scene.control.Tooltip;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.util.Callback;
import net.sourceforge.marathon.api.LogRecord;
import net.sourceforge.marathon.display.IErrorListener;
import net.sourceforge.marathon.fx.api.FXUIUtils;
import net.sourceforge.marathon.fxdocking.DockKey;
import net.sourceforge.marathon.fxdocking.DockKey.TabPolicy;
import net.sourceforge.marathon.fxdocking.Dockable;
import net.sourceforge.marathon.runtime.api.ILogger;

public class LogView extends Dockable {

    public static final Logger LOGGER = Logger.getLogger(LogView.class.getName());

    private DateFormat dateTimeInstance = DateFormat.getDateTimeInstance();

    private VBox logViewLayout = new VBox();
    private TableView<LogRecord> logTable = new TableView<>();
    private ToggleGroup toggleGroup = new ToggleGroup();
    private ToolBar toolBar = new ToolBar();

    private ToggleButton infoButton = FXUIUtils.createToggleButton("info", "Show information messages");
    private ToggleButton warnButton = FXUIUtils.createToggleButton("warn", "Show warning messages");
    private ToggleButton errorButton = FXUIUtils.createToggleButton("error", "Show error messages");
    private Button showMessageButton = FXUIUtils.createButton("show_message", "Show Message");
    private Button clearButton = FXUIUtils.createButton("clear", "Clear Messages");

    private static final DockKey DOCK_KEY = new DockKey("Log", "Record & Playback Log", "Log", FXUIUtils.getIcon("warn"),
            TabPolicy.NotClosable, Side.BOTTOM);

    private ObservableList<LogRecord> logList = FXCollections.observableArrayList();
    private IErrorListener errorListener;

    public LogView() {
        this.logList = FXCollections.observableArrayList();
        initComponents();
    }

    private void initComponents() {
        initToolBar();
        initLogTable();
        logViewLayout.getChildren().addAll(toolBar, logTable);
        VBox.setVgrow(logTable, Priority.ALWAYS);
    }

    private void initToolBar() {
        infoButton.setId("infoButton");
        infoButton.setToggleGroup(toggleGroup);
        infoButton.setTooltip(new Tooltip("Show all messages"));
        infoButton.setOnAction((e) -> {
            FilteredList<LogRecord> filtered = logList.filtered(new Predicate<LogRecord>() {
                @Override
                public boolean test(LogRecord t) {
                    if (t.getType() == ILogger.INFO || t.getType() == ILogger.MESSAGE) {
                        return true;
                    }
                    return false;
                }
            });
            logTable.setItems(filtered);
            logTable.refresh();
        });

        warnButton.setId("warnButton");
        warnButton.setToggleGroup(toggleGroup);
        warnButton.setTooltip(new Tooltip("Show only warnings and errors"));
        warnButton.setOnAction((e) -> {
            FilteredList<LogRecord> filtered = logList.filtered(new Predicate<LogRecord>() {
                @Override
                public boolean test(LogRecord t) {
                    if (t.getType() == ILogger.WARN || t.getType() == ILogger.MESSAGE || t.getType() == ILogger.ERROR) {
                        return true;
                    }
                    return false;
                }
            });
            logTable.setItems(filtered);
            logTable.refresh();
        });

        errorButton.setId("errorButton");
        errorButton.setToggleGroup(toggleGroup);
        errorButton.setTooltip(new Tooltip("Show only errors"));
        errorButton.setOnAction((e) -> {
            FilteredList<LogRecord> filtered = logList.filtered(new Predicate<LogRecord>() {
                @Override
                public boolean test(LogRecord t) {
                    if (t.getType() == ILogger.ERROR || t.getType() == ILogger.MESSAGE) {
                        return true;
                    }
                    return false;
                }
            });
            logTable.setItems(filtered);
            logTable.refresh();
        });
        toggleGroup.selectToggle(infoButton);
        toggleGroup.selectedToggleProperty().addListener((observable, oldValue, newValue) -> {
            if (newValue == null) {
                toggleGroup.selectToggle(oldValue);
            }
        });

        showMessageButton.setId("showMessageButton");
        showMessageButton.setDisable(true);
        showMessageButton.setTooltip(new Tooltip("Show message"));
        showMessageButton.setOnAction((e) -> onShowMessage());
        clearButton.setId("clearButton");
        clearButton.setTooltip(new Tooltip("Clear"));
        if (logList.isEmpty()) {
            clearButton.setDisable(true);
        }
        clearButton.setOnAction((e) -> clear());

        Separator separator = new Separator(Orientation.VERTICAL);
        toolBar.setId("toolBar");
        toolBar.getItems().addAll(clearButton, showMessageButton, separator, errorButton, warnButton, infoButton);
        toolBar.setNodeOrientation(NodeOrientation.RIGHT_TO_LEFT);
    }

    private void onShowMessage() {
        LogRecord selectedItem = logTable.getSelectionModel().getSelectedItem();
        showMessage(selectedItem);
    }

    private void showMessage(LogRecord selectedItem) {
        if (selectedItem.getDescription() != null) {
            String title = "Log @" + dateTimeInstance.format(selectedItem.getDate()) + " >" + selectedItem.getModule();
            MessageStage messageStage = new MessageStage(new MessageInfo(selectedItem.getDescription(), title, new TextArea()));
            messageStage.getStage().showAndWait();
        }
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    private void initLogTable() {
        logTable.setId("logTable");
        TableColumn<LogRecord, Integer> iconColumn = new TableColumn<>("");
        iconColumn.prefWidthProperty().bind(logTable.widthProperty().multiply(0.05));
        iconColumn.setCellValueFactory(new PropertyValueFactory<>("type"));
        iconColumn.setCellFactory(new Callback<TableColumn<LogRecord, Integer>, TableCell<LogRecord, Integer>>() {
            @Override
            public TableCell call(TableColumn<LogRecord, Integer> param) {
                return new IconTableCell();
            }
        });

        TableColumn<LogRecord, String> messageColumn = new TableColumn<>("Message");
        messageColumn.setCellValueFactory(new PropertyValueFactory<>("message"));
        messageColumn.prefWidthProperty().bind(logTable.widthProperty().multiply(0.50));

        TableColumn<LogRecord, String> moduleColumn = new TableColumn<>("Module");
        moduleColumn.setCellValueFactory(new PropertyValueFactory<>("module"));
        moduleColumn.prefWidthProperty().bind(logTable.widthProperty().multiply(0.195));

        TableColumn<LogRecord, String> dateColumn = new TableColumn<>("Date");
        dateColumn.setCellValueFactory(new PropertyValueFactory<>("date"));
        dateColumn.prefWidthProperty().bind(logTable.widthProperty().multiply(0.25));

        logList.addListener(new ListChangeListener<LogRecord>() {
            @Override
            public void onChanged(javafx.collections.ListChangeListener.Change<? extends LogRecord> c) {
                if (logList.size() == 0) {
                    clearButton.setDisable(true);
                } else {
                    clearButton.setDisable(false);
                }
            }
        });

        logTable.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
            if (newValue != null && newValue.getDescription() != null) {
                showMessageButton.setDisable(false);
            } else {
                showMessageButton.setDisable(true);
            }
        });
        logTable.setRowFactory(e -> {
            TableRow<LogRecord> tableRow = new TableRow<>();
            tableRow.setOnMouseClicked(event -> {
                if (event.getClickCount() == 2 && !tableRow.isEmpty()) {
                    LogRecord rowData = tableRow.getItem();
                    if (rowData.getDescription() != null) {
                        showMessage(rowData);
                    }
                }
            });
            return tableRow;
        });

        errorButton.setSelected(true);
        FilteredList<LogRecord> filtered = logList.filtered(new Predicate<LogRecord>() {
            @Override
            public boolean test(LogRecord t) {
                if (t.getType() == ILogger.ERROR || t.getType() == ILogger.MESSAGE) {
                    return true;
                }
                return false;
            }
        });
        logTable.setItems(filtered);
        logTable.refresh();
        logTable.getColumns().addAll(iconColumn, messageColumn, moduleColumn, dateColumn);
    }

    public void clear() {
        logList.clear();
        logTable.refresh();
    }

    public void addLog(LogRecord result) {
        logList.add(result);
        if (errorListener != null && result.getType() == ILogger.ERROR) {
            errorListener.addError(result);
        }
    }

    public ObservableList<LogRecord> getLogList() {
        return logList;
    }

    public void setLogList(ObservableList<LogRecord> logList) {
        this.logList = logList;
    }

    public void setErrorListener(IErrorListener controller) {
        this.errorListener = controller;
    }

    public class IconTableCell extends TableCell<LogRecord, Integer> {

        @Override
        protected void updateItem(Integer item, boolean empty) {
            super.updateItem(item, empty);
            if (item != null) {
                Node value = null;
                if (item == ILogger.INFO) {
                    value = FXUIUtils.getIcon("info");
                } else if (item == ILogger.ERROR) {
                    value = FXUIUtils.getIcon("error");
                } else if (item == ILogger.WARN) {
                    value = FXUIUtils.getIcon("warn");
                } else if (item == ILogger.MESSAGE) {
                    value = null;
                }
                setGraphic(value);
                setText(null);
            }
        }

    }

    @Override
    public DockKey getDockKey() {
        return DOCK_KEY;
    }

    @Override
    public Node getComponent() {
        return logViewLayout;
    }

}