/*
 * Copyright 2015-2016 Todd Kulesza <[email protected]>.
 *
 * This file is part of Archivo.
 *
 * Archivo is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Archivo is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Archivo.  If not, see <http://www.gnu.org/licenses/>.
 */

package net.straylightlabs.archivo;

import ch.qos.logback.classic.Level;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Insets;
import javafx.scene.Cursor;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.ButtonType;
import javafx.scene.image.Image;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import net.straylightlabs.archivo.controller.*;
import net.straylightlabs.archivo.model.*;
import net.straylightlabs.archivo.net.MindCommandRecordingUpdate;
import net.straylightlabs.archivo.net.MindTask;
import net.straylightlabs.archivo.utilities.OSHelper;
import net.straylightlabs.archivo.view.*;
import org.controlsfx.control.HyperlinkLabel;
import org.controlsfx.glyphfont.FontAwesome;
import org.controlsfx.glyphfont.Glyph;
import org.controlsfx.glyphfont.GlyphFont;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.awt.*;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.*;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

public class Archivo extends Application {
    private Stage primaryStage;
    private final MAKManager maks;
    private final StringProperty statusText;
    private final ExecutorService rpcExecutor;
    private final UserPrefs prefs;
    private RootLayoutController rootController;
    private RecordingListController recordingListController;
    private RecordingDetailsController recordingDetailsController;
    private final ArchiveQueueManager archiveQueueManager;
    private final CrashReportController crashReportController;
    private ArchiveHistory archiveHistory;
    private GlyphFont symbolFont;

    private final static Logger logger = LoggerFactory.getLogger(Archivo.class);
    public final static TelemetryController telemetryController = new TelemetryController();

    public static final String APPLICATION_NAME = "Archivo";
    public static final String APPLICATION_RDN = "net.straylightlabs.archivo";
    public static final int APP_MAJOR_VERSION = 1;
    public static final int APP_MINOR_VERSION = 1;
    public static final int APP_RELEASE_VERSION = 0;
    public static final boolean IS_BETA = false;
    public static final int BETA_VERSION = 0;
    public static final String APPLICATION_VERSION;
    public static final String USER_AGENT;
    public static final Path LOG_PATH = Paths.get(OSHelper.getDataDirectory().toString(), "log.txt");
    private static final int WINDOW_MIN_HEIGHT = 400;
    private static final int WINDOW_MIN_WIDTH = 555;
    private static final Path ARCHIVE_HISTORY_PATH = Paths.get(OSHelper.getDataDirectory().toString(), "history.xml");

    static {
        if (IS_BETA) {
            APPLICATION_VERSION = String.format(
                    "%d.%d.%d Beta %d", APP_MAJOR_VERSION, APP_MINOR_VERSION, APP_RELEASE_VERSION, BETA_VERSION
            );
        } else {
            APPLICATION_VERSION = String.format("%d.%d.%d", APP_MAJOR_VERSION, APP_MINOR_VERSION, APP_RELEASE_VERSION);
        }
        USER_AGENT = String.format("%s/%s", APPLICATION_NAME, APPLICATION_VERSION);
    }

    public Archivo() {
        super();
        prefs = new UserPrefs();
        maks = new MAKManager();
        prefs.loadMAKs(maks);
        if (prefs.getShareTelemetry()) {
            telemetryController.enable();
        }
        setLogLevel();
        telemetryController.setUserId(prefs.getUserId());
        crashReportController = new CrashReportController(prefs.getUserId());
        statusText = new SimpleStringProperty();
        rpcExecutor = Executors.newSingleThreadExecutor();
        archiveQueueManager = new ArchiveQueueManager(this);
    }

    /**
     * If the user has requested debugging mode, set the root logger to the DEBUG level
     */
    private void setLogLevel() {
        if (prefs.getDebugMode() || Archivo.IS_BETA) {
            ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(
                    ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME
            );
            root.setLevel(Level.DEBUG);
        }
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        if (!prefs.parseParameters(getParameters())) {
            cleanShutdown();
        }

        logger.info("Starting up {} {}...", APPLICATION_NAME, APPLICATION_VERSION);
        logVMInfo();

        loadSymbolFont();
        archiveHistory = ArchiveHistory.loadFrom(ARCHIVE_HISTORY_PATH);

        this.primaryStage = primaryStage;
        this.primaryStage.setTitle(APPLICATION_NAME);
        this.primaryStage.setMinHeight(WINDOW_MIN_HEIGHT);
        this.primaryStage.setMinWidth(WINDOW_MIN_WIDTH);
        restoreWindowDimensions();

        initRootLayout();

        String mak = maks.currentMAK();
        if (mak == null) {
            try {
                SetupDialog dialog = new SetupDialog(primaryStage);
                mak = dialog.promptUser();
                maks.addMAK(mak);
                prefs.saveMAKs(maks);
            } catch (IllegalStateException e) {
                logger.error("Error getting MAK from user: ", e);
                cleanShutdown();
            }
        }

        initRecordingList();
        initRecordingDetails();

        archiveQueueManager.addObserver(rootController);

        primaryStage.setOnCloseRequest(e -> {
            e.consume();
            if (confirmTaskCancellation()) {
                archiveQueueManager.cancelAllArchiveTasks();
                cleanShutdown();
            }
        });

        prefs.addNetworkChangedListener(((observable, oldValue, newValue) -> recordingListController.startTivoSearch()));
        telemetryController.sendStartupEvent();
        checkForUpdates();
    }

    private void loadSymbolFont() {
        URL fontUrl = getClass().getClassLoader().getResource("resources/fontawesome.otf");
        logger.debug("Loading font resource at {}", fontUrl);
        if (fontUrl == null) {
            logger.error("Error loading symbol font");
        } else {
            symbolFont = new FontAwesome(fontUrl.toString());
        }
    }

    public GlyphFont getSymbolFont() {
        return symbolFont;
    }

    public Glyph getGlyph(Enum<?> glyphName) {
        if (symbolFont == null) {
            logger.warn("No symbol font available");
            return null;
        }
        return symbolFont.create(glyphName);
    }

    private void logVMInfo() {
        logger.info("Running on Java {} from {}", System.getProperty("java.version"), System.getProperty("java.vendor"));
        logger.info("System is {} (version = {}, arch = {})", System.getProperty("os.name"),
                System.getProperty("os.version"), System.getProperty("os.arch"));
        for (Path root : FileSystems.getDefault().getRootDirectories()) {
            try {
                FileStore store = Files.getFileStore(root);
                logger.info("Volume {} has {} MB free of {} MB", root,
                        store.getUsableSpace() / 1024 / 1024, store.getTotalSpace() / 1024 / 1024);
            } catch (FileSystemException e) {
                // Ignore these
            } catch (IOException e) {
                logger.error("Error getting available disk space for volume {}: ", root, e);
            }
        }
    }

    private void checkForUpdates() {
        Task<SoftwareUpdateDetails> updateCheck = new UpdateCheckTask();
        updateCheck.setOnSucceeded(event -> {
            SoftwareUpdateDetails update = updateCheck.getValue();
            if (update.isAvailable()) {
                logger.info("Update check: A newer version of {} is available!", APPLICATION_NAME);
                showUpdateDialog(update);
            } else {
                logger.info("Update check: This is the latest version of {} ({})", APPLICATION_NAME, APPLICATION_VERSION);
            }
        });
        updateCheck.setOnFailed(event -> logger.error("Error checking for updates: ", event.getSource().getException()));
        Executors.newSingleThreadExecutor().submit(updateCheck);
    }

    private void showUpdateDialog(SoftwareUpdateDetails updateDetails) {
        Alert alert = new Alert(Alert.AlertType.INFORMATION);
        alert.setTitle("Update Available");
        alert.setHeaderText(String.format("A new version of %s is available", Archivo.APPLICATION_NAME));
        alert.setContentText(String.format("%s %s was released on %s.\n\nNotable changes include %s.\n\n" +
                        "Would you like to install the update now?\n\n",
                Archivo.APPLICATION_NAME, updateDetails.getVersion(),
                updateDetails.getReleaseDate().format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)),
                updateDetails.getSummary()));

        ButtonType deferButtonType = new ButtonType("No, I'll Update Later", ButtonBar.ButtonData.NO);
        ButtonType updateButtonType = new ButtonType("Yes, Let's Update Now", ButtonBar.ButtonData.YES);

        alert.getButtonTypes().setAll(deferButtonType, updateButtonType);
        ((Button) alert.getDialogPane().lookupButton(deferButtonType)).setDefaultButton(false);
        ((Button) alert.getDialogPane().lookupButton(updateButtonType)).setDefaultButton(true);

        Optional<ButtonType> result = alert.showAndWait();
        if ((result.isPresent() && result.get() == updateButtonType)) {
            try {
                Desktop.getDesktop().browse(updateDetails.getLocation().toURI());
            } catch (URISyntaxException | IOException e) {
                Archivo.logger.error("Error opening a web browser to download '{}': ", updateDetails.getLocation(), e);
            }
        }
    }

    public void cleanShutdown() {
        if (!confirmTaskCancellation()) {
            return;
        }

        archiveHistory.save();
        saveWindowDimensions();

        int waitTimeMS = 100;
        int msLimit = 15000;
        if (archiveQueueManager.hasTasks()) {
            setStatusText("Exiting...");
            try {
                int msWaited = 0;
                archiveQueueManager.cancelAllArchiveTasks();
                while (archiveQueueManager.hasTasks() && msWaited < msLimit) {
                    Thread.sleep(waitTimeMS);
                    msWaited += waitTimeMS;
                }
            } catch (InterruptedException e) {
                logger.error("Interrupted while waiting for archive tasks to shutdown: ", e);
            }
        }

        boolean reportingCrashes = crashReportController.hasCrashReport() && getUserPrefs().getShareTelemetry();
        if (reportingCrashes) {
            setStatusText("Exiting...");
            Task<Void> crashReportTask = crashReportController.buildTask();
            crashReportTask.setOnSucceeded(event -> shutdown());
            crashReportTask.setOnFailed(event -> shutdown());
            Executors.newSingleThreadExecutor().submit(crashReportTask);
        } else {
            shutdown();
        }
    }

    private void shutdown() {
        logger.info("Shutting down.");
        Platform.exit();
        System.exit(0);
    }

    /**
     * If there are active tasks, prompt the user before exiting.
     * Returns true if the user wants to cancel all tasks and exit.
     */
    private boolean confirmTaskCancellation() {
        if (archiveQueueManager.hasTasks()) {
            Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
            alert.setTitle("Cancel Task Confirmation");
            alert.setHeaderText("Really cancel all tasks and exit?");
            alert.setContentText("You are currently archiving recordings from your TiVo. Are you sure you want to " +
                    "close Archivo and cancel these tasks?");

            ButtonType cancelButtonType = new ButtonType("Cancel tasks and exit", ButtonBar.ButtonData.NO);
            ButtonType keepButtonType = new ButtonType("Keep archiving", ButtonBar.ButtonData.CANCEL_CLOSE);

            alert.getButtonTypes().setAll(cancelButtonType, keepButtonType);
            ((Button) alert.getDialogPane().lookupButton(cancelButtonType)).setDefaultButton(false);
            ((Button) alert.getDialogPane().lookupButton(keepButtonType)).setDefaultButton(true);

            Optional<ButtonType> result = alert.showAndWait();
            if (!result.isPresent()) {
                logger.error("No result from alert dialog");
                return false;
            } else {
                return (result.get() == cancelButtonType);
            }
        }
        return true;
    }

    private void saveWindowDimensions() {
        if (primaryStage.isMaximized()) {
            getUserPrefs().setWindowMaximized(true);
        } else {
            getUserPrefs().setWindowMaximized(false);
            getUserPrefs().setWindowWidth((int) primaryStage.getWidth());
            getUserPrefs().setWindowHeight((int) primaryStage.getHeight());
        }
    }

    private void restoreWindowDimensions() {
        Archivo.logger.debug("Restoring window width of {}", getUserPrefs().getWindowWidth());
        Archivo.logger.debug("Restoring window height of {}", getUserPrefs().getWindowHeight());
        Archivo.logger.debug("Restoring window maximized: {}", getUserPrefs().isWindowMaximized());
        primaryStage.setWidth(getUserPrefs().getWindowWidth());
        primaryStage.setHeight(getUserPrefs().getWindowHeight());
        primaryStage.setMaximized(getUserPrefs().isWindowMaximized());
    }

    private void initRootLayout() {
        try {
            FXMLLoader loader = new FXMLLoader();
            loader.setLocation(Archivo.class.getResource("view/RootLayout.fxml"));
            BorderPane rootLayout = loader.load();

            rootController = loader.getController();
            rootController.setMainApp(this);
            rootController.disableMenuItems();

            Scene scene = new Scene(rootLayout);
            URL styleUrl = getClass().getClassLoader().getResource("resources/style.css");
            if (styleUrl != null) {
                scene.getStylesheets().add(styleUrl.toExternalForm());
            }
            primaryStage.setScene(scene);

            primaryStage.getIcons().addAll(
                    new Image(getClass().getClassLoader().getResourceAsStream("resources/archivo-16.png")),
                    new Image(getClass().getClassLoader().getResourceAsStream("resources/archivo-32.png")),
                    new Image(getClass().getClassLoader().getResourceAsStream("resources/archivo-64.png")),
                    new Image(getClass().getClassLoader().getResourceAsStream("resources/archivo-96.png")),
                    new Image(getClass().getClassLoader().getResourceAsStream("resources/archivo-128.png")),
                    new Image(getClass().getClassLoader().getResourceAsStream("resources/archivo-48.png"))
            );
            primaryStage.show();
        } catch (IOException e) {
            logger.error("Error initializing main window: ", e);
        }
    }

    private void initRecordingList() {
        assert (rootController != null);

        try {
            FXMLLoader loader = new FXMLLoader();
            loader.setLocation(Archivo.class.getResource("view/RecordingList.fxml"));

            recordingListController = new RecordingListController(this);
            loader.setController(recordingListController);

            Pane recordingList = loader.load();
            rootController.getMainGrid().add(recordingList, 0, 0);
            rootController.setMenuBindings(recordingListController);

            recordingListController.startTivoSearch();
        } catch (IOException e) {
            logger.error("Error initializing recording list: ", e);
        }
    }

    private void initRecordingDetails() {
        assert (rootController != null);
        assert (recordingListController != null);

        try {
            FXMLLoader loader = new FXMLLoader();
            loader.setLocation(Archivo.class.getResource("view/RecordingDetails.fxml"));

            recordingDetailsController = new RecordingDetailsController(this);
            loader.setController(recordingDetailsController);

            Pane recordingDetails = loader.load();
            rootController.getMainGrid().add(recordingDetails, 0, 1);
        } catch (IOException e) {
            logger.error("Error initializing recording details: ", e);
        }
    }

    public void enqueueRecordingForArchiving(Recording recording) {
        if (!archiveQueueManager.enqueueArchiveTask(recording, getActiveTivo(), getMak())) {
            logger.error("Error adding recording to queue");
        }
    }

    public void cancelArchiving(Recording recording) {
        archiveQueueManager.cancelArchiveTask(recording);
    }

    /**
     * Cancel all of the current and queued Archive tasks
     */
    public void cancelAll() {
        archiveQueueManager.cancelAllArchiveTasks();
    }

    public void archiveSelection() {
        List<Recording> toArchive = new ArrayList<>();
        for (Recording recording : recordingListController.getRecordingSelection().getRecordingsWithChildren()) {
            if (recording.isSeriesHeading()) {
                continue;
            }
            boolean archive;
            if (getUserPrefs().getOrganizeArchivedShows()) {
                archive = setOrganizedPath(recording);
            } else {
                SaveFileDialog dialog = new SaveFileDialog(getPrimaryStage(), recording, prefs);
                archive = dialog.showAndWait();
            }
            if (!archive) {
                break;
            }
            logger.info("Archive recording {} to {} (file type = {})...",
                    recording.getFullTitle(), recording.getDestination(), recording.getDestinationType()
            );
            toArchive.add(recording);
        }
        if (getUserPrefs().getOrganizeArchivedShows()) {
            List<Recording> destExists = toArchive.stream().filter(recording -> Files.exists(recording.getDestination())).collect(Collectors.toList());
            destExists.addAll(RecordingsExistDialog.getRecordingsWithDuplicateDestination(toArchive));
            if (destExists.size() > 0) {
                RecordingsExistDialog recordingsExistDialog = new RecordingsExistDialog(this, toArchive, destExists, prefs);
                if (!recordingsExistDialog.showAndWait()) {
                    // The the user canceled the dialog, don't archive anything
                    return;
                }
            }
        }
        // Enqueue selected recordings for archiving
        for (Recording recording : toArchive) {
            recording.setStatus(ArchiveStatus.QUEUED);
            enqueueRecordingForArchiving(recording);
        }
    }



    private boolean setOrganizedPath(Recording recording) {
        Path path = null;
        try {
            FileType fileType = FileType.fromDescription(getUserPrefs().getMostRecentFileType());
            path = Paths.get(
                    getLastFolder().toString(),
                    recording.getDefaultNestedPath().toString() + fileType.getExtension().substring(1)
            );
            Files.createDirectories(path.getParent());
            recording.setDestinationType(fileType);
            recording.setDestination(path);
            return true;
        } catch (IOException e) {
            logger.error("Error creating directory '{}': ", path.getParent(), e);
        }
        return false;
    }

    public void deleteFromTivo(List<Recording> recordings) {
        for (Recording recording : recordings) {
            Archivo.logger.info("User requested we delete {}", recording.getFullTitle());
            Tivo tivo = getActiveTivo();
            if (tivo != null) {
                if (confirmDelete(recording, tivo)) {
                    sendDeleteCommand(recording, tivo);
                }
            } else {
                Archivo.logger.error("No TiVo is selected to delete from");
            }
        }
    }

    private boolean confirmDelete(Recording recording, Tivo tivo) {
        Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
        alert.setTitle("Remove Recording Confirmation");
        alert.setHeaderText("Really remove this recording?");
        alert.setContentText(String.format("Are you sure you want to delete %s from %s?",
                recording.getFullTitle(), tivo.getName()));

        ButtonType deleteButtonType = new ButtonType("Delete", ButtonBar.ButtonData.NO);
        ButtonType keepButtonType = new ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE);

        alert.getButtonTypes().setAll(deleteButtonType, keepButtonType);
        ((Button) alert.getDialogPane().lookupButton(deleteButtonType)).setDefaultButton(false);
        ((Button) alert.getDialogPane().lookupButton(keepButtonType)).setDefaultButton(true);

        Optional<ButtonType> result = alert.showAndWait();
        if (!result.isPresent()) {
            logger.error("No result from alert dialog");
            return false;
        } else {
            return (result.get() == deleteButtonType);
        }
    }

    private void sendDeleteCommand(Recording recording, Tivo tivo) {
        assert (recording != null);
        assert (tivo != null);

        setStatusText(String.format("Deleting '%s' from %s...", recording.getTitle(), tivo.getName()));
        primaryStage.getScene().setCursor(Cursor.WAIT);
        MindCommandRecordingUpdate command = new MindCommandRecordingUpdate(recording.getRecordingId(), tivo.getBodyId());
        MindTask task = new MindTask(tivo.getClient(), command);
        task.setOnSucceeded(event -> {
            recordingListController.updateTivoDetails(tivo);
            recordingListController.removeRecording(recording);
            primaryStage.getScene().setCursor(Cursor.DEFAULT);
        });
        task.setOnFailed(event -> {
            Throwable e = event.getSource().getException();
            Archivo.logger.error("Error fetching recordings from {}: ", tivo.getName(), e);
            clearStatusText();
            primaryStage.getScene().setCursor(Cursor.DEFAULT);
            showErrorMessage("Problem deleting recording",
                    String.format("Unfortunately we encountered a problem while removing '%s' from %s. " +
                                    "This usually means that either your computer or your TiVo has lost " +
                                    "its network connection.%n%nError message: %s",
                            recording.getTitle(), tivo.getName(), e.getLocalizedMessage())
            );
        });
        rpcExecutor.submit(task);
    }

    public void expandShows() {
        recordingListController.expandShows();
    }

    public void collapseShows() {
        recordingListController.collapseShows();
    }

    public Stage getPrimaryStage() {
        return primaryStage;
    }

    private Tivo getActiveTivo() {
        return recordingListController.getSelectedTivo();
    }

    public ExecutorService getRpcExecutor() {
        return rpcExecutor;
    }

    public StringProperty statusTextProperty() {
        return statusText;
    }

    public void setStatusText(String status) {
        logger.info("Setting status to '{}'", status);
        statusText.set(status);
        rootController.showStatus();
    }

    public void clearStatusText() {
        logger.info("TaskStatus cleared");
        rootController.hideStatus();
    }

    public void showPreferencesDialog() {
        PreferencesDialog preferences = new PreferencesDialog(getPrimaryStage(), this);
        preferences.show();
    }

    public void showErrorMessage(String header, String message) {
        showErrorMessageWithAction(header, message, null, null);
    }

    public boolean showErrorMessageWithAction(String header, String message, String action) {
        return showErrorMessageWithAction(header, message, action, null);
    }

    public boolean showErrorMessageWithAction(String header, String message, String action,
                                              EventHandler<ActionEvent> eventHandler) {
        Alert alert = new Alert(Alert.AlertType.ERROR);
        alert.setTitle("Something went wrong...");
        alert.setHeaderText(header);

        VBox contentPane = new VBox();
        HyperlinkLabel contentText = new HyperlinkLabel(message);
        contentText.setPrefWidth(500);
        contentPane.setPadding(new Insets(30, 15, 20, 15));
        contentPane.getChildren().add(contentText);
        alert.getDialogPane().setContent(contentPane);

        if (eventHandler != null) {
            contentText.setOnAction(eventHandler);
            contentText.addEventHandler(ActionEvent.ACTION, (event) -> alert.close());
        }

        ButtonType closeButtonType = new ButtonType("Close", ButtonBar.ButtonData.CANCEL_CLOSE);
        ButtonType actionButtonType = new ButtonType(action, ButtonBar.ButtonData.YES);
        ButtonType reportButtonType = new ButtonType("Report Problem", ButtonBar.ButtonData.HELP_2);
        alert.getButtonTypes().setAll(closeButtonType, reportButtonType);
        if (action != null) {
            alert.getButtonTypes().add(actionButtonType);
            ((Button) alert.getDialogPane().lookupButton(closeButtonType)).setDefaultButton(false);
            ((Button) alert.getDialogPane().lookupButton(reportButtonType)).setDefaultButton(false);
            ((Button) alert.getDialogPane().lookupButton(actionButtonType)).setDefaultButton(true);
        }

        Optional<ButtonType> result = alert.showAndWait();
        while (result.isPresent() && result.get() == reportButtonType) {
            rootController.reportProblem(null);
            result = alert.showAndWait();
        }
        return (result.isPresent() && result.get() == actionButtonType);
    }

    public void tryNextMAK() {
        String nextMak = maks.tryNextMAK();
        if (nextMak == null) {
            promptForMAK();
        } else {
            updateMAK(nextMak);
        }
    }

    private void promptForMAK() {
        ChangeMAKDialog dialog = new ChangeMAKDialog(primaryStage, maks.currentMAK());
        try {
            updateMAK(dialog.promptUser());
        } catch (IllegalStateException e) {
            cleanShutdown();
        }
    }

    private void updateMAK(String newMak) {
        if (newMak == null || newMak.isEmpty()) {
            logger.error("MAK cannot be empty");
        } else {
            maks.addMAK(newMak);
            prefs.saveMAKs(maks);
            recordingListController.updateMak(newMak);
        }
    }

    public RecordingDetailsController getRecordingDetailsController() {
        return recordingDetailsController;
    }

    @SuppressWarnings("unused")
    public RecordingListController getRecordingListController() {
        return recordingListController;
    }

    public ArchiveHistory getArchiveHistory() {
        return archiveHistory;
    }

    public ArchiveQueueManager getArchiveQueueManager() {
        return archiveQueueManager;
    }

    public UserPrefs getUserPrefs() {
        return prefs;
    }

    public String getMak() {
        return maks.currentMAK();
    }

    public void setLastDevice(Tivo tivo) {
        prefs.setLastDevice(tivo);
    }

    public Tivo getLastDevice() {
        return prefs.getLastDevice(maks.currentMAK());
    }

    public Path getLastFolder() {
        return prefs.getLastFolder();
    }

    public void crashOccurred() {
        crashReportController.crashOccurred();
    }

    public static void main(String[] args) {
        launch(args);
    }
}