package leigh.notek.app.javafx;

import com.dansoftware.mdeditor.MarkdownEditorControl;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import jfxtras.styles.jmetro.JMetro;
import jfxtras.styles.jmetro.Style;
import leigh.mecha.log.MechaLogger;
import leigh.mecha.log.MechaLoggerFactory;
import leigh.mecha.util.UniversalJob;
import leigh.notek.store.ZettelContext;
import leigh.notek.store.ZettelFilesStore;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.queryparser.classic.ParseException;

import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.function.Consumer;

public class NotekApp extends Application {
    private static final MechaLogger logger = MechaLoggerFactory.getLogger(NotekApp.class);
    private final TabPane tabPane = new TabPane();
    private ZettelContext ctx;
    private ListView<String> zettelListView;
    private TextField searchBox;

    public NotekApp() throws IOException {
        // Intentionally Blank
    }

    private void styleUI(Scene scene) {
        new JMetro(Style.DARK).setScene(scene);
        scene.getStylesheets().add(getClass().getResource("/mdfx-dark.css").toExternalForm());
    }

    public void init() throws IOException {
        Parameters parameters = getParameters();
        if (parameters.getUnnamed().size() < 1) {
            logger.info("Usage: notek /path/to/archive");
            System.exit(UniversalJob.RET_BADARGS);
        }

        String path = parameters.getUnnamed().get(0);
        ctx = new ZettelContext(new ZettelFilesStore(Path.of(path)));
    }

    @Override
    public void start(Stage primaryStage) throws IOException {
        primaryStage.setTitle("NOTEK mk1");

        Menu fileMenu = new Menu("Zettel");
        MenuItem newNote = new MenuItem("New Zettel");
        newNote.setAccelerator(new KeyCodeCombination(KeyCode.N, KeyCombination.META_DOWN));
        newNote.setOnAction(event -> {
            try {
                newNote(false);
            } catch (IOException e) {
                e.printStackTrace();
            }
        });

        MenuItem insertNote = new MenuItem("Insert New Zettel");
        insertNote.setAccelerator(new KeyCodeCombination(KeyCode.N, KeyCombination.SHIFT_DOWN, KeyCombination.META_DOWN));
        insertNote.setOnAction(event -> {
            try {
                newNote(true);
            } catch (IOException e) {
                e.printStackTrace();
            }
        });

        MenuItem deleteNote = new MenuItem("Delete Zettel");
        deleteNote.setAccelerator(new KeyCodeCombination(KeyCode.D, KeyCombination.META_DOWN));
        deleteNote.setOnAction(event -> {
            try {
                deleteNote();
            } catch (IOException e) {
                e.printStackTrace();
            }
        });

        fileMenu.getItems().addAll(newNote, insertNote, deleteNote);

        Menu editMenu = new Menu("Edit");
        MenuItem copyLink = new MenuItem("Copy Link");
        // FIXME This is going to be wrong for windows
        copyLink.setAccelerator(new KeyCodeCombination(KeyCode.C, KeyCombination.SHIFT_DOWN, KeyCombination.META_DOWN));
        copyLink.setOnAction(event -> {
            Tab t = tabPane.getSelectionModel().getSelectedItem();
            if (t != null) {
                TabData td = (TabData) t.getUserData();
                String link = "[[" + td.getId() + "]]";
                Clipboard clipboard = Clipboard.getSystemClipboard();
                ClipboardContent cc = new ClipboardContent();
                cc.put(DataFormat.PLAIN_TEXT, link);
                clipboard.setContent(cc);
            }
        });
        editMenu.getItems().add(copyLink);

        Menu windowMenu = new Menu("Window");
        MenuItem closeCurrentTab = new MenuItem("Close Active Tab");
        closeCurrentTab.setAccelerator(new KeyCodeCombination(KeyCode.W, KeyCombination.META_DOWN));
        closeCurrentTab.setOnAction(event -> {
            Tab t = tabPane.getSelectionModel().getSelectedItem();
            if (t != null) {
                tabPane.getTabs().remove(t);
            }
        });

        MenuItem closeAllTabs = new MenuItem("Close All Tabs");
        closeAllTabs.setAccelerator(new KeyCodeCombination(KeyCode.W, KeyCombination.SHIFT_DOWN,
                KeyCombination.META_DOWN));
        closeAllTabs.setOnAction(event -> {
            Tab t = tabPane.getSelectionModel().getSelectedItem();
            if (t != null) {
                tabPane.getTabs().clear();
            }
        });

        windowMenu.getItems().addAll(closeCurrentTab, closeAllTabs);

        MenuBar menuBar = new MenuBar(fileMenu, editMenu, windowMenu);

        VBox rootPane = new VBox();
        searchBox = new TextField();
        searchBox.setOnKeyTyped(event -> {
            String terms = searchBox.getText();
            try {
                if (StringUtils.isEmpty(terms)) {
                    ctx.setFilter(null);
                } else {
                    ctx.setFilter(terms.trim());
                }
                updateList();
            } catch (IOException | ParseException e) {
                e.printStackTrace();
            }
        });

        SplitPane bp = new SplitPane();
        ObservableList<String> noteTitles = FXCollections.observableArrayList();
        VBox westPanel = new VBox();
        zettelListView = new ListView<>(noteTitles);
        zettelListView.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
                // If we lost the selection, we do not want to change the open tab.
                if (newValue == null) return;

                // If we gained the selection, we want to set the current tab to that zettel.
                try {
                    Tab t = tabPane.getSelectionModel().getSelectedItem();
                    if (t == null) {
                        t = new Tab();
                        tabPane.getTabs().add(t);
                    }
                    loadTab(t, newValue);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });

        Button addZettelBtn = new Button("+");
        addZettelBtn.setOnMouseClicked(event -> {
            try {
                newNote(false);
            } catch (IOException e) {
                e.printStackTrace();
            }
        });

        Button delZettelBtn = new Button("-");
        delZettelBtn.setOnMouseClicked(event -> {
            try {
                deleteNote();
            } catch (IOException e) {
                e.printStackTrace();
            }
        });

        zettelListView.setPrefHeight(5000);

        westPanel.getChildren().addAll(new ToolBar(addZettelBtn, delZettelBtn), zettelListView);

        bp.getItems().addAll(westPanel, tabPane);
        bp.setDividerPosition(0, .20);
        SplitPane.setResizableWithParent(westPanel, false);

        updateList();
        rootPane.getChildren().addAll(menuBar, searchBox, bp);

        Scene scene = new Scene(rootPane, 800, 600);
        styleUI(scene);
        primaryStage.setScene(scene);

        primaryStage.show();
    }

    private void deleteNote() throws IOException {
        String id = zettelListView.getSelectionModel().getSelectedItem();
        ctx.delId(id);
        updateList();
        ArrayList<Tab> toRemove = new ArrayList<>();
        for (Tab t : tabPane.getTabs()) {
            TabData data = (TabData) t.getUserData();
            if (data.getId().equals(id)) {
                toRemove.add(t);
            }
        }
        tabPane.getTabs().removeAll(toRemove);
    }

    private void newNote(boolean insertLinkIntoCurrent) throws IOException {
        String id = ctx.newId();
        if (insertLinkIntoCurrent) {
            Tab t = tabPane.getSelectionModel().getSelectedItem();
            if (t != null) {
                MarkdownEditorControl mec = (MarkdownEditorControl) t.getContent();
                mec.insertText("[[" + id + "]]");
            }
        }
        selectOrOpenTab(id);
        updateList();
    }

    public void loadTab(Tab tab, String id) throws IOException {
        // We preserve the control to keep the view mode / etc.
        NotekMarkdownEditorControl oldControl = (NotekMarkdownEditorControl) tab.getContent();
        NotekMarkdownEditorControl control = new NotekMarkdownEditorControl(new NotekTransformer());
        if (oldControl != null) {
            control.setViewMode(oldControl.getViewMode());
        }

        tab.setText(id);
        String zettel = ctx.getStore().getZettel(id);

        control.setToolbarVisible(true); // setting the toolbar visible on the top
        control.setMarkdown(zettel == null ? "" : zettel); // setting an initial text
        control.setOnLinkClicked(new Consumer<String>() {
            @Override
            public void accept(String s) {
                if (s.startsWith("#")) {
                    searchBox.setText(s);

                    try {
                        ctx.setFilter(s.trim());
                        updateList();
                    } catch (IOException | ParseException e) {
                        e.printStackTrace();
                    }
                } else if (s.startsWith("http")) {
                    getHostServices().showDocument(s.trim());
                } else {
                    try {
                        // TODO: Select it if we already have it
                        selectOrOpenTab(s.trim());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                }
            }
        });

        tab.setContent(control);
        tab.setUserData(new TabData(id));

        control.markdownProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
                try {
                    ctx.setZettel(id, newValue);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    private void selectOrOpenTab(String id) throws IOException {
        for (Tab t : tabPane.getTabs()) {
            TabData td = (TabData) t.getUserData();
            if (td.getId().equals(id)) {
                tabPane.getSelectionModel().select(t);
                return;
            }
        }

        Tab t = new Tab();
        loadTab(t, id);
        tabPane.getTabs().add(t);
        tabPane.getSelectionModel().select(t);
    }

    public void updateList() throws IOException {
        ArrayList<String> noteIds = new ArrayList<>();
        ctx.getIds(noteIds);
        ObservableList<String> lst = zettelListView.getItems();
        lst.clear();
        lst.addAll(noteIds);
    }
}
