/*
 * This file is part of Quelea, free projection software for churches.
 *
 *
 * This program 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.
 *
 * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.quelea.services.utils;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.logging.Level;

import javafx.geometry.BoundingBox;
import javafx.geometry.Bounds;
import javafx.scene.paint.Color;
import org.quelea.data.bible.Bible;
import org.quelea.data.displayable.TextAlignment;
import org.quelea.services.languages.spelling.Dictionary;
import org.quelea.services.languages.spelling.DictionaryManager;
import org.quelea.services.notice.NoticeDrawer.NoticePosition;

import static org.quelea.services.utils.QueleaPropertyKeys.*;

/**
 * Manages the properties specific to Quelea.
 * <p>
 *
 * @author Michael
 */
public final class QueleaProperties extends Properties {

    public static final Version VERSION = new Version("2020.0", VersionType.CI);
    private static QueleaProperties INSTANCE;
    private String userHome;

    public static void init(String userHome) {
        INSTANCE = new QueleaProperties(userHome);
        try {
            if (!get().getPropFile().exists()) {
                get().getPropFile().createNewFile();
            }
            try (StringReader reader = new StringReader(Utils.getTextFromFile(get().getPropFile().getAbsolutePath(), ""))) {
                get().load(reader);
            }
        } catch (IOException ex) { //Never mind.
        }
    }

    /**
     * Load the properties from the properties file.
     */
    private QueleaProperties(String userHome) {
        if (userHome != null && !userHome.isEmpty()) {
            this.userHome = userHome;
        } else {
            this.userHome = System.getProperty("user.home");
        }
    }

    /**
     * Get the properties file.
     * <p>
     *
     * @return the properties file.
     */
    private File getPropFile() {
        return new File(getQueleaUserHome(), "quelea.properties");
    }

    /**
     * Save these properties to the file.
     */
    private void write() {
        try (FileWriter writer = new FileWriter(getPropFile())) {
            store(writer, "Auto save");
        } catch (IOException ex) {
//            LOGGER.log(Level.WARNING, "Couldn't store properties", ex);
        }
    }

    /**
     * Get the singleton instance of this class.
     * <p>
     *
     * @return the instance.
     */
    public static QueleaProperties get() {
        return INSTANCE;
    }

    /**
     * Get the languages file that should be used as specified in the properties
     * file.
     * <p>
     *
     * @return the languages file for the GUI.
     */
    public File getLanguageFile() {
        return new File("languages", getProperty(languageFileKey, "gb.lang"));
    }

    public boolean isDictionaryEnabled() {
        return Boolean.parseBoolean(getProperty(enableDictKey, "false"));
    }

    /**
     * Get the languages file that should be used as specified in the properties
     * file.
     * <p>
     *
     * @return the languages file for the GUI.
     */
    public Dictionary getDictionary() {
        String dict = getProperty(languageFileKey, "gb.lang");
        String[] parts = dict.split("\\.");
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < parts.length - 1; i++) {
            builder.append(parts[i]);
            builder.append(".");
        }
        builder.append("words");
        return DictionaryManager.INSTANCE.getFromFilename(builder.toString());
    }

    /**
     * Set the name of the language file to use.
     * <p>
     *
     * @param file the name of the language file to use.
     */
    public void setLanguageFile(String file) {
        setProperty(languageFileKey, file);
        write();
    }

    /**
     * Get the english languages file that should be present on all
     * installations. We can default to this if labels are missing in other
     * languages.
     * <p>
     *
     * @return the english languages file for the GUI.
     */
    public File getEnglishLanguageFile() {
        return new File("languages", "gb.lang");
    }

    /**
     * Determine whether or not to display the video tab.
     *
     * @return true if the video tab should be displayed, false otherwise.
     */
    public boolean getDisplayVideoTab() {
        try {
            return Boolean.parseBoolean(getProperty(videoTabKey, "false"));
        } catch (Exception ex) {
            return true;
        }
    }

    public void setDisplayVideoTab(boolean videoTab) {
        setProperty(videoTabKey, Boolean.toString(videoTab));
        write();
    }

    /**
     * Get the scene info as stored from the last exit of Quelea (or some
     * default values if it doesn't exist in the properties file.)
     * <p>
     *
     * @return the scene info.
     */
    public SceneInfo getSceneInfo() {
        try {
            String[] parts = getProperty(sceneInfoKey, "461,15,997,995,false").split(",");
            if (parts.length == 4) {
                return new SceneInfo(Integer.parseInt(parts[0]), Integer.parseInt(parts[1]), Integer.parseInt(parts[2]), Integer.parseInt(parts[3]), false);
            } else if (parts.length == 5) {
                return new SceneInfo(Integer.parseInt(parts[0]), Integer.parseInt(parts[1]), Integer.parseInt(parts[2]), Integer.parseInt(parts[3]), Boolean.parseBoolean(parts[4]));
            } else {
                return null;
            }
        } catch (Exception ex) {
            LoggerUtils.getLogger().log(Level.WARNING, "Invalid scene info: " + getProperty(sceneInfoKey), ex);
            return null;
        }
    }

    /**
     * Set the scene info for Quelea's main window - generally called just
     * before exit so the next invocation of the program can display the window
     * in the same position.
     * <p>
     *
     * @param info the scene info.
     */
    public void setSceneInfo(SceneInfo info) {
        setProperty(sceneInfoKey, info.toString());
        write();
    }

    /**
     * Get the main splitpane divider position property.
     *
     * @return the main splitpane divider position property, or -1 if none is
     * set.
     */
    public double getMainDivPos() {
        return Double.parseDouble(getProperty(mainDivposKey, "-1"));
    }

    public String getElevantoClientId() {
        return getProperty(elevantoClientIdKey, "91955");
    }

    /**
     * Get the library / schedule splitpane divider position property.
     *
     * @return the library / schedule splitpane divider position property, or -1
     * if none is set.
     */
    public double getLibraryDivPos() {
        return Double.parseDouble(getProperty(libraryDivposKey, "-1"));
    }

    /**
     * Get the preview / live splitpane divider position property.
     *
     * @return the preview / live splitpane divider position property, or -1 if
     * none is set.
     */
    public double getPrevLiveDivPos() {
        return Double.parseDouble(getProperty(preliveDivposKey, "-1"));
    }

    /**
     * Get the canvas divider position property.
     *
     * @return the canvas divider position property, or -1 if none is set.
     */
    public double getCanvasDivPos() {
        return Double.parseDouble(getProperty(canvasDivposKey, "-1"));
    }

    /**
     * Set the main divider position property.
     *
     * @param val the position of the divider 0-1.
     */
    public void setMainDivPos(double val) {
        setProperty(mainDivposKey, Double.toString(val));
        write();
    }

    /**
     * Set the preview / live divider position property.
     *
     * @param val the position of the divider 0-1.
     */
    public void setPrevLiveDivPos(double val) {
        setProperty(preliveDivposKey, Double.toString(val));
        write();
    }

    /**
     * Set the canvas divider position property.
     *
     * @param val the position of the divider 0-1.
     */
    public void setCanvasDivPos(double val) {
        setProperty(canvasDivposKey, Double.toString(val));
        write();
    }

    /**
     * Set the library divider position property.
     *
     * @param val the position of the divider 0-1.
     */
    public void setLibraryDivPos(double val) {
        setProperty(libraryDivposKey, Double.toString(val));
        write();
    }

    /**
     * Get a list of user chosen fonts to appear in the theme dialog.
     * <p>
     *
     * @return a list of user chosen fonts to appear in the theme dialog.
     */
    public List<String> getChosenFonts() {
        String fontStr = getProperty(chosenFontsKey, "Arial|Liberation Sans|Noto Sans|Oxygen|Roboto|Vegur|Roboto Mono|Ubuntu Mono");
        List<String> ret = new ArrayList<>();
        for (String str : fontStr.split("\\|")) {
            if (!str.trim().isEmpty()) {
                ret.add(str);
            }
        }
        return ret;
    }

    /**
     * Set a list of user chosen fonts to appear in the theme dialog.
     * <p>
     *
     * @param fonts the list of user chosen fonts to appear in the theme dialog.
     */
    public void setChosenFonts(List<String> fonts) {
        StringBuilder fontBuilder = new StringBuilder();
        for (int i = 0; i < fonts.size(); i++) {
            fontBuilder.append(fonts.get(i));
            if (i < fonts.size() - 1) {
                fontBuilder.append("|");
            }
        }
        setProperty(chosenFontsKey, fontBuilder.toString());
        write();
    }

    /**
     * Determine if the same font size should be used for each section in a
     * displayable - this can stop the sizes jumping all over the place
     * depending on how much text there is per slide.
     * <p>
     *
     * @return true if the uniform font size should be used, false otherwise.
     */
    public boolean getUseUniformFontSize() {
        return Boolean.parseBoolean(getProperty(uniformFontSizeKey, "true"));
    }

    /**
     * Set if the same font size should be used for each section in a
     * displayable - this can stop the sizes jumping all over the place
     * depending on how much text there is per slide.
     * <p>
     *
     * @param val true if the uniform font size should be used, false otherwise.
     */
    public void setUseUniformFontSize(boolean val) {
        setProperty(uniformFontSizeKey, Boolean.toString(val));
    }

    /**
     * Determine if we should show verse numbers for bible passages.
     * <p>
     *
     * @return true if we should show verse numbers, false otherwise.
     */
    public boolean getShowVerseNumbers() {
        return Boolean.parseBoolean(getProperty(showVerseNumbersKey, "true"));
    }

    /**
     * Set if we should show verse numbers for bible passages.
     * <p>
     *
     * @param val true if we should show verse numbers, false otherwise.
     */
    public void setShowVerseNumbers(boolean val) {
        setProperty(showVerseNumbersKey, Boolean.toString(val));
    }

    /**
     * Get the colour to use for notice backgrounds.
     *
     * @return the colour to use for notice backgrounds.
     */
    public Color getNoticeBackgroundColour() {
        return getColor(getProperty(noticeBackgroundColourKey, getStr(Color.BROWN)));
    }

    /**
     * Set the colour to use for notice backgrounds.
     *
     * @param colour the colour to use for notice backgrounds.
     */
    public void setNoticeBackgroundColour(Color colour) {
        setProperty(noticeBackgroundColourKey, getStr(colour));
    }

    /**
     * Get the position at which to display the notices.
     *
     * @return the position at which to display the notices.
     */
    public NoticePosition getNoticePosition() {
        if (getProperty(noticePositionKey, "Bottom").equalsIgnoreCase("top")) {
            return NoticePosition.TOP;
        } else {
            return NoticePosition.BOTTOM;
        }
    }

    /**
     * Set the position at which to display the notices.
     *
     * @param position the position at which to display the notices.
     */
    public void setNoticePosition(NoticePosition position) {
        setProperty(noticePositionKey, position.getText());
    }

    /**
     * Get the speed at which to display the notices.
     *
     * @return the speed at which to display the notices.
     */
    public double getNoticeSpeed() {
        return Double.parseDouble(getProperty(noticeSpeedKey, "10"));
    }

    /**
     * Set the speed at which to display the notices.
     *
     * @param speed the speed at which to display the notices.
     */
    public void setNoticeSpeed(double speed) {
        setProperty(noticeSpeedKey, Double.toString(speed));
    }

    /**
     * Get the last directory used in the general file chooser.
     *
     * @return the last directory used in the general file chooser.
     */
    public File getLastDirectory() {
        String path = getProperty(lastDirectoryKey);
        if (path == null) {
            return null;
        }
        File f = new File(path);
        if (f.isDirectory()) {
            return f;
        } else {
            LoggerUtils.getLogger().log(Level.INFO, "Cannot find last directory, reverting to default location");
            return null;
        }
    }

    /**
     * Set the last directory used in the general file chooser.
     *
     * @param directory the last directory used in the general file chooser.
     */
    public void setLastDirectory(File directory) {
        setProperty(lastDirectoryKey, directory.getAbsolutePath());
    }

    /**
     * Get the last directory used in the schedule file chooser.
     *
     * @return the last directory used in the schedule file chooser.
     */
    public File getLastScheduleFileDirectory() {
        String path = getProperty(lastSchedulefileDirectoryKey);
        if (path == null) {
            return null;
        }
        File f = new File(path);
        if (f.isDirectory()) {
            return f;
        } else {
            LoggerUtils.getLogger().log(Level.INFO, "Cannot find last schedule directory, reverting to default location");
            return null;
        }
    }

    /**
     * Sets whether the schedule should embed videos when saving
     *
     * @param embed true if should embed, false otherwise
     */
    public void setEmbedMediaInScheduleFile(boolean embed) {
        setProperty(scheduleEmbedMediaKey, embed + "");
    }

    /**
     * Gets whether the schedule should embed videos when saving
     *
     * @return true if should embed, false otherwise
     */
    public boolean getEmbedMediaInScheduleFile() {
        boolean ret = Boolean.parseBoolean(getProperty(scheduleEmbedMediaKey, "true"));
        return ret;
    }

    /**
     * Sets whether item themes can override the global theme.
     *
     * @param val true if should override, false otherwise
     */
    public void setItemThemeOverride(boolean val) {
        setProperty(itemThemeOverrideKey, val + "");
    }

    /**
     * Gets whether item themes can override the global theme.
     *
     * @return true if should override, false otherwise
     */
    public boolean getItemThemeOverride() {
        boolean ret = Boolean.parseBoolean(getProperty(itemThemeOverrideKey, "false"));
        return ret;
    }

    /**
     * Set the currently selected global theme file.
     */
    public void setGlobalSongThemeFile(File file) {
        if (file == null) {
            setProperty(globalSongThemeFileKey, "");
        } else {
            setProperty(globalSongThemeFileKey, file.getAbsolutePath());
        }
    }

    /**
     * Get the currently selected global theme file.
     */
    public File getGlobalSongThemeFile() {
        String path = getProperty(globalSongThemeFileKey);
        if (path == null || path.isEmpty()) {
            return null;
        }
        return new File(path);
    }

    /**
     * Set the currently selected global theme file.
     */
    public void setGlobalBibleThemeFile(File file) {
        if (file == null) {
            setProperty(globalBibleThemeFileKey, "");
        } else {
            setProperty(globalBibleThemeFileKey, file.getAbsolutePath());
        }
    }

    /**
     * Get the currently selected global theme file.
     */
    public File getGlobalBibleThemeFile() {
        String path = getProperty(globalBibleThemeFileKey);
        if (path == null || path.isEmpty()) {
            return null;
        }
        return new File(path);
    }

    /**
     * Set the last directory used in the schedule file chooser.
     *
     * @param directory the last directory used in the schedule file chooser.
     */
    public void setLastScheduleFileDirectory(File directory) {
        setProperty(lastSchedulefileDirectoryKey, directory.getAbsolutePath());
    }

    /**
     * Get the last directory used in the video file chooser.
     *
     * @return the last directory used in the video file chooser.
     */
    public File getLastVideoDirectory() {
        String path = getProperty(lastVideoDirectoryKey);
        if (path == null) {
            return null;
        }
        File f = new File(path);
        if (f.isDirectory()) {
            return f;
        } else {
            LoggerUtils.getLogger().log(Level.INFO, "Cannot find last video directory, reverting to default location");
            return null;
        }
    }

    /**
     * Set the last directory used in the video file chooser.
     *
     * @param directory the last directory used in the video file chooser.
     */
    public void setLastVideoDirectory(File directory) {
        setProperty(lastVideoDirectoryKey, directory.getAbsolutePath());
    }

    /**
     * Determine whether to auto-play videos after they have been set in live
     * view.
     *
     * @return true if auto play is enabled, false otherwise.
     */
    public boolean getAutoPlayVideo() {
        return Boolean.parseBoolean(getProperty(autoplayVidKey, "false"));
    }

    /**
     * Set whether to auto-play videos after they have been set in live view.
     *
     * @param val true to enable auto play, false otherwise.
     */
    public void setAutoPlayVideo(boolean val) {
        setProperty(autoplayVidKey, Boolean.toString(val));
    }

    /**
     * Determine whether to use Java FX rendering for video playback with VLC.
     * This approach is totally cross-platform capable.
     *
     * @return true if should use java fx for VLC Rendering, false otherwise
     */
    public boolean getUseJavaFXforVLCRendering() {
        return Boolean.parseBoolean(getProperty(useVlcJavafxRenderingKey, "false"));
    }

    /**
     * Set whether to use Java FX rendering for video playback with VLC. This
     * approach is totally cross-platform capable.
     *
     * @param val true if should use java fx for VLC Rendering, false otherwise.
     */
    public void setUseJavaFXforVLCRendering(boolean val) {
        setProperty(useVlcJavafxRenderingKey, Boolean.toString(val));
    }

    /**
     * Get the font size at which to display the notices.
     *
     * @return the font size at which to display the notices.
     */
    public double getNoticeFontSize() {
        return Double.parseDouble(getProperty(noticeFontSizeKey, "50"));
    }

    /**
     * Set the font size at which to display the notices.
     *
     * @param fontSize the font size at which to display the notices.
     */
    public void setNoticeFontSize(double fontSize) {
        setProperty(noticeFontSizeKey, Double.toString(fontSize));
    }

    /**
     * Determine if we should attempt to fetch translations automatically.
     * <p>
     *
     * @return true if we should translate automatically, false otherwise.
     */
    public boolean getAutoTranslate() {
        return Boolean.parseBoolean(getProperty(autoTranslateKey, "true"));
    }

    /**
     * Set if we should attempt to fetch translations automatically.
     * <p>
     *
     * @param val true if we should translate automatically, false otherwise.
     */
    public void setAutoTranslate(boolean val) {
        setProperty(autoTranslateKey, Boolean.toString(val));
    }

    /**
     * Get the maximum font size used by text displayables.
     * <p>
     *
     * @return the maximum font size used by text displayables.
     */
    public double getMaxFontSize() {
        return Double.parseDouble(getProperty(maxFontSizeKey, "1000"));
    }

    /**
     * Set the maximum font size used by text displayables.
     * <p>
     *
     * @param fontSize the maximum font size used by text displayables.
     */
    public void setMaxFontSize(double fontSize) {
        setProperty(maxFontSizeKey, Double.toString(fontSize));
    }

    /**
     * Get the additional line spacing (in pixels) to be used between each line.
     * <p>
     *
     * @return the additional line spacing.
     */
    public double getAdditionalLineSpacing() {
        return Double.parseDouble(getProperty(additionalLineSpacingKey, "10"));
    }

    /**
     * Set the additional line spacing (in pixels) to be used between each line.
     * <p>
     *
     * @param spacing the additional line spacing.
     */
    public void setAdditionalLineSpacing(double spacing) {
        setProperty(additionalLineSpacingKey, Double.toString(spacing));
    }

    /**
     * Get the thumbnail size.
     * <p>
     *
     * @return the thumbnail size.
     */
    public int getThumbnailSize() {
        return Integer.parseInt(getProperty(thumbnailSizeKey, "200"));
    }

    /**
     * Set the thumbnail size.
     * <p>
     *
     * @param thumbnailSize the thumbnail size.
     */
    public void setThumbnailSize(int thumbnailSize) {
        setProperty(thumbnailSizeKey, Integer.toString(thumbnailSize));
    }

    /**
     * Get the show extra live panel toolbar options setting.
     * <p>
     *
     * @return the true to show extra toolbar options.
     */
    public boolean getShowExtraLivePanelToolbarOptions() {
        return Boolean.parseBoolean(getProperty(showExtraLivePanelToolbarOptionsKey, "false"));
    }

    /**
     * Set the show extra live panel toolbar options setting.
     * <p>
     *
     * @param show the extra options or leave them hidden.
     */
    public void setShowExtraLivePanelToolbarOptions(boolean show) {
        setProperty(showExtraLivePanelToolbarOptionsKey, Boolean.toString(show));
    }

    /**
     * Determine if, when an item is removed from the schedule and displayed on
     * the live view, whether it should be removed from the live view or kept
     * until something replaces it.
     * <p>
     *
     * @return true if it should be cleared, false otherwise.
     */
    public boolean getClearLiveOnRemove() {
        return Boolean.parseBoolean(getProperty(clearLiveOnRemoveKey, "true"));
    }

    /**
     * Set if, when an item is removed from the schedule and displayed on the
     * live view, whether it should be removed from the live view or kept until
     * something replaces it.
     * <p>
     *
     * @param val true if it should be cleared, false otherwise.
     */
    public void setClearLiveOnRemove(boolean val) {
        setProperty(clearLiveOnRemoveKey, Boolean.toString(val));
    }

    /**
     * Get the location of Quelea's Facebook page.
     * <p>
     *
     * @return the location of the facebook page.
     */
    public String getFacebookPageLocation() {
        return getProperty(facebookPageKey, "http://www.facebook.com/quelea.projection");
    }

    /**
     * Get the location of Quelea's Facebook page.
     * <p>
     *
     * @return the location of the facebook page.
     */
    public String getWikiPageLocation() {
        return getProperty(wikiPageKey, "http://quelea.org/wiki/index.php/Main_Page");
    }

    /**
     * Get the Quelea home directory in the user's directory.
     * <p>
     *
     * @return the Quelea home directory.
     */
    public File getQueleaUserHome() {
        File ret = new File(new File(userHome), ".quelea");
        if (!ret.exists()) {
            ret.mkdir();
        }
        return ret;
    }

    /**
     * Get the user's turbo db exe converter file.
     * <p>
     *
     * @return the user's turbo db exe converter.
     */
    public File getTurboDBExe() {
        return new File(getQueleaUserHome(), "TdbDataX.exe");
    }

    public int getTranslationFontSizeOffset() {
        return Integer.parseInt(getProperty(translationFontSizeOffsetKey, "3"));
    }

    /**
     * Get the font to use for stage text.
     * <p>
     *
     * @return the font to use for stage text.
     */
    public String getStageTextFont() {
        return getProperty(stageFontKey, "SansSerif");
    }

    /**
     * Set the font to use for stage text.
     * <p>
     *
     * @param font the font to use for stage text.
     */
    public void setStageTextFont(String font) {
        setProperty(stageFontKey, font);
        write();
    }

    /**
     * Get the alignment of the text on stage view.
     * <p>
     *
     * @return the alignment of the text on stage view.
     */
    public String getStageTextAlignment() {
        return TextAlignment.valueOf(getProperty(stageTextAlignmentKey, "LEFT")).toFriendlyString();
    }

    /**
     * Set the alignment of the text on stage view.
     * <p>
     *
     * @param alignment the alignment of the text on stage view.
     */
    public void setStageTextAlignment(TextAlignment alignment) {
        setProperty(stageTextAlignmentKey, alignment.toString());
        write();
    }

    /**
     * Get whether we should display the chords in stage view.
     * <p>
     *
     * @return true if they should be displayed, false otherwise.
     */
    public boolean getShowChords() {
        return Boolean.parseBoolean(getProperty(stageShowChordsKey, "true"));
    }

    /**
     * Set whether we should display the chords in stage view.
     * <p>
     *
     * @param showChords true if they should be displayed, false otherwise.
     */
    public void setShowChords(boolean showChords) {
        setProperty(stageShowChordsKey, Boolean.toString(showChords));
        write();
    }

    /**
     * Determine whether we should phone home at startup with anonymous
     * information. Simply put phonehome=false in the properties file to disable
     * phonehome.
     * <p>
     *
     * @return true if we should phone home, false otherwise.
     */
    public boolean getPhoneHome() {
        return Boolean.parseBoolean(getProperty(phonehomeKey, "true"));
    }

    /**
     * Get the directory used for storing the bibles.
     * <p>
     *
     * @return the bibles directory.
     */
    public File getBibleDir() {
        return new File(getQueleaUserHome(), "bibles");
    }

    /**
     * Get the directory used for storing images.
     * <p>
     *
     * @return the img directory
     */
    public File getImageDir() {
        return new File(getQueleaUserHome(), "img");
    }

    /**
     * Get the directory used for storing dictionaries.
     * <p>
     *
     * @return the dictionaries directory
     */
    public File getDictionaryDir() {
        return new File(getQueleaUserHome(), "dictionaries");
    }

    /**
     * Get the directory used for storing videos.
     * <p>
     *
     * @return the vid directory
     */
    public File getVidDir() {
        return new File(getQueleaUserHome(), "vid");
    }

    /**
     * Get the directory used for storing temporary recordings.
     * <p>
     *
     * @return the temp directory
     */
    public File getTempDir() {
        return new File(getQueleaUserHome(), "temp");
    }

    /**
     * Get the extension used for quelea schedules.
     * <p>
     *
     * @return the extension used for quelea schedules.
     */
    public String getScheduleExtension() {
        return getProperty(queleaScheduleExtensionKey, "qsch");
    }

    /**
     * Get the extension used for quelea song packs.
     * <p>
     *
     * @return the extension used for quelea song packs.
     */
    public String getSongPackExtension() {
        return getProperty(queleaSongpackExtensionKey, "qsp");
    }

    /**
     * Get the number of the screen used for the control screen. This is the
     * screen that the main Quelea operator window will be displayed on.
     * <p>
     *
     * @return the control screen number.
     */
    public int getControlScreen() {
        return Integer.parseInt(getProperty(controlScreenKey, "0"));
    }

    /**
     * Set the control screen output.
     * <p>
     *
     * @param screen the number of the screen to use for the output.
     */
    public void setControlScreen(int screen) {
        setProperty(controlScreenKey, Integer.toString(screen));
        write();
    }

    /**
     * Get the one line mode.
     * <p>
     *
     * @return true if one line mode should be enabled, false otherwise.
     */
    public boolean getOneLineMode() {
        return Boolean.parseBoolean(getProperty(oneLineModeKey, "false"));
    }

    /**
     * Set the one line mode property.
     * <p>
     *
     * @param val the value of the one linde mode.
     */
    public void setOneLineMode(boolean val) {
        setProperty(oneLineModeKey, Boolean.toString(val));
        write();
    }

    /**
     * Get the text shadow property.
     * <p>
     *
     * @return true if text shadows are enabled, false otherwise.
     */
    public boolean getTextShadow() {
        return Boolean.parseBoolean(getProperty(textShadowKey, "false"));
    }

    /**
     * Set the text shadow property.
     * <p>
     *
     * @param val true if text shadows are enabled, false otherwise.
     */
    public void setTextShadow(boolean val) {
        setProperty(textShadowKey, Boolean.toString(val));
        write();
    }

    /**
     * Get the number of the projector screen. This is the screen that the
     * projected output will be displayed on.
     * <p>
     *
     * @return the projector screen number.
     */
    public int getProjectorScreen() {
        return Integer.parseInt(getProperty(projectorScreenKey, "1"));
    }

    /**
     * Set the control screen output.
     * <p>
     *
     * @param screen the number of the screen to use for the output.
     */
    public void setProjectorScreen(int screen) {
        setProperty(projectorScreenKey, Integer.toString(screen));
        write();
    }

    /**
     * Determine whether the projection screen automatically should be moved to
     * a recently inserted monitor.
     * * <p/>
     *
     * @return true if the projector screen should be moved, false otherwise.
     */
    public boolean getUseAutoExtend() {
        return Boolean.parseBoolean(getProperty(useAutoExtendKey, "false"));
    }

    /**
     * Set whether the projection screen automatically should be moved to a
     * recently inserted monitor.
     * * <p/>
     *
     * @param extend true if it should automatically move projection screen,
     * false otherwise.
     */
    public void setUseAutoExtend(boolean extend) {
        setProperty(useAutoExtendKey, Boolean.toString(extend));
    }

    /**
     * Get the maximum number of characters allowed on any one line of projected
     * text. If the line is longer than this, it will be split up intelligently.
     * <p>
     *
     * @return the maximum number of characters allowed on any one line of
     * projected text.
     */
    public int getMaxChars() {
        return Integer.parseInt(getProperty(maxCharsKey, "30"));
    }

    /**
     * Set the max chars value.
     * <p>
     *
     * @param maxChars the maximum number of characters allowed on any one line
     * of projected text.
     */
    public void setMaxChars(int maxChars) {
        setProperty(maxCharsKey, Integer.toString(maxChars));
        write();
    }

    /**
     * Get the custom projector co-ordinates.
     * <p>
     *
     * @return the co-ordinates.
     */
    public Bounds getProjectorCoords() {
        String[] prop = getProperty(projectorCoordsKey, "0,0,0,0").trim().split(",");
        return new BoundingBox(Integer.parseInt(prop[0]),
                Integer.parseInt(prop[1]),
                Integer.parseInt(prop[2]),
                Integer.parseInt(prop[3]));
    }

    /**
     * Set the custom projector co-ordinates.
     * <p>
     *
     * @param coords the co-ordinates to set.
     */
    public void setProjectorCoords(Bounds coords) {
        String rectStr = Integer.toString((int) coords.getMinX())
                + "," + Integer.toString((int) coords.getMinY())
                + "," + Integer.toString((int) coords.getWidth())
                + "," + Integer.toString((int) coords.getHeight());

        setProperty(projectorCoordsKey, rectStr);
        write();
    }

    public void setXProjectorCoord(String x) {
        String[] prop = getProperty(projectorCoordsKey, "0,0,0,0").trim().split(",");
        String rectStr = x
                + "," + prop[1]
                + "," + prop[2]
                + "," + prop[3];
        setProperty(projectorCoordsKey, rectStr);
        write();
    }

    public void setYProjectorCoord(String y) {
        String[] prop = getProperty(projectorCoordsKey, "0,0,0,0").trim().split(",");
        String rectStr = prop[0]
                + "," + y
                + "," + prop[2]
                + "," + prop[3];
        setProperty(projectorCoordsKey, rectStr);
        write();
    }

    public void setWidthProjectorCoord(String width) {
        String[] prop = getProperty(projectorCoordsKey, "0,0,0,0").trim().split(",");
        String rectStr = prop[0]
                + "," + prop[1]
                + "," + width
                + "," + prop[3];
        setProperty(projectorCoordsKey, rectStr);
        write();
    }

    public void setHeightProjectorCoord(String height) {
        String[] prop = getProperty(projectorCoordsKey, "0,0,0,0").trim().split(",");
        String rectStr = prop[0]
                + "," + prop[1]
                + "," + prop[2]
                + "," + height;
        setProperty(projectorCoordsKey, rectStr);
        write();
    }

    public void setXStageCoord(String x) {
        String[] prop = getProperty(stageCoordsKey, "0,0,0,0").trim().split(",");
        String rectStr = x
                + "," + prop[1]
                + "," + prop[2]
                + "," + prop[3];
        setProperty(stageCoordsKey, rectStr);
        write();
    }

    public void setYStageCoord(String y) {
        String[] prop = getProperty(stageCoordsKey, "0,0,0,0").trim().split(",");
        String rectStr = prop[0]
                + "," + y
                + "," + prop[2]
                + "," + prop[3];
        setProperty(stageCoordsKey, rectStr);
        write();
    }

    public void setWidthStageCoord(String width) {
        String[] prop = getProperty(stageCoordsKey, "0,0,0,0").trim().split(",");
        String rectStr = prop[0]
                + "," + prop[1]
                + "," + width
                + "," + prop[3];
        setProperty(stageCoordsKey, rectStr);
        write();
    }

    public void setHeightStageCoord(String height) {
        String[] prop = getProperty(stageCoordsKey, "0,0,0,0").trim().split(",");
        String rectStr = prop[0]
                + "," + prop[1]
                + "," + prop[2]
                + "," + height;
        setProperty(stageCoordsKey, rectStr);
        write();
    }

    /**
     * Determine if the projector mode is set to manual co-ordinates or a screen
     * number.
     * <p>
     *
     * @return true if it's set to manual co-ordinates, false if it's a screen
     * number.
     */
    public boolean isProjectorModeCoords() {
        return "coords".equals(getProperty(projectorModeKey));
    }

    /**
     * Set the projector mode to be manual co-ordinates.
     */
    public void setProjectorModeCoords() {
        setProperty(projectorModeKey, "coords");
        write();
    }

    /**
     * Set the projector mode to be a screen number.
     */
    public void setProjectorModeScreen() {
        setProperty(projectorModeKey, "screen");
        write();
    }

    /**
     * Get the number of the stage screen. This is the screen that the projected
     * output will be displayed on.
     * <p>
     *
     * @return the stage screen number.
     */
    public int getStageScreen() {
        return Integer.parseInt(getProperty(stageScreenKey, "-1"));
    }

    /**
     * Set the stage screen output.
     * <p>
     *
     * @param screen the number of the screen to use for the output.
     */
    public void setStageScreen(int screen) {
        setProperty(stageScreenKey, Integer.toString(screen));
        write();
    }

    /**
     * Get the custom stage screen co-ordinates.
     * <p>
     *
     * @return the co-ordinates.
     */
    public Bounds getStageCoords() {
        String[] prop = getProperty(stageCoordsKey, "0,0,0,0").trim().split(",");
        return new BoundingBox(Integer.parseInt(prop[0]),
                Integer.parseInt(prop[1]),
                Integer.parseInt(prop[2]),
                Integer.parseInt(prop[3]));
    }

    /**
     * Set the custom stage screen co-ordinates.
     * <p>
     *
     * @param coords the co-ordinates to set.
     */
    public void setStageCoords(Bounds coords) {
        String rectStr = Integer.toString((int) coords.getMinX())
                + "," + Integer.toString((int) coords.getMinY())
                + "," + Integer.toString((int) coords.getWidth())
                + "," + Integer.toString((int) coords.getHeight());

        setProperty(stageCoordsKey, rectStr);
        write();
    }

    /**
     * Determine if the stage mode is set to manual co-ordinates or a screen
     * number.
     * <p>
     *
     * @return true if it's set to manual co-ordinates, false if it's a screen
     * number.
     */
    public boolean isStageModeCoords() {
        return "coords".equals(getProperty(stageModeKey));
    }

    /**
     * Set the stage mode to be manual co-ordinates.
     */
    public void setStageModeCoords() {
        setProperty(stageModeKey, "coords");
        write();
    }

    /**
     * Set the stage mode to be a screen number.
     */
    public void setStageModeScreen() {
        setProperty(stageModeKey, "screen");
        write();
    }

    /**
     * Get the minimum number of lines that should be displayed on each page.
     * This purely applies to font sizes, the font will be adjusted so this
     * amount of lines can fit on. This stops small lines becoming huge in the
     * preview window rather than displaying normally.
     * <p>
     *
     * @return the minimum line count.
     */
    public int getMinLines() {
        return Integer.parseInt(getProperty(minLinesKey, "10"));
    }

    /**
     * Set the min lines value.
     * <p>
     *
     * @param minLines the minimum line count.
     */
    public void setMinLines(int minLines) {
        setProperty(minLinesKey, Integer.toString(minLines));
        write();
    }

    /**
     * Determine whether the single monitor warning should be shown (this warns
     * the user they only have one monitor installed.)
     * <p>
     *
     * @return true if the warning should be shown, false otherwise.
     */
    public boolean showSingleMonitorWarning() {
        return Boolean.parseBoolean(getProperty(singleMonitorWarningKey, "true"));
    }

    /**
     * Set whether the single monitor warning should be shown.
     * <p>
     *
     * @param val true if the warning should be shown, false otherwise.
     */
    public void setSingleMonitorWarning(boolean val) {
        setProperty(singleMonitorWarningKey, Boolean.toString(val));
        write();
    }

    /**
     * Get the URL to download Quelea.
     * <p>
     *
     * @return the URL to download Quelea.
     */
    public String getDownloadLocation() {
        return "https://sourceforge.net/projects/quelea/files/";
    }

    /**
     * Get the URL to the Quelea website.
     * <p>
     *
     * @return the URL to the Quelea website.
     */
    public String getWebsiteLocation() {
        return getProperty(websiteLocationKey, "http://www.quelea.org/");
    }

    /**
     * Get the URL to the Quelea discussion forum.
     * <p>
     *
     * @return the URL to the Quelea discussion forum.
     */
    public String getDiscussLocation() {
        return getProperty(discussLocationKey, "https://quelea.discourse.group/");
    }

    /**
     * Get the URL to the Quelea feedback form.
     * <p>
     *
     * @return the URL to the Quelea feedback form.
     */
    public String getFeedbackLocation() {
        return getProperty(feedbackLocationKey, "https://quelea.org/feedback/");
    }

    /**
     * Get the URL used for checking the latest version.
     * <p>
     *
     * @return the URL used for checking the latest version.
     */
    public String getUpdateURL() {
        return "https://quelea-projection.github.io/changelog";
    }

    /**
     * Determine whether we should check for updates each time the program
     * starts.
     * <p>
     *
     * @return true if we should check for updates, false otherwise.
     */
    public boolean checkUpdate() {
        return Boolean.parseBoolean(getProperty(checkUpdateKey, "true"));
    }

    /**
     * Set whether we should check for updates each time the program starts.
     * <p>
     *
     * @param val true if we should check for updates, false otherwise.
     */
    public void setCheckUpdate(boolean val) {
        setProperty(checkUpdateKey, Boolean.toString(val));
        write();
    }

    /**
     * Determine whether the first letter of all displayed lines should be a
     * capital.
     * <p>
     *
     * @return true if it should be a capital, false otherwise.
     */
    public boolean checkCapitalFirst() {
        return Boolean.parseBoolean(getProperty(capitalFirstKey, "false"));
    }

    /**
     * Set whether the first letter of all displayed lines should be a capital.
     * <p>
     *
     * @param val true if it should be a capital, false otherwise.
     */
    public void setCapitalFirst(boolean val) {
        setProperty(capitalFirstKey, Boolean.toString(val));
        write();
    }

    /**
     * Determine whether the song info text should be displayed.
     * <p>
     *
     * @return true if it should be a displayed, false otherwise.
     */
    public boolean checkDisplaySongInfoText() {
        return Boolean.parseBoolean(getProperty(displaySonginfotextKey, "true"));
    }

    /**
     * Set whether the song info text should be displayed.
     * <p>
     *
     * @param val true if it should be displayed, false otherwise.
     */
    public void setDisplaySongInfoText(boolean val) {
        setProperty(displaySonginfotextKey, Boolean.toString(val));
        write();
    }

    /**
     * Get the default bible to use.
     * <p>
     *
     * @return the default bible.
     */
    public String getDefaultBible() {
        return getProperty(defaultBibleKey);
    }

    /**
     * Set the default bible.
     * <p>
     *
     * @param bible the default bible.
     */
    public void setDefaultBible(Bible bible) {
        setProperty(defaultBibleKey, bible.getName());
        write();
    }

    /**
     * Get the colour used to display chords in stage view.
     * <p>
     *
     * @return the colour used to display chords in stage view.
     */
    public Color getStageChordColor() {
        return getColor(getProperty(stageChordColorKey, "200,200,200"));
    }

    /**
     * Set the colour used to display chords in stage view.
     * <p>
     *
     * @param color the colour used to display chords in stage view.
     */
    public void setStageChordColor(Color color) {
        setProperty(stageChordColorKey, getStr(color));
    }

    /**
     * Get the colour used to display lyrics in stage view.
     * <p>
     *
     * @return the colour used to display lyrics in stage view.
     */
    public Color getStageLyricsColor() {
        return getColor(getProperty(stageLyricsColorKey, "255,255,255"));
    }

    /**
     * Set the colour used to display lyrics in stage view.
     * <p>
     *
     * @param color the colour used to display lyrics in stage view.
     */
    public void setStageLyricsColor(Color color) {
        setProperty(stageLyricsColorKey, getStr(color));
    }

    /**
     * Set the colour used for the background in stage view.
     * <p>
     *
     * @param color the colour used for the background in stage view.
     */
    public void setStageBackgroundColor(Color color) {
        setProperty(stageBackgroundColorKey, getStr(color));
    }

    /**
     * Get the colour used for the background in stage view.
     * <p>
     *
     * @return the colour used for the background in stage view.
     */
    public Color getStageBackgroundColor() {
        return getColor(getProperty(stageBackgroundColorKey, "0,0,0"));
    }

    /**
     * Get a color from a string.
     * <p>
     *
     * @param str the string to use to get the color value.
     * @return the color.
     */
    private Color getColor(String str) {
        String[] color = str.split(",");
        double red = Double.parseDouble(color[0].trim());
        double green = Double.parseDouble(color[1].trim());
        double blue = Double.parseDouble(color[2].trim());
        if (red > 1 || green > 1 || blue > 1) {
            red /= 255;
            green /= 255;
            blue /= 255;
        }
        return new Color(red, green, blue, 1);
    }

    /**
     * Get a color value as a string.
     * <p>
     *
     * @param color the color to get as a string.
     * @return the color as a string.
     */
    public String getStr(Color color) {
        return color.getRed() + "," + color.getGreen() + "," + color.getBlue();
    }

    /**
     * Get the colour used to signify an active list.
     * <p>
     *
     * @return the colour used to signify an active list.
     */
    public Color getActiveSelectionColor() {
        return getColor(getProperty(activeSelectionColorKey, "30,160,225"));
    }

    /**
     * Get the colour used to signify an active list.
     * <p>
     *
     * @return the colour used to signify an active list.
     */
    public Color getInactiveSelectionColor() {
        return getColor(getProperty(inactiveSelectionColorKey, "150,150,150"));
    }

    /**
     * Get the thickness of the outline to use for displaying the text.
     * <p>
     *
     * @return the outline thickness in pixels.
     */
    public int getOutlineThickness() {
        return Integer.parseInt(getProperty(outlineThicknessKey, "2"));
    }

    /**
     * Set the outline thickness.
     * <p>
     *
     * @param px the outline thickness in pixels.
     */
    public void setOutlineThickness(int px) {
        setProperty(outlineThicknessKey, Integer.toString(px));
        write();
    }

    /**
     * Get the notice box height (px).
     * <p>
     *
     * @return the notice box height.
     */
    public int getNoticeBoxHeight() {
        return Integer.parseInt(getProperty(noticeBoxHeightKey, "40"));
    }

    /**
     * Set the notice box height (px).
     * <p>
     *
     * @param height the notice box height.
     */
    public void setNoticeBoxHeight(int height) {
        setProperty(noticeBoxHeightKey, Integer.toString(height));
        write();
    }

    /**
     * Get the notice box speed.
     * <p>
     *
     * @return the notice box speed.
     */
    public int getNoticeBoxSpeed() {
        return Integer.parseInt(getProperty(noticeBoxSpeedKey, "8"));
    }

    /**
     * Set the notice box speed.
     * <p>
     *
     * @param speed the notice box speed.
     */
    public void setNoticeBoxSpeed(int speed) {
        setProperty(noticeBoxSpeedKey, Integer.toString(speed));
        write();
    }

    /**
     * Get the specially treated words that are auto-capitalised by the song
     * importer when deciding how to un-caps-lock a line of text.
     * <p>
     *
     * @return the array of God words, separated by commas in the properties
     * file.
     */
    public String[] getGodWords() {
        return getProperty(godWordsKey,
                "god,God,jesus,Jesus,christ,Christ,you,You,he,He,lamb,Lamb,"
                + "lord,Lord,him,Him,son,Son,i,I,his,His,your,Your,king,King,"
                + "saviour,Saviour,savior,Savior,majesty,Majesty,alpha,Alpha,omega,Omega") //Yeah.. default testing properties.
                .trim().split(",");
    }

    /**
     * Determine whether to advance the scheudle item when the current item is
     * sent live.
     * <p>
     *
     * @return true if we should auto-advance, false otherwise.
     */
    public boolean getAdvanceOnLive() {
        return Boolean.parseBoolean(getProperty(advanceOnLiveKey, "false"));
    }

    /**
     * Set whether to advance the scheudle item when the current item is sent
     * live.
     * <p>
     *
     * @param val true if we should auto-advance, false otherwise.
     */
    public void setAdvanceOnLive(boolean val) {
        setProperty(advanceOnLiveKey, Boolean.toString(val));
        write();
    }

    /**
     * Determine whether to preview the scheudle item when the background image
     * has been updated.
     * <p>
     *
     * @return true if we should preview, false otherwise.
     */
    public boolean getPreviewOnImageUpdate() {
        return Boolean.parseBoolean(getProperty(previewOnImageChangeKey, "false"));
    }

    /**
     * Determine whether to preview the scheudle item when the background image
     * has been updated.
     * <p>
     *
     * @param val true if we should preview, false otherwise.
     */
    public void setPreviewOnImageUpdate(boolean val) {
        setProperty(previewOnImageChangeKey, Boolean.toString(val));
        write();
    }

    /**
     * Get whether to use openoffice for presentations.
     * <p>
     *
     * @return true if we should use openoffice, false if we should just use the
     * basic POI images.
     */
    public boolean getUseOO() {
        return Boolean.parseBoolean(getProperty(useOoKey, "false"));
    }

    /**
     * Set whether to use openoffice for presentations.
     * <p>
     *
     * @param val if we should use openoffice, false if we should just use the
     * basic POI images.
     */
    public void setUseOO(boolean val) {
        setProperty(useOoKey, Boolean.toString(val));
        write();
    }

    /**
     * Get the path to the openoffice installation on this machine.
     * <p>
     *
     * @return the path to the openoffice installation on this machine.
     */
    public String getOOPath() {
        return getProperty(ooPathKey, "");
    }

    /**
     * Set the path to the openoffice installation on this machine.
     * <p>
     *
     * @param path the path to the openoffice installation on this machine.
     */
    public void setOOPath(String path) {
        setProperty(ooPathKey, path);
        write();
    }

    /**
     * Get whether to use PowerPoint for presentations.
     * <p/>
     *
     * @return true if we should use PowerPoint, false if we should just use the
     * basic POI images or openoffice.
     */
    public boolean getUsePP() {
        return Boolean.parseBoolean(getProperty(usePpKey, "false"));
    }

    /**
     * Set whether to use PowerPoint for presentations.
     * <p/>
     *
     * @param val if we should use PowerPoint, false if we should just use the
     * basic POI images or openoffice.
     */
    public void setUsePP(boolean val) {
        setProperty(usePpKey, Boolean.toString(val));
        write();
    }

    /**
     * Get the path to the PowerPoint installation on this machine.
     * <p/>
     *
     * @return the path to the PowerPoint installation on this machine.
     */
    public String getPPPath() {
        return getProperty(ppPathKey, "");
    }

    /**
     * Set the path to the PowerPoint installation on this machine.
     * <p/>
     *
     * @param path the path to the PowerPoint installation on this machine.
     */
    public void setPPPath(String path) {
        setProperty(ppPathKey, path);
        write();
    }

    /**
     * Get the path to the desired directory for recordings.
     * <p>
     *
     * @return the path to the desired directory for recordings.
     */
    public String getRecordingsPath() {
        return getProperty(recPathKey, "");
    }

    /**
     * Set the path to the desired directory for recordings.
     * <p>
     *
     * @param path the path to the desired directory for recordings.
     */
    public void setRecordingsPath(String path) {
        setProperty(recPathKey, path);
        write();
    }

    /**
     * Get the path to the desired directory for downloading.
     * <p/>
     *
     * @return the path to the desired directory for recordings.
     */
    public String getDownloadPath() {
        return getProperty(downloadPathKey, "");
    }

    /**
     * Set the path to the desired directory for downloading.
     * <p/>
     *
     * @param path the path to the desired directory for downloading.
     */
    public void setDownloadPath(String path) {
        setProperty(downloadPathKey, path);
        write();
    }

    /**
     * Determine if the recordings should be converted to MP3 files.
     * <p>
     *
     * @return true if recordings should be converted, false otherwise.
     */
    public boolean getConvertRecordings() {
        return Boolean.parseBoolean(getProperty(convertMp3Key, "false"));
    }

    /**
     * Set whether to automatically convert the recordings to MP3 files.
     * <p>
     *
     * @param val if we should use covert to MP#, false if we should just store
     * recordings as WAV files.
     */
    public void setConvertRecordings(boolean val) {
        setProperty(convertMp3Key, Boolean.toString(val));
        write();
    }

    /**
     * Determine if the OO presentation should be always on top or not. Not user
     * controlled, but useful for testing.
     * <p>
     *
     * @return true if the presentation should be always on top, false
     * otherwise.
     */
    public boolean getOOPresOnTop() {
        return Boolean.parseBoolean(getProperty(ooOntopKey, "true"));
    }

    /**
     * Sets the logo image location for persistent use
     * <p>
     *
     * @param location File location
     */
    public void setLogoImage(String location) {
        setProperty(logoImageLocationKey, location);
        write();
    }

    /**
     * Return the location of the logo image
     * <p>
     *
     * @return the logo image
     */
    public String getLogoImageURI() {
        return "file:" + getProperty(logoImageLocationKey, "icons/logo default.png");
    }

    /**
     * Sets the port used for mobile lyrics display.
     * <p>
     *
     * @param port the port used for mobile lyrics display.
     */
    public void setMobLyricsPort(int port) {
        setProperty(mobLyricsPortKey, Integer.toString(port));
        write();
    }

    /**
     * Gets the port used for mobile lyrics display.
     * <p>
     *
     * @return the port used for mobile lyrics display.
     */
    public int getMobLyricsPort() {
        return Integer.parseInt(getProperty(mobLyricsPortKey, "1111"));
    }

    /**
     * Determine if we should use mobile lyrics.
     * <p>
     *
     * @return true if we should, false otherwise.
     */
    public boolean getUseMobLyrics() {
        return Boolean.parseBoolean(getProperty(useMobLyricsKey, "false"));
    }

    /**
     * Set if we should use mobile lyrics.
     * <p>
     *
     * @param val true if we should, false otherwise.
     */
    public void setUseMobLyrics(boolean val) {
        setProperty(useMobLyricsKey, Boolean.toString(val));
        write();
    }

    public void setUseRemoteControl(boolean val) {
        setProperty(useRemoteControlKey, Boolean.toString(val));
        write();
    }

    /**
     * Determine if we should set up remote control server.
     * <p>
     *
     * @return true if we should, false otherwise.
     */
    public boolean getUseRemoteControl() {
        return Boolean.parseBoolean(getProperty(useRemoteControlKey, "false"));
    }

    /**
     * Gets the port used for remote control server.
     * <p>
     *
     * @return the port used for mobile lyrics display.
     */
    public int getRemoteControlPort() {
        return Integer.parseInt(getProperty(remoteControlPortKey, "1112"));
    }

    public void setRemoteControlPort(int port) {
        setProperty(remoteControlPortKey, Integer.toString(port));
        write();
    }

    public void setRemoteControlPassword(String text) {
        setProperty(remoteControlPasswordKey, text);
        write();
    }

    public String getRemoteControlPassword() {
        return getProperty(remoteControlPasswordKey, "quelea");
    }

    public void setPlanningCenterRefreshToken(String text) {
        setProperty(planningCenterRefreshToken, text);
        write();
    }

    public String getPlanningCenterRefreshToken() {
        return getProperty(planningCenterRefreshToken, null);
    }

    public String getSmallSongTextPositionH() {
        return getProperty(smallSongTextHPositionKey, "right");
    }

    public void setSmallSongTextPositionH(String position) {
        setProperty(smallSongTextHPositionKey, position);
        write();
    }

    public String getSmallSongTextPositionV() {
        return getProperty(smallSongTextVPositionKey, "bottom");
    }

    public void setSmallSongTextPositionV(String position) {
        setProperty(smallSongTextVPositionKey, position);
        write();
    }

    public Double getSmallSongTextSize() {
        return Double.parseDouble(getProperty(smallSongTextSizeKey, "0.1"));
    }

    public void setSmallSongTextSize(double size) {
        setProperty(smallSongTextSizeKey, Double.toString(size));
        write();
    }

    public String getSmallBibleTextPositionH() {
        return getProperty(smallBibleTextHPositionKey, "right");
    }

    public void setSmallBibleTextPositionH(String position) {
        setProperty(smallBibleTextHPositionKey, position);
        write();
    }

    public String getSmallBibleTextPositionV() {
        return getProperty(smallBibleTextVPositionKey, "bottom");
    }

    public void setSmallBibleTextPositionV(String position) {
        setProperty(smallBibleTextVPositionKey, position);
        write();
    }

    public Double getSmallBibleTextSize() {
        return Double.parseDouble(getProperty(smallBibleTextSizeKey, "0.1"));
    }

    public void setSmallBibleTextSize(double size) {
        setProperty(smallBibleTextSizeKey, Double.toString(size));
        write();
    }

    public boolean getSmallSongTextShow() {
        return Boolean.parseBoolean(getProperty(showSmallSongTextKey, "true"));
    }

    public void setSmallSongTextShow(boolean show) {
        setProperty(showSmallSongTextKey, Boolean.toString(show));
        write();
    }

    public boolean getSmallBibleTextShow() {
        return Boolean.parseBoolean(getProperty(showSmallBibleTextKey, "true"));
    }

    public void setSmallBibleTextShow(boolean show) {
        setProperty(showSmallBibleTextKey, Boolean.toString(show));
        write();
    }

    /**
     * Get how many words or verses to show per slide
     *
     * @return number of words or verses (depends on use.max.bible.verses)
     */
    public int getMaxBibleVerses() {
        return Integer.parseInt(getProperty(maxBibleVersesKey, "5"));
    }

    public void setMaxBibleVerses(int number) {
        setProperty(maxBibleVersesKey, Integer.toString(number));
        write();
    }

    /**
     * Get whether the max items is verses or words
     *
     * @return true if using maximum verses per slide
     */
    public boolean getBibleUsingMaxChars() {
        return Boolean.parseBoolean(getProperty(useMaxBibleCharsKey, "true"));
    }

    public void setBibleUsingMaxChars(boolean useChars) {
        setProperty(useMaxBibleCharsKey, Boolean.toString(useChars));
        write();
    }

    /**
     * Get the maximum number of characters allowed on any one line of bible
     * text.
     * <p>
     *
     * @return the maximum number of characters allowed on any one line of bible
     * text.
     */
    public int getMaxBibleChars() {
        return Integer.parseInt(getProperty(maxBibleCharsKey, "80"));
    }

    /**
     * Set the max bible chars value.
     * <p>
     *
     * @param maxChars the maximum number of characters allowed on any one line
     * of bible text.
     */
    public void setMaxBibleChars(int maxChars) {
        setProperty(maxBibleCharsKey, Integer.toString(maxChars));
        write();
    }

    /**
     * Get the fade duration of the logo button text.
     * <p>
     *
     * @return the duration of the fade in milliseconds text.
     */
    public int getLogoFadeDuration() {
        String t = getProperty(logoFadeDurationKey, "");
        if (t.equals("")) {
            t = "1000";
            setProperty(logoFadeDurationKey, t);
            write();
        }
        return Integer.parseInt(t);
    }

    /**
     * Get the fade duration of the black button text.
     * <p>
     *
     * @return the duration of the fade in milliseconds text.
     */
    public int getBlackFadeDuration() {
        String t = getProperty(blackFadeDurationKey, "");
        if (t.equals("")) {
            t = "1000";
            setProperty(blackFadeDurationKey, t);
            write();
        }
        return Integer.parseInt(t);
    }

    /**
     * Get the fade duration of the clear button text.
     * <p>
     *
     * @return the duration of the fade in milliseconds text.
     */
    public int getClearFadeDuration() {
        String t = getProperty(clearFadeDurationKey, "");
        if (t.equals("")) {
            t = "1000";
            setProperty(clearFadeDurationKey, t);
            write();
        }
        return Integer.parseInt(t);
    }

    /**
     * Get the Translate ID from the properties file
     * <p>
     *
     * @return the translate ID
     */
    public String getTranslateClientID() {
        String t = getProperty(translateClientIdKey, "");
        if (t.equals("")) {
            t = "quelea-projection";
            setProperty(translateClientIdKey, t);
            write();
        }
        return t;
    }

    /**
     * Get the Translate secret key from the properties file
     * <p>
     *
     * @return the translate secret key
     */
    public String getTranslateClientSecret() {
        String t = getProperty(translateClientSecretKey, "");
        if (t.equals("")) {
            t = "wk4+wd9YJkjIHmz2qwD1oR7pP9/kuHOL6OsaOKEi80U=";
            setProperty(translateClientSecretKey, t);
            write();
        }
        return t;
    }

    public boolean getClearStageWithMain() {
        return Boolean.parseBoolean(getProperty(clearStageviewWithMainKey, "true"));
    }

    public void setClearStageWithMain(boolean clear) {
        setProperty(clearStageviewWithMainKey, Boolean.toString(clear));
        write();
    }

    /**
     * Get the directory used for storing countdown timers.
     * <p>
     *
     * @return the timer directory
     */
    public File getTimerDir() {
        return new File(getQueleaUserHome(), "timer");
    }

    public boolean getSongOverflow() {
        return Boolean.parseBoolean(getProperty(songOverflowKey, "false"));
    }

    public void setSongOverflow(boolean overflow) {
        setProperty(songOverflowKey, Boolean.toString(overflow));
        write();
    }

    public int getAutoDetectPort() {
        return Integer.parseInt(getProperty(autoDetectPortKey, "50015"));
    }

    public boolean getUse24HourClock() {
        return Boolean.parseBoolean(getProperty(use24hClockKey, "true"));
    }

    public void setUse24HourClock(boolean s24h) {
        setProperty(use24hClockKey, Boolean.toString(s24h));
        write();
    }

    public boolean getBibleSplitVerses() {
        return Boolean.parseBoolean(getProperty(splitBibleVersesKey, "false"));
    }

    public void setBibleSplitVerses(boolean selected) {
        setProperty(splitBibleVersesKey, Boolean.toString(selected));
        write();
    }

    public double getLyricWidthBounds() {
        return Double.parseDouble(getProperty(lyricWidthBoundKey, "0.92"));
    }

    public double getLyricHeightBounds() {
        return Double.parseDouble(getProperty(lyricHeightBoundKey, "0.9"));
    }

    public boolean getDefaultSongDBUpdate() {
        return Boolean.parseBoolean(getProperty(defaultSongDbUpdateKey, "true"));
    }

    public boolean getShowDBSongPreview() {
        return Boolean.parseBoolean(getProperty(dbSongPreviewKey, "false"));
    }

    public void setShowDBSongPreview(boolean val) {
        setProperty(dbSongPreviewKey, Boolean.toString(val));
    }

    public boolean getImmediateSongDBPreview() {
        return Boolean.parseBoolean(getProperty("db.song.immediate.preview", "false"));
    }

    public void setImmediateSongDBPreview(boolean val) {
        setProperty("db.song.immediate.preview", Boolean.toString(val));
    }

    public void setDefaultSongDBUpdate(boolean updateInDB) {
        setProperty(defaultSongDbUpdateKey, Boolean.toString(updateInDB));
        write();
    }

    public int getWebDisplayableRefreshRate() {
        return Integer.parseInt(getProperty(webRefreshRateKey, "500"));
    }

    public String getWebProxyHost() {
        return getProperty(webProxyHostKey, null);
    }

    public String getWebProxyPort() {
        return getProperty(webProxyPortKey, null);
    }

    public String getWebProxyUser() {
        return getProperty(webProxyUserKey, null);
    }

    public String getWebProxyPassword() {
        return getProperty(webProxyPasswordKey, null);
    }

    public String getChurchCcliNum() {
        return getProperty(churchCcliNumKey, null);
    }

    /**
     * Get the directory used for storing notices.
     * <p>
     *
     * @return the notice directory
     */
    public File getNoticeDir() {
        return new File(getQueleaUserHome(), "notices");
    }

    /**
     * Set whether fade should be used.
     *
     * @param useFade true if fade should be used
     */
    public void setUseSlideTransition(boolean useFade) {
        setProperty(useSlideTransitionKey, Boolean.toString(useFade));
    }

    /**
     * Determine whether fade should be used.
     *
     * @return true if fade is enabled, false otherwise.
     */
    public boolean getUseSlideTransition() {
        return Boolean.parseBoolean(getProperty(useSlideTransitionKey, "false"));
    }

    /**
     * Set the slide transition in duration.
     *
     * @param millis milliseconds for fade-in effect.
     */
    public void setSlideTransitionInDuration(int millis) {
        setProperty(slideTransitionInDurationKey, Integer.toString(millis));
    }

    /**
     * Get the slide transition in duration.
     *
     * @return milliseconds for fade-in effect.
     */
    public int getSlideTransitionInDuration() {
        return Integer.parseInt(getProperty(slideTransitionInDurationKey, "750"));
    }

    /**
     * Get the slide transition out duration.
     *
     * @return milliseconds for fade-out effect.
     */
    public int getSlideTransitionOutDuration() {
        return Integer.parseInt(getProperty(slideTransitionOutDurationKey, "400"));
    }

    /**
     * Set the slide transition out duration.
     *
     * @param millis milliseconds for fade-out effect.
     */
    public void setSlideTransitionOutDuration(int millis) {
        setProperty(slideTransitionOutDurationKey, Integer.toString(millis));
    }

    public boolean getUseDarkTheme() {
        return Boolean.parseBoolean(getProperty(darkThemeKey, "false"));
    }

    public void setUseDarkTheme(boolean useDarkTheme) {
        setProperty(darkThemeKey, String.valueOf(useDarkTheme));
    }
}