/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package jace.ide;

import jace.JaceApplication;
import jace.ide.Program.DocumentType;
import jace.ide.Program.Option;
import java.io.File;
import java.net.URL;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
import javafx.collections.ListChangeListener;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.ToolBar;
import javafx.scene.web.WebView;
import javafx.stage.FileChooser;

/**
 * FXML Controller class
 *
 * @author blurry
 */
public class IdeController {

    @FXML // ResourceBundle that was given to the FXMLLoader
    private ResourceBundle resources;

    @FXML // URL location of the FXML file that was given to the FXMLLoader
    private URL location;

    @FXML // fx:id="saveMenuItem"
    private MenuItem saveMenuItem; // Value injected by FXMLLoader

    @FXML // fx:id="saveAllMenuItem"
    private MenuItem saveAllMenuItem; // Value injected by FXMLLoader

    @FXML // fx:id="saveAsMenuItem"
    private MenuItem saveAsMenuItem; // Value injected by FXMLLoader

    @FXML // fx:id="closeMenuItem"
    private MenuItem closeMenuItem; // Value injected by FXMLLoader

    @FXML // fx:id="closeAllMenuItem"
    private MenuItem closeAllMenuItem; // Value injected by FXMLLoader

    @FXML // fx:id="editMenu"
    private Menu editMenu; // Value injected by FXMLLoader

    @FXML // fx:id="runMenu"
    private Menu runMenu; // Value injected by FXMLLoader

    @FXML // fx:id="autocompile"
    private ToggleGroup autocompile; // Value injected by FXMLLoader

    @FXML // fx:id="tabPane"
    private TabPane tabPane; // Value injected by FXMLLoader

    @FXML // fx:id="statusBar"
    private ToolBar statusBar; // Value injected by FXMLLoader

    @FXML
    void onCloseAllClicked(ActionEvent event) {
        openDocuments.forEach((Tab t, Program proxy) -> {
            if (!event.isConsumed()) {
                closeTab(t, event);
            }
        });
    }

    @FXML
    void onCloseClicked(ActionEvent event) {
        getCurrentTab().ifPresent((t) -> closeTab(t, event));
    }

    public Optional<Tab> getCurrentTab() {
        return Optional.ofNullable(tabPane.getSelectionModel().getSelectedItem());
    }

    public Optional<Program> getCurrentProgram() {
        return getCurrentTab().map(t -> openDocuments.get(t));
    }

    @FXML
    void newApplesoftBasicClicked(ActionEvent event) {
        Program tab = createTab(DocumentType.applesoft, null);
    }

    @FXML
    void newAssemblyListingClicked(ActionEvent event) {
        createTab(DocumentType.assembly, null);
    }

    @FXML
    void newHexdataClicked(ActionEvent event) {
        createTab(DocumentType.hex, null);
    }

    @FXML
    void newPlainTextClicked(ActionEvent event) {
        createTab(DocumentType.plain, null);
    }

    Map<Tab, Program> openDocuments = new HashMap<>();
    Map<Option, Object> globalOptions = new EnumMap<>(Option.class);

    private Program createTab(DocumentType type, File document) {
        WebView editor = new WebView();
        Program proxy = new Program(type, globalOptions);
        proxy.initEditor(editor, document);
        Tab t = new Tab(proxy.getName(), editor);
        tabPane.getTabs().add(t);
        openDocuments.put(t, proxy);
        t.setOnCloseRequest(this::handleCloseTabRequest);
        return proxy;
    }

    private void handleCloseTabRequest(Event e) {
        Tab t = (Tab) e.getTarget();
        closeTab(t, e);
    }

    private void closeTab(Tab t, Event e) {
        tabPane.getTabs().remove(t);
        openDocuments.remove(t);
    }

    @FXML
    void onOpenClicked(ActionEvent event) {
        FileChooser chooser = new FileChooser();
        chooser.setTitle("Open document");
        for (DocumentType type : Program.DocumentType.values()) {
            chooser.getExtensionFilters().add(
                    new FileChooser.ExtensionFilter(type.name(), type.extensions)
            );
        }
        chooser.getExtensionFilters().addAll(
                new FileChooser.ExtensionFilter("All Files", "*.*")
        );
        File file = chooser.showOpenDialog(JaceApplication.getApplication().primaryStage);
        if (file != null && file.isFile() && file.exists()) {
            DocumentType type = DocumentType.fromFile(file);
            createTab(type, file);
        }
    }

    @FXML
    void onSaveAllClicked(ActionEvent event) {
        openDocuments.forEach((Tab t, Program p) -> {
            if (p.isChanged()) {
                if (p.getFile().isPresent()) {
                    p.save(p.getFile().get());
                    t.setText(p.getName());
                } else {
                    tabPane.getSelectionModel().select(t);
                    onSaveAsClicked(event);
                }
            }
        });
    }

    @FXML
    void onSaveAsClicked(ActionEvent event) {
        getCurrentProgram().ifPresent(program -> {
            program.save(chooseFileToSave(program.getType()));
            getCurrentTab().get().setText(program.getName());
        });
    }

    @FXML
    void onSaveClicked(ActionEvent event) {
        getCurrentProgram().ifPresent(program -> {
            program.save(
                    program.getFile()
                    .orElseGet(() -> chooseFileToSave(program.getType())));
            getCurrentTab().get().setText(program.getName());
        });
    }

    private File chooseFileToSave(DocumentType type) {
        FileChooser chooser = new FileChooser();
        chooser.setTitle("Save " + type.name() + " document");
        chooser.getExtensionFilters().addAll(
                new FileChooser.ExtensionFilter(type.name(), type.extensions),
                new FileChooser.ExtensionFilter("All Files", "*.*")
        );
        return chooser.showSaveDialog(JaceApplication.getApplication().primaryStage);
    }

    @FXML
    void executeClicked(ActionEvent event) {
        getCurrentProgram().ifPresent(program -> {
            program.execute();
            updateStatusMessages(program.lastResult);
        });
    }

    @FXML
    void testCompileClicked(ActionEvent event) {
        getCurrentProgram().ifPresent(program -> {
            program.test();
            updateStatusMessages(program.lastResult);
        });
    }

    @FXML
    void viewCompilerOutputClicked(ActionEvent event) {

    }

    @FXML
    void viewSymbolTableClicked(ActionEvent event) {

    }

    @FXML // This method is called by the FXMLLoader when initialization is complete
    public void initialize() {
        assert saveMenuItem != null : "fx:id=\"saveMenuItem\" was not injected: check your FXML file 'editor.fxml'.";
        assert saveAllMenuItem != null : "fx:id=\"saveAllMenuItem\" was not injected: check your FXML file 'editor.fxml'.";
        assert saveAsMenuItem != null : "fx:id=\"saveAsMenuItem\" was not injected: check your FXML file 'editor.fxml'.";
        assert closeMenuItem != null : "fx:id=\"closeMenuItem\" was not injected: check your FXML file 'editor.fxml'.";
        assert closeAllMenuItem != null : "fx:id=\"closeAllMenuItem\" was not injected: check your FXML file 'editor.fxml'.";
        assert editMenu != null : "fx:id=\"editMenu\" was not injected: check your FXML file 'editor.fxml'.";
        assert runMenu != null : "fx:id=\"runMenu\" was not injected: check your FXML file 'editor.fxml'.";
        assert autocompile != null : "fx:id=\"autocompile\" was not injected: check your FXML file 'editor.fxml'.";
        assert tabPane != null : "fx:id=\"tabPane\" was not injected: check your FXML file 'editor.fxml'.";
        assert statusBar != null : "fx:id=\"statusBar\" was not injected: check your FXML file 'editor.fxml'.";

        tabPane.getTabs().addListener((ListChangeListener.Change<? extends Tab> c) -> {
            boolean hasNoItems = c.getList().isEmpty();
            saveMenuItem.setDisable(hasNoItems);
            saveAsMenuItem.setDisable(hasNoItems);
            saveAllMenuItem.setDisable(hasNoItems);
            closeMenuItem.setDisable(hasNoItems);
            closeAllMenuItem.setDisable(hasNoItems);
            editMenu.setDisable(hasNoItems);
            runMenu.setDisable(hasNoItems);
        });
    }

    private void updateStatusMessages(CompileResult lastResult) {
        String message = "Compiler was " + (lastResult.isSuccessful() ? " successful" : " NOT SUCCESSFUL");
        message += " -- ";
        message += lastResult.getErrors().size() + " error(s) and "+lastResult.getWarnings().size()+" warning(s) reported.";
        statusBar.getItems().clear();
        statusBar.getItems().add(new Label(message));
    }
}
