//
//  XTUserOptions.m
//  TadsTerp
//
//  Created by Rune Berg on 17/09/14.
//  Copyright (c) 2014 Rune Berg. All rights reserved.
//

#import "XTPrefs.h"
#import "XTStringUtils.h"
#import "XTLogger.h"


//TODO consider an obj per pref
//TODO unit test

@interface XTPrefs ()

@property NSDictionary *defaultUserDefaultsDictForInternal;
@property NSDictionary *defaultUserDefaultsDictForDirsAndFiles;
@property NSDictionary *defaultUserDefaultsDictForFonts;
@property NSDictionary *defaultUserDefaultsDictForColors;
@property NSDictionary *defaultUserDefaultsDictForLayout;
@property NSDictionary *defaultUserDefaultsDictForIOSafetyModes;
@property NSDictionary *defaultUserDefaultsDictForMisc;
@property NSDictionary *defaultUserDefaultsDictForDevMode;
@property NSArray *unusedUserDefaultsKeys;

@property BOOL skipPersistOnKVOEvent;

//TODO no, use transform func instead
@property BOOL enableChoiceOfSpecificGamesDirectory;
@property BOOL enableChoiceOfSpecificSavesDirectory;
@property BOOL enableChoiceOfSpecificTranscriptsDirectory;
@property BOOL enableChoiceOfSpecificCommandScriptsDirectory;

@end


@implementation XTPrefs

static XTLogger* logger;

//TODO why not instance variables?
static SEL *selectorsForDirsAndFiles;
static NSUInteger countSelectorsForDirsAndFiles;
static SEL *selectorsForFonts;
static NSUInteger countSelectorsForFonts;
static SEL *selectorsForColors;
static NSUInteger countSelectorsForColors;
static SEL *selectorsForLayout;
static NSUInteger countSelectorsForLayout;
static SEL *selectorsForIOSafetyModes;
static NSUInteger countSelectorsForIOSafetyModes;
static SEL *selectorsForMisc;
static NSUInteger countSelectorsForMisc;
static SEL *selectorsForDevMode;
static NSUInteger countSelectorsForDevMode;

#define KEY_GAMES_DIRECTORY_MODE @"XTadsGamesDirectoryMode"
#define KEY_GAMES_DIRECTORY_WHEN_SPECIFIC @"XTadsGamesDirectoryWhenSpecific"
#define KEY_GAMES_DIRECTORY_LAST_USED @"XTadsGamesDirectoryLastUsed"

#define KEY_SAVES_DIRECTORY_MODE @"XTadsSavesDirectoryMode"
#define KEY_SAVES_DIRECTORY_WHEN_SPECIFIC @"XTadsSavesDirectoryWhenSpecific"
#define KEY_SAVES_DIRECTORY_LAST_USED @"XTadsSavesDirectoryLastUsed"
#define KEY_SAVES_FILE_NAME_MODE @"XTadsSavesFileNameMode"

#define KEY_TRANSCRIPTS_DIRECTORY_MODE @"XTadsTranscriptsDirectoryMode"
#define KEY_TRANSCRIPTS_DIRECTORY_WHEN_SPECIFIC @"XTadsTranscriptsDirectoryWhenSpecific"
#define KEY_TRANSCRIPTS_DIRECTORY_LAST_USED @"XTadsTranscriptsDirectoryLastUsed"
#define KEY_TRANSCRIPTS_FILE_NAME_MODE @"XTadsTranscriptsFileNameMode"

#define KEY_COMMANDSCRIPTS_DIRECTORY_MODE @"XTadsCommandScriptsDirectoryMode"
#define KEY_COMMANDSCRIPTS_DIRECTORY_WHEN_SPECIFIC @"XTadsCommandScriptsDirectoryWhenSpecific"
#define KEY_COMMANDSCRIPTS_DIRECTORY_LAST_USED @"XTadsCommandScriptsDirectoryLastUsed"
#define KEY_COMMANDSCRIPTS_FILE_NAME_MODE @"XTadsCommandScriptsFileNameMode"

#define KEY_USER_DEFAULTS_WRITE_COUNTER @"XTadsPrefsWriteCounter"

#define KEY_DEFAULT_FONT_NAME @"XTadsDefaultFontName"
#define KEY_DEFAULT_FONT_SIZE @"XTadsDefaultFontSize"

#define KEY_FIXEDWIDTH_FONT_NAME @"XTadsFixedWidthFontName"
#define KEY_FIXEDWIDTH_FONT_SIZE @"XTadsFixedWidthFontSize"

#define KEY_SERIFED_FONT_NAME @"XTadsSerifedFontName"
#define KEY_SERIFED_FONT_SIZE  @"XTadsSerifedFontSize"

#define KEY_SANSSERIF_FONT_NAME @"XTadsSansSerifFontName"
#define KEY_SANSSERIF_FONT_SIZE @"XTadsSansSerifFontSize"

#define KEY_SCRIPT_FONT_NAME @"XTadsScriptFontName"
#define KEY_SCRIPT_FONT_SIZE @"XTadsScriptFontSize"

#define KEY_TYPEWRITER_FONT_NAME @"XTadsTypewriterFontName"
#define KEY_TYPEWRITER_FONT_SIZE @"XTadsTypewriterFontSize"

#define KEY_INPUT_FONT_IS_SAME_AS_DEFAULT_FONT @"XTadsInputFontIsSameAsDefaultFont"
#define KEY_INPUT_FONT_NAME @"XTadsInputFontName"
#define KEY_INPUT_FONT_SIZE @"XTadsInputFontSize"
#define KEY_INPUT_FONT_USED_EVEN_IF_NOT_REQUESTED_BY_GAME @"XTadsInputFontUsedEvenIfNotRequestedByGame"

#define KEY_STATUSLINE_FONT_IS_SAME_AS_DEFAULT_FONT @"XTadsStatusLineFontIsSameAsDefaultFont"
#define KEY_STATUSLINE_FONT_NAME @"XTadsStatusLineFontName"
#define KEY_STATUSLINE_FONT_SIZE @"XTadsStatusLineFontSize"

#define KEY_ALLOW_GAMES_TO_SET_FONTS @"XTadsAllowGamesToSetFonts"
#define KEY_MIN_ALLOWED_FONT_SIZE @"XTadsMinAllowedFontSize"
#define KEY_MAX_ALLOWED_FONT_SIZE @"XTadsMaxAllowedFontSize"

#define KEY_STATUSLINE_TEXTCOLOR @"XTadsStatusLineTextColor"
#define KEY_STATUSLINE_BACKGROUNDCOLOR @"XTadsStatusLineBackgroundColor"

#define KEY_OUTPUTAREA_TEXTCOLOR @"XTadsOutputAreaTextColor"
#define KEY_OUTPUTAREA_BACKGROUNDCOLOR @"XTadsOutputAreaBackgroundColor"

#define KEY_INPUT_TEXTCOLOR @"XTadsInputTextColor"

#define KEY_LINKS_TEXTCOLOR @"XTadsLinksTextColor"
#define KEY_LINKS_UNDERLINE @"XTadsLinksUnderline"
#define KEY_LINKS_SHOW_TOOLTIPS @"XTadsLinksShowTooltips"

#define KEY_ALLOW_GAMES_TO_SET_FONT_COLORS @"XTadsAllowGamesToSetFontColors"
#define KEY_ALLOW_GAMES_TO_SET_BACKGROUND_COLORS @"XTadsAllowGamesToSetBackgroundColors"

#define KEY_GAME_WINDOW_START_MODE @"XTadsGameWindowStartMode"

#define KEY_IO_SAFETY_MODE_READ @"XTadsIOSafetyModeRead"
#define KEY_IO_SAFETY_MODE_WRITE @"XTadsIOSafetyModeWrite"

#define KEY_ASK_FOR_GAME_FILE_ON_TERP_START @"XTadsAskForGameFileOnTerpStart"
#define KEY_PRINT_TADS_BANNER_ON_GAME_START @"XTadsPrintTadsBannerOnGameStart"
#define KEY_ASK_FOR_CONFIRMATION_ON_GAME_RESTART @"XTadsAskForConfirmationOnGameRestart"
#define KEY_ASK_FOR_CONFIRMATION_ON_GAME_QUIT @"XTadsAskForConfirmationOnGameQuit"
#define KEY_ASK_FOR_CONFIRMATION_ON_GAME_OPEN_IF_GAME_RUNNING @"XTadsAskForConfirmationOnGameOpenIfGameRunning"
#define KEY_ENABLE_DEVELOPMENT_FEATURES @"XTadsEnableDevelopmentFeatures"
#define KEY_LIMIT_SCROLLBACK_BUFFER_SIZE @"XTadsLimitScrollBackBufferSize"
#define KEY_SCROLLBACK_BUFFER_SIZE_IN_KB @"XTadsScrollBackBufferSizeInKB"
#define KEY_TADS2_ENCODING @"XTadsTads2Encoding"
#define KEY_TADS2_ENCODING_OVERRIDE @"XTadsTads2EncodingOverride"

#define KEY_PRINT_BROKEN_HTML_MARKUP @"XTadsPrintBrokenHtmlMarkup"
#define KEY_SHOW_PARSER_MODE @"XTadsShowParserMode"
#define KEY_SPELL_CHECK_GAME_TEXT @"XTadsSpellCheckGameText"
#define KEY_GRAMMAR_CHECK_GAME_TEXT @"XTadsGrammarCheckGameText"


#define VALUE_DIRECTORY_MODE_SPECIFIC @"specific"
#define VALUE_DIRECTORY_MODE_NONE @"none"
#define VALUE_DIRECTORY_MODE_LAST_SELECTED @"lastSelected"
#define VALUE_DIRECTORY_MODE_CURRENT_GAMEFILE @"sameAsCurrentGameFile"

#define VALUE_FILE_NAME_MODE_DATE_TIME @"gameFileTimestamp"
#define VALUE_FILE_NAME_MODE_UNTITLED @"untitled"

#define VALUE_IO_SAFETY_MODE_NO_ACCESS @"noAccess"
#define VALUE_IO_SAFETY_MODE_GAME_DIR_ONLY @"gameDirOnly"
#define VALUE_IO_SAFETY_MODE_ANYWHERE @"anywhere"

#define VALUE_GAME_WINDOW_START_MODE_SAME_AS_LAST @"sameAsLast"
#define VALUE_GAME_WINDOW_START_MODE_NICE_IN_MIDDLE @"niceInMiddle"
#define VALUE_GAME_WINDOW_START_MODE_WHATEVER @"whatever"


#define DEFAULT_DEFAULT_FONT_NAME @"Helvetica"
#define DEFAULT_DEFAULT_FONT_SIZE [NSNumber numberWithFloat:14.0]

#define DEFAULT_FIXEDWIDTH_FONT_NAME @"Consolas"
#define DEFAULT_FIXEDWIDTH_FONT_SIZE [NSNumber numberWithFloat:14]

#define DEFAULT_SERIFED_FONT_NAME @"Georgia"
#define DEFAULT_SERIFED_FONT_SIZE [NSNumber numberWithFloat:14]

#define DEFAULT_SANSSERIF_FONT_NAME @"Helvetica"
#define DEFAULT_SANSSERIF_FONT_SIZE [NSNumber numberWithFloat:14]

#define DEFAULT_SCRIPT_FONT_NAME @"Apple Chancery"
#define DEFAULT_SCRIPT_FONT_SIZE [NSNumber numberWithFloat:14]

#define DEFAULT_TYPEWRITER_FONT_NAME @"Consolas"
#define DEFAULT_TYPEWRITER_FONT_SIZE [NSNumber numberWithFloat:14]

#define DEFAULT_INPUT_FONT_IS_SAME_AS_DEFAULT_FONT [NSNumber numberWithBool:TRUE]
#define DEFAULT_INPUT_FONT_NAME @"Helvetica"
#define DEFAULT_INPUT_FONT_SIZE [NSNumber numberWithFloat:14]
#define DEFAULT_INPUT_FONT_USED_EVEN_IF_NOT_REQUESTED_BY_GAME [NSNumber numberWithBool:TRUE]

#define DEFAULT_STATUSLINE_FONT_IS_SAME_AS_DEFAULT_FONT [NSNumber numberWithBool:TRUE]
#define DEFAULT_STATUSLINE_FONT_NAME @"Helvetica"
#define DEFAULT_STATUSLINE_FONT_SIZE [NSNumber numberWithFloat:14]

#define DEFAULT_ALLOW_GAMES_TO_SET_FONTS [NSNumber numberWithBool:TRUE]
#define DEFAULT_MIN_ALLOWED_FONT_SIZE [NSNumber numberWithInteger:6]
#define DEFAULT_MAX_ALLOWED_FONT_SIZE [NSNumber numberWithInteger:72]

#define DEFAULT_STATUSLINE_TEXTCOLOR [NSColor whiteColor]
#define DEFAULT_STATUSLINE_BACKGROUNDCOLOR [NSColor blackColor]

#define DEFAULT_OUTPUTAREA_TEXTCOLOR [NSColor blackColor]
#define DEFAULT_OUTPUTAREA_BACKGROUNDCOLOR [NSColor whiteColor]

#define DEFAULT_INPUT_TEXTCOLOR [NSColor blackColor]

#define DEFAULT_LINKS_TEXTCOLOR [NSColor blueColor]
#define DEFAULT_LINKS_UNDERLINE [NSNumber numberWithBool:TRUE]
#define DEFAULT_LINKS_SHOW_TOOLTIPS [NSNumber numberWithBool:TRUE]

#define DEFAULT_ALLOW_GAMES_TO_SET_FONT_COLORS [NSNumber numberWithBool:TRUE]
#define DEFAULT_ALLOW_GAMES_TO_SET_BACKGROUND_COLORS [NSNumber numberWithBool:TRUE]

#define DEFAULT_GAME_WINDOW_START_MODE VALUE_GAME_WINDOW_START_MODE_SAME_AS_LAST

//TODO why not used?
//#define DEFAULT_IO_SAFETY_MODE_READ   XTPREFS_IO_SAFETY_MODE_GAME_DIR_ONLY
//#define DEFAULT_IO_SAFETY_MODE_WRITE  XTPREFS_IO_SAFETY_MODE_GAME_DIR_ONLY

#define DEFAULT_ASK_FOR_GAME_FILE_ON_TERP_START [NSNumber numberWithBool:TRUE]
#define DEFAULT_PRINT_TADS_BANNER_ON_GAME_START [NSNumber numberWithBool:FALSE]
#define DEFAULT_ASK_FOR_CONFIRMATION_ON_GAME_RESTART [NSNumber numberWithBool:TRUE]
#define DEFAULT_ASK_FOR_CONFIRMATION_ON_GAME_QUIT [NSNumber numberWithBool:TRUE]
#define DEFAULT_ASK_FOR_CONFIRMATION_ON_GAME_OPEN_IF_GAME_RUNNING [NSNumber numberWithBool:TRUE]
#define DEFAULT_ENABLE_DEVELOPMENT_FEATURES [NSNumber numberWithBool:FALSE]
#define DEFAULT_LIMIT_SCROLLBACK_BUFFER_SIZE [NSNumber numberWithBool:TRUE]
#define DEFAULT_SCROLLBACK_BUFFER_SIZE_IN_KB [NSNumber numberWithUnsignedInteger:XTPREFS_SCROLLBACK_BUFFER_SIZE_50KB]
#define DEFAULT_TADS2_ENCODING [NSNumber numberWithUnsignedInteger:NSISOLatin1StringEncoding]
#define DEFAULT_TADS2_ENCODING_OVERRIDE [NSNumber numberWithBool:FALSE]

#define DEFAULT_PRINT_BROKEN_HTML_MARKUP [NSNumber numberWithBool:FALSE]
#define DEFAULT_SHOW_PARSER_MODE [NSNumber numberWithBool:FALSE]
#define DEFAULT_SPELL_CHECK_GAME_TEXT [NSNumber numberWithBool:FALSE]
#define DEFAULT_GRAMMAR_CHECK_GAME_TEXT [NSNumber numberWithBool:FALSE]


#define LOG_FMT_OBJ_EQ_OBJ @"\"%@\" = \"%@\""


static XTPrefs *singletonInstance = nil;


+ (void)initialize
{
	if (singletonInstance != nil) {
		// we can be called more than once :-(
		return;
	}
	
	logger = [XTLogger loggerForClass:[XTPrefs class]];

	SEL selectorsFDM[] = {
		@selector(gamesDirectoryMode),
		@selector(gamesDirectoryWhenSpecific),
		@selector(gamesDirectoryLastUsed),
		
		@selector(savesDirectoryMode),
		@selector(savesDirectoryWhenSpecific),
		@selector(savesDirectoryLastUsed),
		@selector(savesFileNameMode),
		
		@selector(transcriptsDirectoryMode),
		@selector(transcriptsDirectoryWhenSpecific),
		@selector(transcriptsDirectoryLastUsed),
		@selector(transcriptsFileNameMode),

		@selector(commandScriptsDirectoryMode),
		@selector(commandScriptsDirectoryWhenSpecific),
		@selector(commandScriptsDirectoryLastUsed),
		@selector(commandScriptsFileNameMode)
	};
	countSelectorsForDirsAndFiles = sizeof(selectorsFDM) / sizeof(selectorsFDM[0]);
	selectorsForDirsAndFiles = [XTPrefs copySelectorArray:selectorsFDM countElements:countSelectorsForDirsAndFiles];

	SEL selectorsFF[] = {
		@selector(defaultFontName),
		@selector(defaultFontSize),
		
		@selector(fixedWidthFontName),
		@selector(fixedWidthFontSize),
		
		@selector(serifedFontName),
		@selector(serifedFontSize),
		
		@selector(sansSerifFontName),
		@selector(sansSerifFontSize),
		
		@selector(scriptFontName),
		@selector(scriptFontSize),
		
		@selector(typewriterFontName),
		@selector(typewriterFontSize),
		
		@selector(inputFontIsSameAsDefaultFont),
		@selector(inputFontName),
		@selector(inputFontSize),
		@selector(inputFontUsedEvenIfNotRequestedByGame),
		
		@selector(statusLineFontIsSameAsDefaultFont),
		@selector(statusLineFontName),
		@selector(statusLineFontSize),
		
		@selector(allowGamesToSetFonts),
		@selector(minAllowedFontSize),
		@selector(maxAllowedFontSize)

	};
	countSelectorsForFonts = sizeof(selectorsFF) / sizeof(selectorsFF[0]);
	selectorsForFonts = [XTPrefs copySelectorArray:selectorsFF countElements:countSelectorsForFonts];
	
	SEL selectorsFC[] = {
		@selector(statusLineTextColor),
		@selector(statusLineBackgroundColor),
		@selector(outputAreaTextColor),
		@selector(outputAreaBackgroundColor),
		@selector(inputTextColor),
		@selector(linksTextColor),
		@selector(linksUnderline),
		@selector(linksShowToolTips),
		@selector(allowGamesToSetFontColors),
		@selector(allowGamesToSetBackgroundColors)
	};
	countSelectorsForColors = sizeof(selectorsFC) / sizeof(selectorsFC[0]);
	selectorsForColors = [XTPrefs copySelectorArray:selectorsFC countElements:countSelectorsForColors];
	
	SEL selectorsLO[] = {
		@selector(gameWindowStartMode)
	};
	countSelectorsForLayout = sizeof(selectorsLO) / sizeof(selectorsLO[0]);
	selectorsForLayout = [XTPrefs copySelectorArray:selectorsLO countElements:countSelectorsForLayout];
	
	SEL selectorsIO[] = {
		@selector(readSafetyMode),
		@selector(writeSafetyMode)
	};
	countSelectorsForIOSafetyModes = sizeof(selectorsIO) / sizeof(selectorsIO[0]);
	selectorsForIOSafetyModes = [XTPrefs copySelectorArray:selectorsIO countElements:countSelectorsForIOSafetyModes];

	SEL selectorsMisc[] = {
		@selector(askForGameFileOnTerpStart),
		@selector(printTadsBannerOnGameStart),
		@selector(askForConfirmationOnGameRestart),
		@selector(askForConfirmationOnGameQuit),
		@selector(askForConfirmationOnGameOpenIfGameRunning),
		@selector(enableDevelopmentModeFeatures),
		@selector(limitScrollbackBufferSize),
		@selector(scrollbackBufferSizeInKBs),
		@selector(tads2Encoding),
		@selector(tads2EncodingOverride)
	};
	countSelectorsForMisc = sizeof(selectorsMisc) / sizeof(selectorsMisc[0]);
	selectorsForMisc = [XTPrefs copySelectorArray:selectorsMisc countElements:countSelectorsForMisc];

	SEL selectorsDevMode[] = {
		@selector(printBrokenHtmlMarkup),
		@selector(showParserMode),
		@selector(spellCheckGameText),
		@selector(grammarCheckGameText)
	};
	countSelectorsForDevMode = sizeof(selectorsDevMode) / sizeof(selectorsDevMode[0]);
	selectorsForDevMode = [XTPrefs copySelectorArray:selectorsDevMode countElements:countSelectorsForDevMode];
	
	singletonInstance = [XTPrefs new];
}

+ (SEL *)copySelectorArray:(SEL *)selectorArray countElements:(NSUInteger)countElements
{
	NSUInteger sizeInBytes = sizeof(selectorArray[0]) * countElements;
	SEL *copyArray = malloc(sizeInBytes);
	for (NSUInteger i = 0; i < countElements; i++) {
		copyArray[i] = selectorArray[i];
	}
	return copyArray;
}

+ (id) prefs;
{
	return singletonInstance;
}

- (id)init
{
	self = [super init];
	if (self != nil) {
		//TO DO rm:
		//[self cleanupAll];
		
		// ensure sane default prefs, just in case reading from NSUserDefaults fails or isn't done
		[self setDefaultDirsAndFilesPrefs];
		[self setDefaultFontsPrefs];
		[self setDefaultColorsPrefs];
		[self setDefaultLayoutPrefs];
		[self setDefaultIOSafetyPrefs];
		[self setDefaultMiscPrefs];
		[self setDefaultDevModePrefs];
		
		[self refreshDerivedNonPersistedPrefs];
		
		[self initDefaultUserDefaultsDicts];
		[self initUnusedUserDefaultsKeys];
		
		_skipPersistOnKVOEvent = NO;
		
		_alwaysFalse = NO;
		_minMinAllowedFontSize = DEFAULT_MIN_ALLOWED_FONT_SIZE;
		_maxMaxAllowedFontSize = DEFAULT_MAX_ALLOWED_FONT_SIZE;
		
	}
	return self;
}

- (void)initDefaultUserDefaultsDicts
{
	_defaultUserDefaultsDictForInternal = @{
		// Turn on regular press-and-hold behaviour in NSTextView, disabling accented char popups:
		// (https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKitOlderNotes/)
		@"ApplePressAndHoldEnabled": [NSNumber numberWithBool:NO],

		// Turn off Edit|Start Dictation and Edit|Emoji & Symbols
		// See: http://stackoverflow.com/questions/21369736/remove-start-dictation-and-special-characters-from-menu
		@"NSDisabledDictationMenuItem": [NSNumber numberWithBool:YES],
		@"NSDisabledCharacterPaletteMenuItem": [NSNumber numberWithBool:YES],
		
		KEY_USER_DEFAULTS_WRITE_COUNTER: [NSNumber numberWithInteger:0]
	};
	
	_defaultUserDefaultsDictForDirsAndFiles = @{
	    KEY_GAMES_DIRECTORY_MODE:          VALUE_DIRECTORY_MODE_NONE,
		KEY_GAMES_DIRECTORY_WHEN_SPECIFIC: [NSNull null],
		KEY_GAMES_DIRECTORY_LAST_USED:	   [NSNull null],
		
		KEY_SAVES_DIRECTORY_MODE:          VALUE_DIRECTORY_MODE_NONE,
		KEY_SAVES_DIRECTORY_WHEN_SPECIFIC: [NSNull null],
		KEY_SAVES_DIRECTORY_LAST_USED:	   [NSNull null],
		KEY_SAVES_FILE_NAME_MODE:          VALUE_FILE_NAME_MODE_DATE_TIME,
													  
		KEY_TRANSCRIPTS_DIRECTORY_MODE:          VALUE_DIRECTORY_MODE_NONE,
		KEY_TRANSCRIPTS_DIRECTORY_WHEN_SPECIFIC: [NSNull null],
		KEY_TRANSCRIPTS_DIRECTORY_LAST_USED:	 [NSNull null],
		KEY_TRANSCRIPTS_FILE_NAME_MODE:          VALUE_FILE_NAME_MODE_DATE_TIME,

		KEY_COMMANDSCRIPTS_DIRECTORY_MODE:          VALUE_DIRECTORY_MODE_NONE,
		KEY_COMMANDSCRIPTS_DIRECTORY_WHEN_SPECIFIC: [NSNull null],
		KEY_COMMANDSCRIPTS_DIRECTORY_LAST_USED:		[NSNull null],
		KEY_COMMANDSCRIPTS_FILE_NAME_MODE:          VALUE_FILE_NAME_MODE_DATE_TIME,
	};
	
	_defaultUserDefaultsDictForFonts = @{
		KEY_DEFAULT_FONT_NAME: DEFAULT_DEFAULT_FONT_NAME,
		KEY_DEFAULT_FONT_SIZE: DEFAULT_DEFAULT_FONT_SIZE,
											   
		KEY_FIXEDWIDTH_FONT_NAME: DEFAULT_FIXEDWIDTH_FONT_NAME,
		KEY_FIXEDWIDTH_FONT_SIZE: DEFAULT_FIXEDWIDTH_FONT_SIZE,
											   
		KEY_SERIFED_FONT_NAME: DEFAULT_SERIFED_FONT_NAME,
		KEY_SERIFED_FONT_SIZE: DEFAULT_SERIFED_FONT_SIZE,
											   
		KEY_SANSSERIF_FONT_NAME: DEFAULT_SANSSERIF_FONT_NAME,
		KEY_SANSSERIF_FONT_SIZE: DEFAULT_SANSSERIF_FONT_SIZE,
											   
		KEY_SCRIPT_FONT_NAME: DEFAULT_SCRIPT_FONT_NAME,
		KEY_SCRIPT_FONT_SIZE: DEFAULT_SCRIPT_FONT_SIZE,
											   
		KEY_TYPEWRITER_FONT_NAME: DEFAULT_TYPEWRITER_FONT_NAME,
		KEY_TYPEWRITER_FONT_SIZE: DEFAULT_TYPEWRITER_FONT_SIZE,
											   
		KEY_INPUT_FONT_IS_SAME_AS_DEFAULT_FONT:            DEFAULT_INPUT_FONT_IS_SAME_AS_DEFAULT_FONT,
		KEY_INPUT_FONT_NAME:                               DEFAULT_INPUT_FONT_NAME,
		KEY_INPUT_FONT_SIZE:                               DEFAULT_INPUT_FONT_SIZE,
		KEY_INPUT_FONT_USED_EVEN_IF_NOT_REQUESTED_BY_GAME: DEFAULT_INPUT_FONT_USED_EVEN_IF_NOT_REQUESTED_BY_GAME,
		
		KEY_STATUSLINE_FONT_IS_SAME_AS_DEFAULT_FONT: DEFAULT_STATUSLINE_FONT_IS_SAME_AS_DEFAULT_FONT,
		KEY_STATUSLINE_FONT_NAME:                    DEFAULT_STATUSLINE_FONT_NAME,
		KEY_STATUSLINE_FONT_SIZE:                    DEFAULT_STATUSLINE_FONT_SIZE,
		
		KEY_ALLOW_GAMES_TO_SET_FONTS: DEFAULT_ALLOW_GAMES_TO_SET_FONTS,
		KEY_MIN_ALLOWED_FONT_SIZE:    DEFAULT_MIN_ALLOWED_FONT_SIZE,
		KEY_MAX_ALLOWED_FONT_SIZE:	  DEFAULT_MAX_ALLOWED_FONT_SIZE,
	};
	
	_defaultUserDefaultsDictForColors = @{
		KEY_STATUSLINE_TEXTCOLOR:       DEFAULT_STATUSLINE_TEXTCOLOR,
		KEY_STATUSLINE_BACKGROUNDCOLOR: DEFAULT_STATUSLINE_BACKGROUNDCOLOR,

		KEY_OUTPUTAREA_TEXTCOLOR:       DEFAULT_OUTPUTAREA_TEXTCOLOR,
		KEY_OUTPUTAREA_BACKGROUNDCOLOR: DEFAULT_OUTPUTAREA_BACKGROUNDCOLOR,

		KEY_INPUT_TEXTCOLOR: DEFAULT_INPUT_TEXTCOLOR,
		
		KEY_LINKS_TEXTCOLOR: DEFAULT_LINKS_TEXTCOLOR,
		KEY_LINKS_UNDERLINE: DEFAULT_LINKS_UNDERLINE,
		KEY_LINKS_SHOW_TOOLTIPS: DEFAULT_LINKS_SHOW_TOOLTIPS,
		
		KEY_ALLOW_GAMES_TO_SET_FONT_COLORS:			DEFAULT_ALLOW_GAMES_TO_SET_FONT_COLORS,
		KEY_ALLOW_GAMES_TO_SET_BACKGROUND_COLORS:	DEFAULT_ALLOW_GAMES_TO_SET_BACKGROUND_COLORS,
	};
	
	_defaultUserDefaultsDictForLayout = @{
		KEY_GAME_WINDOW_START_MODE: DEFAULT_GAME_WINDOW_START_MODE
	};
	
	_defaultUserDefaultsDictForIOSafetyModes = @{
		KEY_IO_SAFETY_MODE_READ:  VALUE_IO_SAFETY_MODE_GAME_DIR_ONLY,
		KEY_IO_SAFETY_MODE_WRITE: VALUE_IO_SAFETY_MODE_GAME_DIR_ONLY
	};

	_defaultUserDefaultsDictForMisc = @{
		KEY_ASK_FOR_GAME_FILE_ON_TERP_START:				   DEFAULT_ASK_FOR_GAME_FILE_ON_TERP_START,
		KEY_PRINT_TADS_BANNER_ON_GAME_START:				   DEFAULT_PRINT_TADS_BANNER_ON_GAME_START,
		KEY_ASK_FOR_CONFIRMATION_ON_GAME_RESTART:			   DEFAULT_ASK_FOR_CONFIRMATION_ON_GAME_RESTART,
		KEY_ASK_FOR_CONFIRMATION_ON_GAME_QUIT:				   DEFAULT_ASK_FOR_CONFIRMATION_ON_GAME_QUIT,
		KEY_ASK_FOR_CONFIRMATION_ON_GAME_OPEN_IF_GAME_RUNNING: DEFAULT_ASK_FOR_CONFIRMATION_ON_GAME_OPEN_IF_GAME_RUNNING,
		KEY_ENABLE_DEVELOPMENT_FEATURES:					   DEFAULT_ENABLE_DEVELOPMENT_FEATURES,
		KEY_LIMIT_SCROLLBACK_BUFFER_SIZE:					   DEFAULT_LIMIT_SCROLLBACK_BUFFER_SIZE,
		KEY_SCROLLBACK_BUFFER_SIZE_IN_KB:					   DEFAULT_SCROLLBACK_BUFFER_SIZE_IN_KB,
		KEY_TADS2_ENCODING:									   DEFAULT_TADS2_ENCODING,
		KEY_TADS2_ENCODING_OVERRIDE:						   DEFAULT_TADS2_ENCODING_OVERRIDE
	};
	
	_defaultUserDefaultsDictForDevMode = @{
		KEY_PRINT_BROKEN_HTML_MARKUP:  DEFAULT_PRINT_BROKEN_HTML_MARKUP,
		KEY_SHOW_PARSER_MODE:		   DEFAULT_SHOW_PARSER_MODE,
		KEY_SPELL_CHECK_GAME_TEXT:	   DEFAULT_SPELL_CHECK_GAME_TEXT,
		KEY_GRAMMAR_CHECK_GAME_TEXT:   DEFAULT_GRAMMAR_CHECK_GAME_TEXT
	};
}

- (void)initUnusedUserDefaultsKeys
{
	_unusedUserDefaultsKeys = @[
	    // keys of no-longer-used prefs go here:

		@"XTadsLimitSizeOfScrollBackBuffer",
		@"XTadsSizeOfScrollBackBufferInKB"
		//TODO rm:
		//@"NSDisabledDictationMenuItem",
		//@"NSDisabledCharacterPaletteMenuItem"
	];
}

- (void)registerMissing
{
	BOOL didSetAValueForInternal = [self registerMissingUserDefaultsInDict:self.defaultUserDefaultsDictForInternal];
	BOOL didSetAValueForDirsAndFiles = [self registerMissingUserDefaultsInDict:self.defaultUserDefaultsDictForDirsAndFiles];
	BOOL didSetAValueForFonts = [self registerMissingUserDefaultsInDict:self.defaultUserDefaultsDictForFonts];
	BOOL didSetAValueForColors = [self registerMissingUserDefaultsInDict:self.defaultUserDefaultsDictForColors];
	BOOL didSetAValueForLayout = [self registerMissingUserDefaultsInDict:self.defaultUserDefaultsDictForLayout];
	BOOL didSetAValueForIOSafetyModes = [self registerMissingUserDefaultsInDict:self.defaultUserDefaultsDictForIOSafetyModes];
	BOOL didSetAValueForMisc = [self registerMissingUserDefaultsInDict:self.defaultUserDefaultsDictForMisc];
	BOOL didSetAValueForDevMode = [self registerMissingUserDefaultsInDict:self.defaultUserDefaultsDictForDevMode];

	BOOL didSetAValue = (didSetAValueForInternal || didSetAValueForDirsAndFiles ||
						 didSetAValueForFonts || didSetAValueForColors || didSetAValueForLayout ||
						 didSetAValueForIOSafetyModes || didSetAValueForMisc ||
						 didSetAValueForDevMode);
	
	if (didSetAValue) {
		[self updateWriteCounter];
	}
}

- (void)resetDefaultDirsAndFiles
{
	self.skipPersistOnKVOEvent = YES;
	[self setDefaultDirsAndFilesPrefs];
	self.skipPersistOnKVOEvent = NO;
	[self persist];
}

- (void)resetDefaultFonts
{
	self.skipPersistOnKVOEvent = YES;
	[self setDefaultFontsPrefs];
	self.skipPersistOnKVOEvent = NO;
	[self persist];
}

- (void)resetDefaultColors
{
	self.skipPersistOnKVOEvent = YES;
	[self setDefaultColorsPrefs];
	self.skipPersistOnKVOEvent = NO;
	[self persist];
}

- (void)resetDefaultLayout
{
	self.skipPersistOnKVOEvent = YES;
	[self setDefaultLayoutPrefs];
	self.skipPersistOnKVOEvent = NO;
	[self persist];
}

- (void)resetDefaultIOSafetyModes
{
	self.skipPersistOnKVOEvent = YES;
	[self setDefaultIOSafetyPrefs];
	self.skipPersistOnKVOEvent = NO;
	[self persist];
}

- (void)resetDefaultMisc
{
	self.skipPersistOnKVOEvent = YES;
	[self setDefaultMiscPrefs];
	self.skipPersistOnKVOEvent = NO;
	[self persist];
}

- (void)resetDefaultDevMode
{
	self.skipPersistOnKVOEvent = YES;
	[self setDefaultDevModePrefs];
	self.skipPersistOnKVOEvent = NO;
	[self persist];
}

- (void)cleanupUnused
{
	NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
	
	BOOL didSetAValue = NO;
	
	for (NSString *key in singletonInstance.unusedUserDefaultsKeys) {
		[userDefaults removeObjectForKey:key];
		didSetAValue = YES;
	}
	
	if (didSetAValue) {
		[self updateWriteCounter];
	}
}

// NB! for dev use only
- (void)cleanupAll
{
	XT_DEF_SELNAME;

	NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
	NSDictionary *allEntries = [userDefaults dictionaryRepresentation];
	NSArray *allKeys = [allEntries allKeys];
	
	for (NSString *key in allKeys) {
		if ([key hasPrefix:@"XTads"]) {
			[userDefaults removeObjectForKey:key];
			XT_WARN_1(@"removed \"%@\"", key);
		}
	}
	
	[self updateWriteCounter];
}

- (void)restoreFromPersisted
{
	XT_DEF_SELNAME;
	
	// Note: this method assumes KV observing is not on
	
	NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
	
	//TODO break this func into smaller pieces
	
	//--- games dir. ---
	
	NSString *gamesDirModeString = [userDefaults stringForKey:KEY_GAMES_DIRECTORY_MODE];
	self.gamesDirectoryMode = [self directoryModeFromString:gamesDirModeString];
	XT_TRACE_2(LOG_FMT_OBJ_EQ_OBJ, KEY_GAMES_DIRECTORY_MODE, gamesDirModeString);
	
	NSString *gamesDirectoryWhenSpecific = gamesDirectoryWhenSpecific = [userDefaults stringForKey:KEY_GAMES_DIRECTORY_WHEN_SPECIFIC];
	if (gamesDirectoryWhenSpecific != nil) {
		self.gamesDirectoryWhenSpecific = [NSURL URLWithString:gamesDirectoryWhenSpecific];
	} else {
		self.gamesDirectoryWhenSpecific = nil;
	}
	XT_TRACE_2(LOG_FMT_OBJ_EQ_OBJ, KEY_GAMES_DIRECTORY_WHEN_SPECIFIC, gamesDirectoryWhenSpecific);
	
	NSString *gamesDirectoryLastUsed = gamesDirectoryLastUsed = [userDefaults stringForKey:KEY_GAMES_DIRECTORY_LAST_USED];
	if (gamesDirectoryLastUsed != nil) {
		self.gamesDirectoryLastUsed = [NSURL URLWithString:gamesDirectoryLastUsed];
	} else {
		self.gamesDirectoryLastUsed = nil;
	}
	XT_TRACE_2(LOG_FMT_OBJ_EQ_OBJ, KEY_GAMES_DIRECTORY_LAST_USED, gamesDirectoryLastUsed);
	
	//--- saves dir. ---
	
	NSString *savesDirModeString = [userDefaults stringForKey:KEY_SAVES_DIRECTORY_MODE];
	self.savesDirectoryMode = [self directoryModeFromString:savesDirModeString];
	XT_TRACE_2(LOG_FMT_OBJ_EQ_OBJ, KEY_SAVES_DIRECTORY_MODE, savesDirModeString);

	NSString *savesDirectoryWhenSpecific = savesDirectoryWhenSpecific = [userDefaults stringForKey:KEY_SAVES_DIRECTORY_WHEN_SPECIFIC];
	if (savesDirectoryWhenSpecific != nil) {
		self.savesDirectoryWhenSpecific = [NSURL URLWithString:savesDirectoryWhenSpecific];
	} else {
		self.savesDirectoryWhenSpecific = nil;
	}
	XT_TRACE_2(LOG_FMT_OBJ_EQ_OBJ, KEY_SAVES_DIRECTORY_WHEN_SPECIFIC, savesDirectoryWhenSpecific);
	
	NSString *savesDirectoryLastUsed = [userDefaults stringForKey:KEY_SAVES_DIRECTORY_LAST_USED];
	if (savesDirectoryLastUsed != nil) {
		self.savesDirectoryLastUsed = [NSURL URLWithString:savesDirectoryLastUsed];
	} else {
		self.savesDirectoryLastUsed = nil;
	}
	XT_TRACE_2(LOG_FMT_OBJ_EQ_OBJ, KEY_SAVES_DIRECTORY_LAST_USED, savesDirectoryLastUsed);

	NSString *savesFileNameMode = [userDefaults stringForKey:KEY_SAVES_FILE_NAME_MODE];
	self.savesFileNameMode = [self fileNameModeFromString:savesFileNameMode];
	XT_TRACE_2(LOG_FMT_OBJ_EQ_OBJ, KEY_SAVES_FILE_NAME_MODE, savesFileNameMode);
	
	//---  transcripts dir. ---
	
	NSString *transcriptsDirModeString = [userDefaults stringForKey:KEY_TRANSCRIPTS_DIRECTORY_MODE];
	self.transcriptsDirectoryMode = [self directoryModeFromString:transcriptsDirModeString];
	XT_TRACE_2(LOG_FMT_OBJ_EQ_OBJ, KEY_TRANSCRIPTS_DIRECTORY_MODE, transcriptsDirModeString);

	NSString *transcriptsDirectoryWhenSpecific = [userDefaults stringForKey:KEY_TRANSCRIPTS_DIRECTORY_WHEN_SPECIFIC];
	if (transcriptsDirectoryWhenSpecific != nil) {
		self.transcriptsDirectoryWhenSpecific = [NSURL URLWithString:transcriptsDirectoryWhenSpecific];
	} else {
		self.transcriptsDirectoryWhenSpecific = nil;
	}
	XT_TRACE_2(LOG_FMT_OBJ_EQ_OBJ, KEY_TRANSCRIPTS_DIRECTORY_WHEN_SPECIFIC, transcriptsDirectoryWhenSpecific);

	NSString *transcriptsDirectoryLastUsed = [userDefaults stringForKey:KEY_TRANSCRIPTS_DIRECTORY_LAST_USED];
	if (transcriptsDirectoryLastUsed != nil) {
		self.transcriptsDirectoryLastUsed = [NSURL URLWithString:transcriptsDirectoryLastUsed];
	} else {
		self.transcriptsDirectoryLastUsed = nil;
	}
	XT_TRACE_2(LOG_FMT_OBJ_EQ_OBJ, KEY_TRANSCRIPTS_DIRECTORY_LAST_USED, transcriptsDirectoryLastUsed);
	
	NSString *transcriptsFileNameMode = [userDefaults stringForKey:KEY_TRANSCRIPTS_FILE_NAME_MODE];
	self.transcriptsFileNameMode = [self fileNameModeFromString:transcriptsFileNameMode];
	XT_TRACE_2(LOG_FMT_OBJ_EQ_OBJ, KEY_TRANSCRIPTS_FILE_NAME_MODE, transcriptsFileNameMode);

	//---  command scripts dir. ---
	
	NSString *commandScriptsDirModeString = [userDefaults stringForKey:KEY_COMMANDSCRIPTS_DIRECTORY_MODE];
	self.commandScriptsDirectoryMode = [self directoryModeFromString:commandScriptsDirModeString];
	XT_TRACE_2(LOG_FMT_OBJ_EQ_OBJ, KEY_COMMANDSCRIPTS_DIRECTORY_MODE, commandScriptsDirModeString);

	NSString *commandScriptsDirectoryWhenSpecific = [userDefaults stringForKey:KEY_COMMANDSCRIPTS_DIRECTORY_WHEN_SPECIFIC];
	if (commandScriptsDirectoryWhenSpecific != nil) {
		self.commandScriptsDirectoryWhenSpecific = [NSURL URLWithString:commandScriptsDirectoryWhenSpecific];
	} else {
		self.commandScriptsDirectoryWhenSpecific = nil;
	}
	XT_TRACE_2(LOG_FMT_OBJ_EQ_OBJ, KEY_COMMANDSCRIPTS_DIRECTORY_WHEN_SPECIFIC, commandScriptsDirectoryWhenSpecific);
	
	NSString *commandScriptsDirectoryLastUsed = [userDefaults stringForKey:KEY_COMMANDSCRIPTS_DIRECTORY_LAST_USED];
	if (commandScriptsDirectoryLastUsed != nil) {
		self.commandScriptsDirectoryLastUsed = [NSURL URLWithString:commandScriptsDirectoryLastUsed];
	} else {
		self.commandScriptsDirectoryLastUsed = nil;
	}
	XT_TRACE_2(LOG_FMT_OBJ_EQ_OBJ, KEY_COMMANDSCRIPTS_DIRECTORY_LAST_USED, commandScriptsDirectoryLastUsed);

	NSString *commandScriptsFileNameMode = [userDefaults stringForKey:KEY_COMMANDSCRIPTS_FILE_NAME_MODE];
	self.commandScriptsFileNameMode = [self fileNameModeFromString:commandScriptsFileNameMode];
	XT_TRACE_2(LOG_FMT_OBJ_EQ_OBJ, KEY_COMMANDSCRIPTS_FILE_NAME_MODE, commandScriptsFileNameMode);
	
	//--- default font ---
	
	self.defaultFontName = [self restoreFontNameWithKey:KEY_DEFAULT_FONT_NAME
										   defaultValue:DEFAULT_DEFAULT_FONT_NAME];
	
	self.defaultFontSize = [self restoreFontSizeWithKey:KEY_DEFAULT_FONT_SIZE
											defaultSize:DEFAULT_DEFAULT_FONT_SIZE];
	
	//--- fixed width font ---
	
	self.fixedWidthFontName = [self restoreFontNameWithKey:KEY_FIXEDWIDTH_FONT_NAME
											  defaultValue:DEFAULT_FIXEDWIDTH_FONT_NAME];

	self.fixedWidthFontSize = [self restoreFontSizeWithKey:KEY_FIXEDWIDTH_FONT_SIZE
											   defaultSize:DEFAULT_FIXEDWIDTH_FONT_SIZE];
	
	//--- serifed font ---
	
	self.serifedFontName = [self restoreFontNameWithKey:KEY_SERIFED_FONT_NAME
										   defaultValue:DEFAULT_SERIFED_FONT_NAME];
	
	self.serifedFontSize = [self restoreFontSizeWithKey:KEY_SERIFED_FONT_SIZE
											defaultSize:DEFAULT_SERIFED_FONT_SIZE];
	
	//--- sans serif font ---
	
	self.sansSerifFontName = [self restoreFontNameWithKey:KEY_SANSSERIF_FONT_NAME
											 defaultValue:DEFAULT_SANSSERIF_FONT_NAME];
	
	self.sansSerifFontSize = [self restoreFontSizeWithKey:KEY_SANSSERIF_FONT_SIZE
											  defaultSize:DEFAULT_SANSSERIF_FONT_SIZE];
	
	//--- script font ---
	
	self.scriptFontName = [self restoreFontNameWithKey:KEY_SCRIPT_FONT_NAME
										  defaultValue:DEFAULT_SCRIPT_FONT_NAME];
	
	self.sansSerifFontSize = [self restoreFontSizeWithKey:KEY_SCRIPT_FONT_SIZE
											  defaultSize:DEFAULT_SCRIPT_FONT_SIZE];
	
	//--- typewriter font ---
	
	self.typewriterFontName = [self restoreFontNameWithKey:KEY_TYPEWRITER_FONT_NAME
											  defaultValue:DEFAULT_TYPEWRITER_FONT_NAME];
	
	self.typewriterFontSize = [self restoreFontSizeWithKey:KEY_TYPEWRITER_FONT_SIZE
											   defaultSize:DEFAULT_TYPEWRITER_FONT_SIZE];
	
	//--- input font ---
	
	self.inputFontIsSameAsDefaultFont = [self restoreBooleanWithKey:KEY_INPUT_FONT_IS_SAME_AS_DEFAULT_FONT
													   defaultValue:DEFAULT_INPUT_FONT_IS_SAME_AS_DEFAULT_FONT];
	
	self.inputFontName = [self restoreFontNameWithKey:KEY_INPUT_FONT_NAME
										 defaultValue:DEFAULT_INPUT_FONT_NAME];
	
	self.inputFontSize = [self restoreFontSizeWithKey:KEY_INPUT_FONT_SIZE
										  defaultSize:DEFAULT_INPUT_FONT_SIZE];
	
	self.inputFontUsedEvenIfNotRequestedByGame = [self restoreBooleanWithKey:KEY_INPUT_FONT_USED_EVEN_IF_NOT_REQUESTED_BY_GAME
																defaultValue:DEFAULT_INPUT_FONT_USED_EVEN_IF_NOT_REQUESTED_BY_GAME];

	//--- status line font ---
	
	self.statusLineFontIsSameAsDefaultFont = [self restoreBooleanWithKey:KEY_STATUSLINE_FONT_IS_SAME_AS_DEFAULT_FONT
															defaultValue:DEFAULT_STATUSLINE_FONT_IS_SAME_AS_DEFAULT_FONT];
	
	self.statusLineFontName = [self restoreFontNameWithKey:KEY_STATUSLINE_FONT_NAME
											  defaultValue:DEFAULT_STATUSLINE_FONT_NAME];
	
	self.statusLineFontSize = [self restoreFontSizeWithKey:KEY_STATUSLINE_FONT_SIZE
											   defaultSize:DEFAULT_STATUSLINE_FONT_SIZE];

	//--- other font stuff ---
	
	self.allowGamesToSetFonts = [self restoreBooleanWithKey:KEY_ALLOW_GAMES_TO_SET_FONTS
											   defaultValue:DEFAULT_ALLOW_GAMES_TO_SET_FONTS];
	
	self.minAllowedFontSize = [self restoreIntegerWithKey:KEY_MIN_ALLOWED_FONT_SIZE
											 defaultValue:DEFAULT_MIN_ALLOWED_FONT_SIZE];
	
	self.maxAllowedFontSize = [self restoreIntegerWithKey:KEY_MAX_ALLOWED_FONT_SIZE
											 defaultValue:DEFAULT_MAX_ALLOWED_FONT_SIZE];
	
	//--- status line colors ---
	
	self.statusLineTextColor = [self restoreColorWithKey:KEY_STATUSLINE_TEXTCOLOR
											defaultValue:DEFAULT_STATUSLINE_TEXTCOLOR];
	
	self.statusLineBackgroundColor = [self restoreColorWithKey:KEY_STATUSLINE_BACKGROUNDCOLOR
												  defaultValue:DEFAULT_STATUSLINE_BACKGROUNDCOLOR];
	
	//--- output area colors ---
	
	self.outputAreaTextColor = [self restoreColorWithKey:KEY_OUTPUTAREA_TEXTCOLOR
											defaultValue:DEFAULT_OUTPUTAREA_TEXTCOLOR];
	
	self.outputAreaBackgroundColor = [self restoreColorWithKey:KEY_OUTPUTAREA_BACKGROUNDCOLOR
												  defaultValue:DEFAULT_OUTPUTAREA_BACKGROUNDCOLOR];
	
	//--- input colors ---

	self.inputTextColor = [self restoreColorWithKey:KEY_INPUT_TEXTCOLOR
									   defaultValue:DEFAULT_INPUT_TEXTCOLOR];

	//--- link color ++ ---

	self.linksTextColor = [self restoreColorWithKey:KEY_LINKS_TEXTCOLOR
									   defaultValue:DEFAULT_LINKS_TEXTCOLOR];
	
	self.linksUnderline = [self restoreBooleanWithKey:KEY_LINKS_UNDERLINE
										 defaultValue:DEFAULT_LINKS_UNDERLINE];

	self.linksShowToolTips = [self restoreBooleanWithKey:KEY_LINKS_SHOW_TOOLTIPS
											defaultValue:DEFAULT_LINKS_SHOW_TOOLTIPS];
	
	//--- Other colors stuff ---
	
	self.allowGamesToSetFontColors = [self restoreBooleanWithKey:KEY_ALLOW_GAMES_TO_SET_FONT_COLORS
													defaultValue:DEFAULT_ALLOW_GAMES_TO_SET_FONT_COLORS];
	
	self.allowGamesToSetBackgroundColors = [self restoreBooleanWithKey:KEY_ALLOW_GAMES_TO_SET_BACKGROUND_COLORS
														  defaultValue:DEFAULT_ALLOW_GAMES_TO_SET_BACKGROUND_COLORS];

	//--- layout ---
	
	NSString *gameWindowStartModeString = [userDefaults stringForKey:KEY_GAME_WINDOW_START_MODE];
	self.gameWindowStartMode = [self gameWindowStartModeFromString:gameWindowStartModeString];
	XT_TRACE_2(LOG_FMT_OBJ_EQ_OBJ, KEY_GAME_WINDOW_START_MODE, gameWindowStartModeString);
	
	//--- i/o safety modes ---
	
	//TODO use restore...WithKey for these:
	
	NSString *ioSafetyModeReadString = [userDefaults stringForKey:KEY_IO_SAFETY_MODE_READ];
	self.readSafetyMode = [self ioSafetyModeFromString:ioSafetyModeReadString];
	XT_TRACE_2(LOG_FMT_OBJ_EQ_OBJ, KEY_IO_SAFETY_MODE_READ, ioSafetyModeReadString);
	
	NSString *ioSafetyModeWriteString = [userDefaults stringForKey:KEY_IO_SAFETY_MODE_WRITE];
	self.writeSafetyMode = [self ioSafetyModeFromString:ioSafetyModeWriteString];
	XT_TRACE_2(LOG_FMT_OBJ_EQ_OBJ, KEY_IO_SAFETY_MODE_WRITE, ioSafetyModeWriteString);
	
	//--- misc. ---
	
	self.askForGameFileOnTerpStart = [self restoreBooleanWithKey:KEY_ASK_FOR_GAME_FILE_ON_TERP_START
													defaultValue:DEFAULT_ASK_FOR_GAME_FILE_ON_TERP_START];
	
	self.printTadsBannerOnGameStart = [self restoreBooleanWithKey:KEY_PRINT_TADS_BANNER_ON_GAME_START
													 defaultValue:DEFAULT_PRINT_TADS_BANNER_ON_GAME_START];
	
	self.askForConfirmationOnGameRestart = [self restoreBooleanWithKey:KEY_ASK_FOR_CONFIRMATION_ON_GAME_RESTART
														  defaultValue:DEFAULT_ASK_FOR_CONFIRMATION_ON_GAME_RESTART];

	self.askForConfirmationOnGameQuit = [self restoreBooleanWithKey:KEY_ASK_FOR_CONFIRMATION_ON_GAME_QUIT
													   defaultValue:DEFAULT_ASK_FOR_CONFIRMATION_ON_GAME_QUIT];
	
	self.askForConfirmationOnGameOpenIfGameRunning = [self restoreBooleanWithKey:KEY_ASK_FOR_CONFIRMATION_ON_GAME_OPEN_IF_GAME_RUNNING
																	defaultValue:DEFAULT_ASK_FOR_CONFIRMATION_ON_GAME_OPEN_IF_GAME_RUNNING];

	self.enableDevelopmentModeFeatures = [self restoreBooleanWithKey:KEY_ENABLE_DEVELOPMENT_FEATURES
														defaultValue:DEFAULT_ENABLE_DEVELOPMENT_FEATURES];

	self.limitScrollbackBufferSize = [self restoreIntegerWithKey:KEY_LIMIT_SCROLLBACK_BUFFER_SIZE
													  defaultValue:DEFAULT_LIMIT_SCROLLBACK_BUFFER_SIZE];

	self.scrollbackBufferSizeInKBs = [self restoreIntegerWithKey:KEY_SCROLLBACK_BUFFER_SIZE_IN_KB
													defaultValue:DEFAULT_SCROLLBACK_BUFFER_SIZE_IN_KB];
	
	self.tads2Encoding = [self restoreIntegerWithKey:KEY_TADS2_ENCODING
										defaultValue:DEFAULT_TADS2_ENCODING];

	self.tads2EncodingOverride = [self restoreBooleanWithKey:KEY_TADS2_ENCODING_OVERRIDE
												defaultValue:DEFAULT_TADS2_ENCODING_OVERRIDE];

	//--- dev. mode ---
	
	self.printBrokenHtmlMarkup = [self restoreBooleanWithKey:KEY_PRINT_BROKEN_HTML_MARKUP
												defaultValue:DEFAULT_PRINT_BROKEN_HTML_MARKUP];
	
	self.showParserMode = [self restoreBooleanWithKey:KEY_SHOW_PARSER_MODE
										 defaultValue:DEFAULT_SHOW_PARSER_MODE];

	self.spellCheckGameText = [self restoreBooleanWithKey:KEY_SPELL_CHECK_GAME_TEXT
											 defaultValue:DEFAULT_SPELL_CHECK_GAME_TEXT];
	
	self.grammarCheckGameText = [self restoreBooleanWithKey:KEY_GRAMMAR_CHECK_GAME_TEXT
											   defaultValue:DEFAULT_GRAMMAR_CHECK_GAME_TEXT];
	
	[self refreshDerivedNonPersistedPrefs];
}

- (void)persist
{
	XT_TRACE_ENTRY;
	
	// avoid KVO loop:
	if (self.skipPersistOnKVOEvent) {
		return;
	}
	self.skipPersistOnKVOEvent = YES;

	XT_TRACE_0(@"continue...");
	
	NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
	
	//---  games dir. ---
	
	NSString *gamesDirModeString = [self stringFromDirectoryMode:self.gamesDirectoryMode];
	[userDefaults setObject:gamesDirModeString forKey:KEY_GAMES_DIRECTORY_MODE];

	NSString *gamesDirectoryWhenSpecific = [self stringFromUrl:self.gamesDirectoryWhenSpecific];
	[userDefaults setObject:gamesDirectoryWhenSpecific forKey:KEY_GAMES_DIRECTORY_WHEN_SPECIFIC];
	
	NSString *gamesDirectoryLastUsed = [self stringFromUrl:self.gamesDirectoryLastUsed];
	[userDefaults setObject:gamesDirectoryLastUsed forKey:KEY_GAMES_DIRECTORY_LAST_USED];
	
	//--- saves dir. ---
	
	NSString *savesDirModeString = [self stringFromDirectoryMode:self.savesDirectoryMode];
	[userDefaults setObject:savesDirModeString forKey:KEY_SAVES_DIRECTORY_MODE];
	
	NSString *savesDirWhenSpecString = [self stringFromUrl:self.savesDirectoryWhenSpecific];
	[userDefaults setObject:savesDirWhenSpecString forKey:KEY_SAVES_DIRECTORY_WHEN_SPECIFIC];
	
	NSString *savesDirLastUsedString = [self stringFromUrl:self.savesDirectoryLastUsed];
	[userDefaults setObject:savesDirLastUsedString forKey:KEY_SAVES_DIRECTORY_LAST_USED];
	
	NSString *savesFileNameModeString = [self stringFromFileNameMode:self.savesFileNameMode];
	[userDefaults setObject:savesFileNameModeString forKey:KEY_SAVES_FILE_NAME_MODE];
	
	//--- transcripts dir. ---
	
	NSString *transDirModeString = [self stringFromDirectoryMode:self.transcriptsDirectoryMode];
	[userDefaults setObject:transDirModeString forKey:KEY_TRANSCRIPTS_DIRECTORY_MODE];
	
	NSString *transDirWhenSpecString = [self stringFromUrl:self.transcriptsDirectoryWhenSpecific];
	[userDefaults setObject:transDirWhenSpecString forKey:KEY_TRANSCRIPTS_DIRECTORY_WHEN_SPECIFIC];
	
	NSString *transDirLastUsedString = [self stringFromUrl:self.transcriptsDirectoryLastUsed];
	[userDefaults setObject:transDirLastUsedString forKey:KEY_TRANSCRIPTS_DIRECTORY_LAST_USED];
	
	NSString *transFileNameModeString = [self stringFromFileNameMode:self.transcriptsFileNameMode];
	[userDefaults setObject:transFileNameModeString forKey:KEY_TRANSCRIPTS_FILE_NAME_MODE];

	//--- command scripts dir. ---
	
	NSString *cmdDirModeString = [self stringFromDirectoryMode:self.commandScriptsDirectoryMode];
	[userDefaults setObject:cmdDirModeString forKey:KEY_COMMANDSCRIPTS_DIRECTORY_MODE];

	NSString *cmdDirWhenSpecString = [self stringFromUrl:self.commandScriptsDirectoryWhenSpecific];
	[userDefaults setObject:cmdDirWhenSpecString forKey:KEY_COMMANDSCRIPTS_DIRECTORY_WHEN_SPECIFIC];

	NSString *cmdDirLastUsedString = [self stringFromUrl:self.commandScriptsDirectoryLastUsed];
	[userDefaults setObject:cmdDirLastUsedString forKey:KEY_COMMANDSCRIPTS_DIRECTORY_LAST_USED];

	NSString *cmdFileNameModeString = [self stringFromFileNameMode:self.commandScriptsFileNameMode];
	[userDefaults setObject:cmdFileNameModeString forKey:KEY_COMMANDSCRIPTS_FILE_NAME_MODE];
	
	//--- default font ---
	
	[userDefaults setObject:self.defaultFontName forKey:KEY_DEFAULT_FONT_NAME];
	[userDefaults setFloat:self.defaultFontSize.floatValue forKey:KEY_DEFAULT_FONT_SIZE];
	
	//--- fixed width font ---
	
	[userDefaults setObject:self.fixedWidthFontName forKey:KEY_FIXEDWIDTH_FONT_NAME];
	[userDefaults setFloat:self.fixedWidthFontSize.floatValue forKey:KEY_FIXEDWIDTH_FONT_SIZE];
	
	//--- serifed font ---
	
	[userDefaults setObject:self.serifedFontName forKey:KEY_SERIFED_FONT_NAME];
	[userDefaults setFloat:self.serifedFontSize.floatValue forKey:KEY_SERIFED_FONT_SIZE];
	
	//--- sans serif font ---
	
	[userDefaults setObject:self.sansSerifFontName forKey:KEY_SANSSERIF_FONT_NAME];
	[userDefaults setFloat:self.sansSerifFontSize.floatValue forKey:KEY_SANSSERIF_FONT_SIZE];
	
	//--- script font ---
	
	[userDefaults setObject:self.scriptFontName forKey:KEY_SCRIPT_FONT_NAME];
	[userDefaults setFloat:self.scriptFontSize.floatValue forKey:KEY_SCRIPT_FONT_SIZE];
	
	//--- typewriter font ---
	
	[userDefaults setObject:self.typewriterFontName forKey:KEY_TYPEWRITER_FONT_NAME];
	[userDefaults setFloat:self.typewriterFontSize.floatValue forKey:KEY_TYPEWRITER_FONT_SIZE];
	
	//--- input font ---

	[userDefaults setObject:self.inputFontIsSameAsDefaultFont forKey:KEY_INPUT_FONT_IS_SAME_AS_DEFAULT_FONT];
	[userDefaults setObject:self.inputFontName forKey:KEY_INPUT_FONT_NAME];
	[userDefaults setFloat:self.inputFontSize.floatValue forKey:KEY_INPUT_FONT_SIZE];
	[userDefaults setObject:self.inputFontUsedEvenIfNotRequestedByGame forKey:KEY_INPUT_FONT_USED_EVEN_IF_NOT_REQUESTED_BY_GAME];

	//--- status line font ---
	
	[userDefaults setObject:self.statusLineFontIsSameAsDefaultFont forKey:KEY_STATUSLINE_FONT_IS_SAME_AS_DEFAULT_FONT];
	[userDefaults setObject:self.statusLineFontName forKey:KEY_STATUSLINE_FONT_NAME];
	[userDefaults setFloat:self.statusLineFontSize.floatValue forKey:KEY_STATUSLINE_FONT_SIZE];

	//--- other font stuff---
	
	[userDefaults setObject:self.allowGamesToSetFonts forKey:KEY_ALLOW_GAMES_TO_SET_FONTS];
	
	if (self.minAllowedFontSize == nil) {
		self.minAllowedFontSize = DEFAULT_MIN_ALLOWED_FONT_SIZE;
	}
	[userDefaults setObject:self.minAllowedFontSize forKey:KEY_MIN_ALLOWED_FONT_SIZE];
	
	if (self.maxAllowedFontSize == nil) {
		self.maxAllowedFontSize = DEFAULT_MAX_ALLOWED_FONT_SIZE;
	}
	[userDefaults setObject:self.maxAllowedFontSize forKey:KEY_MAX_ALLOWED_FONT_SIZE];
	
	//--- status line colors ---
	
	NSData *statusLineTextColorData = [self dataFromColor:self.statusLineTextColor];
	[userDefaults setObject:statusLineTextColorData forKey:KEY_STATUSLINE_TEXTCOLOR];
	
	NSData *statusLineBackgroundColorDate = [self dataFromColor:self.statusLineBackgroundColor];
	[userDefaults setObject:statusLineBackgroundColorDate forKey:KEY_STATUSLINE_BACKGROUNDCOLOR];

	//--- output area colors ---

	NSData *outputAreaTextColorData = [self dataFromColor:self.outputAreaTextColor];
	[userDefaults setObject:outputAreaTextColorData forKey:KEY_OUTPUTAREA_TEXTCOLOR];
	
	NSData *outputAreaBackgroundColorData = [self dataFromColor:self.outputAreaBackgroundColor];
	[userDefaults setObject:outputAreaBackgroundColorData forKey:KEY_OUTPUTAREA_BACKGROUNDCOLOR];
	
	//--- input colors ---

	NSData *inputTextColorData = [self dataFromColor:self.inputTextColor];
	[userDefaults setObject:inputTextColorData forKey:KEY_INPUT_TEXTCOLOR];
	
	//--- link colors ++ ---

	NSData *linksTextColorData = [self dataFromColor:self.linksTextColor];
	[userDefaults setObject:linksTextColorData forKey:KEY_LINKS_TEXTCOLOR];

	[userDefaults setObject:self.linksUnderline forKey:KEY_LINKS_UNDERLINE];
	
	[userDefaults setObject:self.linksShowToolTips forKey:KEY_LINKS_SHOW_TOOLTIPS];
	
	//--- Other colors stuff ---
	
	[userDefaults setObject:self.allowGamesToSetFontColors forKey:KEY_ALLOW_GAMES_TO_SET_FONT_COLORS];

	[userDefaults setObject:self.allowGamesToSetBackgroundColors forKey:KEY_ALLOW_GAMES_TO_SET_BACKGROUND_COLORS];
	
	//--- layout ---
	
	NSString *gameWindowStartModeString = [self stringFromGameWindowStartMode:self.gameWindowStartMode];
	[userDefaults setObject:gameWindowStartModeString forKey:KEY_GAME_WINDOW_START_MODE];
	
	//--- i/o safety ---

	NSString *readIOSafetyModeString = [self stringFromIOSafetyMode:self.readSafetyMode];
	[userDefaults setObject:readIOSafetyModeString forKey:KEY_IO_SAFETY_MODE_READ];

	NSString *writeIOSafetyModeString = [self stringFromIOSafetyMode:self.writeSafetyMode];
	[userDefaults setObject:writeIOSafetyModeString forKey:KEY_IO_SAFETY_MODE_WRITE];
	
	//--- misc. ---
	
	[userDefaults setObject:self.askForGameFileOnTerpStart forKey:KEY_ASK_FOR_GAME_FILE_ON_TERP_START];
	[userDefaults setObject:self.printTadsBannerOnGameStart forKey:KEY_PRINT_TADS_BANNER_ON_GAME_START];
	[userDefaults setObject:self.askForConfirmationOnGameRestart forKey:KEY_ASK_FOR_CONFIRMATION_ON_GAME_RESTART];
	[userDefaults setObject:self.askForConfirmationOnGameQuit forKey:KEY_ASK_FOR_CONFIRMATION_ON_GAME_QUIT];
	[userDefaults setObject:self.askForConfirmationOnGameOpenIfGameRunning forKey:KEY_ASK_FOR_CONFIRMATION_ON_GAME_OPEN_IF_GAME_RUNNING];
	[userDefaults setObject:self.enableDevelopmentModeFeatures forKey:KEY_ENABLE_DEVELOPMENT_FEATURES];
	[userDefaults setObject:self.limitScrollbackBufferSize forKey:KEY_LIMIT_SCROLLBACK_BUFFER_SIZE];
	[userDefaults setObject:self.scrollbackBufferSizeInKBs forKey:KEY_SCROLLBACK_BUFFER_SIZE_IN_KB];
	[userDefaults setObject:self.tads2Encoding forKey:KEY_TADS2_ENCODING];
	[userDefaults setObject:self.tads2EncodingOverride forKey:KEY_TADS2_ENCODING_OVERRIDE];
	
	//--- dev. mode. ---

	[userDefaults setObject:self.printBrokenHtmlMarkup forKey:KEY_PRINT_BROKEN_HTML_MARKUP];
	[userDefaults setObject:self.showParserMode forKey:KEY_SHOW_PARSER_MODE];
	[userDefaults setObject:self.spellCheckGameText forKey:KEY_SPELL_CHECK_GAME_TEXT];
	[userDefaults setObject:self.grammarCheckGameText forKey:KEY_GRAMMAR_CHECK_GAME_TEXT];
	
	[self updateWriteCounter];
	
	[self refreshDerivedNonPersistedPrefs];
	
	self.skipPersistOnKVOEvent = NO;
}

- (void)updateDefaultFontWithName:(NSString *)name size:(CGFloat)zize
{
	self.defaultFontName = name;
	self.defaultFontSize = [NSNumber numberWithFloat:zize];
	[self updateFontDescriptions];
}

- (void)updateFixedWidthFontWithName:(NSString *)name size:(CGFloat)size
{
	self.fixedWidthFontName = name;
	self.fixedWidthFontSize = [NSNumber numberWithFloat:size];
	[self updateFontDescriptions];
}

- (void)updateSerifedFontWithName:(NSString *)name size:(CGFloat)size
{
	self.serifedFontName = name;
	self.serifedFontSize = [NSNumber numberWithFloat:size];
	[self updateFontDescriptions];
}

- (void)updateSansSerifFontWithName:(NSString *)name size:(CGFloat)size
{
	self.sansSerifFontName = name;
	self.sansSerifFontSize = [NSNumber numberWithFloat:size];
	[self updateFontDescriptions];
}

- (void)updateScriptFontWithName:(NSString *)name size:(CGFloat)size
{
	self.scriptFontName = name;
	self.scriptFontSize = [NSNumber numberWithFloat:size];
	[self updateFontDescriptions];
}

- (void)updateTypewriterFontWithName:(NSString *)name size:(CGFloat)size
{
	self.typewriterFontName = name;
	self.typewriterFontSize = [NSNumber numberWithFloat:size];
	[self updateFontDescriptions];
}

- (void)updateInputFontWithName:(NSString *)name size:(CGFloat)size
{
	self.inputFontName = name;
	self.inputFontSize = [NSNumber numberWithFloat:size];
	[self updateFontDescriptions];
}

- (void)updateStatusLineFontWithName:(NSString *)name size:(CGFloat)size
{
	self.statusLineFontName = name;
	self.statusLineFontSize = [NSNumber numberWithFloat:size];
	[self updateFontDescriptions];
}

//------ KV-observe self, so that changes can be written to app's user defaults etc. ------

- (void)startObservingChangesToAll:(NSObject *)observer
{
	XT_TRACE_ENTRY;
	XT_TRACE_2(@"%@ %@", self, observer);
	
	[self startObservingChangesToDirsAndFiles:observer];
	[self startObservingChangesToFonts:observer];
	[self startObservingChangesToColors:observer];
	[self startObservingChangesToLayout:observer];
	[self startObservingChangesToIOSafetyModes:observer];
	[self startObservingChangesToMisc:observer];
	[self startObservingChangesToDevMode:observer];

}

- (void)stopObservingChangesToAll:(NSObject *)observer
{
	XT_TRACE_ENTRY;
	XT_TRACE_2(@"%@ %@", self, observer);
	
	[self stopObservingChangesToDirsAndFiles:observer];
	[self stopObservingChangesToFonts:observer];
	[self stopObservingChangesToColors:observer];
	[self stopObservingChangesToLayout:observer];
	[self stopObservingChangesToIOSafetyModes:observer];
	[self stopObservingChangesToMisc:observer];
	[self stopObservingChangesToDevMode:observer];

}

//TODO inline these:

- (void)startObservingChangesToDirsAndFiles:(NSObject *)observer
{
	[self addObserver:observer forSelectors:selectorsForDirsAndFiles countSelectors:countSelectorsForDirsAndFiles];
}

- (void)stopObservingChangesToDirsAndFiles:(NSObject *)observer
{
	[self removeObserver:observer forSelectors:selectorsForDirsAndFiles countSelectors:countSelectorsForDirsAndFiles];
}

- (void)startObservingChangesToFonts:(NSObject *)observer
{
	[self addObserver:observer forSelectors:selectorsForFonts countSelectors:countSelectorsForFonts];
}

- (void)stopObservingChangesToFonts:(NSObject *)observer
{
	[self removeObserver:observer forSelectors:selectorsForFonts countSelectors:countSelectorsForFonts];
}

- (void)startObservingChangesToColors:(NSObject *)observer
{
	[self addObserver:observer forSelectors:selectorsForColors countSelectors:countSelectorsForColors];
}

- (void)stopObservingChangesToColors:(NSObject *)observer
{
	[self removeObserver:observer forSelectors:selectorsForColors countSelectors:countSelectorsForColors];
}

- (void)startObservingChangesToLayout:(NSObject *)observer
{
	[self addObserver:observer forSelectors:selectorsForLayout countSelectors:countSelectorsForLayout];
}

- (void)stopObservingChangesToLayout:(NSObject *)observer
{
	[self removeObserver:observer forSelectors:selectorsForLayout countSelectors:countSelectorsForLayout];
}

- (void)startObservingChangesToIOSafetyModes:(NSObject *)observer
{
	[self addObserver:observer forSelectors:selectorsForIOSafetyModes countSelectors:countSelectorsForIOSafetyModes];
}

- (void)stopObservingChangesToIOSafetyModes:(NSObject *)observer
{
	[self removeObserver:observer forSelectors:selectorsForIOSafetyModes countSelectors:countSelectorsForIOSafetyModes];
}

- (void)startObservingChangesToMisc:(NSObject *)observer
{
	[self addObserver:observer forSelectors:selectorsForMisc countSelectors:countSelectorsForMisc];
}

- (void)stopObservingChangesToMisc:(NSObject *)observer
{
	[self removeObserver:observer forSelectors:selectorsForMisc countSelectors:countSelectorsForMisc];
}

- (void)startObservingChangesToDevMode:(NSObject *)observer
{
	[self addObserver:observer forSelectors:selectorsForDevMode countSelectors:countSelectorsForDevMode];
}

- (void)stopObservingChangesToDevMode:(NSObject *)observer
{
	[self removeObserver:observer forSelectors:selectorsForDevMode countSelectors:countSelectorsForDevMode];
}

- (void)addObserver:(NSObject *)observer
	   forSelectors:(SEL *)observedSelectors
	 countSelectors:(NSInteger)countSelectors
{
	for (NSInteger i = 0; i < countSelectors; i++) {
		SEL sel = observedSelectors[i];
		NSString *selStr = NSStringFromSelector(sel);
		[self addObserver:observer forKeyPath:selStr options:0 context:NULL];
	}
}

- (void)removeObserver:(NSObject *)observer
		  forSelectors:(SEL *)observedSelectors
		countSelectors:(NSInteger)countSelectors
{
	for (NSInteger i = 0; i < countSelectors; i++) {
		SEL sel = observedSelectors[i];
		NSString *selStr = NSStringFromSelector(sel);
		[self removeObserver:observer forKeyPath:selStr];
	}
}

//------ Internal support methods --------

//TODO mv more here

- (void)updateWriteCounter
{
	NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
	
	NSInteger writeCounter = [userDefaults integerForKey:KEY_USER_DEFAULTS_WRITE_COUNTER];
	writeCounter += 1;
	[userDefaults setInteger:writeCounter forKey:KEY_USER_DEFAULTS_WRITE_COUNTER];
}

- (void)setDefaultFontsPrefs
{
	// use self.* to trigger bindings for UI
	
	self.defaultFontName = DEFAULT_DEFAULT_FONT_NAME;
	self.defaultFontSize = DEFAULT_DEFAULT_FONT_SIZE;
	
	self.fixedWidthFontName = DEFAULT_FIXEDWIDTH_FONT_NAME;
	self.fixedWidthFontSize = DEFAULT_FIXEDWIDTH_FONT_SIZE;
	
	self.serifedFontName = DEFAULT_SERIFED_FONT_NAME;
	self.serifedFontSize = DEFAULT_SERIFED_FONT_SIZE;
	
	self.sansSerifFontName = DEFAULT_SANSSERIF_FONT_NAME;
	self.sansSerifFontSize = DEFAULT_SANSSERIF_FONT_SIZE;
	
	self.scriptFontName = DEFAULT_SCRIPT_FONT_NAME;
	self.scriptFontSize = DEFAULT_SCRIPT_FONT_SIZE;
	
	self.typewriterFontName = DEFAULT_TYPEWRITER_FONT_NAME;
	self.typewriterFontSize = DEFAULT_TYPEWRITER_FONT_SIZE;
	
	self.inputFontName = DEFAULT_INPUT_FONT_NAME;
	self.inputFontSize = DEFAULT_INPUT_FONT_SIZE;
	self.inputFontIsSameAsDefaultFont = DEFAULT_INPUT_FONT_IS_SAME_AS_DEFAULT_FONT;
	self.inputFontUsedEvenIfNotRequestedByGame = DEFAULT_INPUT_FONT_USED_EVEN_IF_NOT_REQUESTED_BY_GAME;

	self.statusLineFontName = DEFAULT_STATUSLINE_FONT_NAME;
	self.statusLineFontSize = DEFAULT_STATUSLINE_FONT_SIZE;
	self.statusLineFontIsSameAsDefaultFont = DEFAULT_STATUSLINE_FONT_IS_SAME_AS_DEFAULT_FONT;
	
	self.allowGamesToSetFonts = DEFAULT_ALLOW_GAMES_TO_SET_FONTS;
	self.minAllowedFontSize = DEFAULT_MIN_ALLOWED_FONT_SIZE;
	self.maxAllowedFontSize = DEFAULT_MAX_ALLOWED_FONT_SIZE;
}

- (void)setDefaultDirsAndFilesPrefs
{
	// use self.* to trigger bindings for UI
	
	//TODO use DEFAULT_...

	self.gamesDirectoryMode = XTPREFS_DIR_MODE_NONE;
	self.gamesDirectoryWhenSpecific = nil;
	self.gamesDirectoryLastUsed = nil;
	
	self.savesDirectoryMode = XTPREFS_DIR_MODE_NONE;
	self.savesDirectoryWhenSpecific = nil;
	self.savesDirectoryLastUsed = nil;
	self.savesFileNameMode = XTPREFS_FILENAME_MODE_GAMENAME_DATETIME;
	
	self.transcriptsDirectoryMode = XTPREFS_DIR_MODE_NONE;
	self.transcriptsDirectoryWhenSpecific = nil;
	self.transcriptsDirectoryLastUsed = nil;
	self.transcriptsFileNameMode = XTPREFS_FILENAME_MODE_GAMENAME_DATETIME;

	self.commandScriptsDirectoryMode = XTPREFS_DIR_MODE_NONE;
	self.commandScriptsDirectoryWhenSpecific = nil;
	self.commandScriptsDirectoryLastUsed = nil;
	self.commandScriptsFileNameMode = XTPREFS_FILENAME_MODE_GAMENAME_DATETIME;
}

- (void)setDefaultColorsPrefs
{
	// use self.* to trigger bindings for UI
	
	self.statusLineTextColor = DEFAULT_STATUSLINE_TEXTCOLOR;
	self.statusLineBackgroundColor = DEFAULT_STATUSLINE_BACKGROUNDCOLOR;

	self.outputAreaTextColor = DEFAULT_OUTPUTAREA_TEXTCOLOR;
	self.outputAreaBackgroundColor = DEFAULT_OUTPUTAREA_BACKGROUNDCOLOR;
	
	self.inputTextColor = DEFAULT_INPUT_TEXTCOLOR;

	self.linksTextColor = DEFAULT_LINKS_TEXTCOLOR;
	self.linksUnderline = DEFAULT_LINKS_UNDERLINE;
	self.linksShowToolTips = DEFAULT_LINKS_SHOW_TOOLTIPS;
	
	self.allowGamesToSetFontColors = DEFAULT_ALLOW_GAMES_TO_SET_FONT_COLORS;
	self.allowGamesToSetBackgroundColors = DEFAULT_ALLOW_GAMES_TO_SET_BACKGROUND_COLORS;
}

- (void)setDefaultLayoutPrefs
{
	// use self.* to trigger bindings for UI
	
	self.gameWindowStartMode = XTPREFS_GAME_WINDOW_SAME_AS_LAST;
}

- (void)setDefaultIOSafetyPrefs
{
	// use self.* to trigger bindings for UI
	
	//TODO use DEFAULT_...
	self.readSafetyMode = XTPREFS_IO_SAFETY_MODE_GAME_DIR_ONLY;
	self.writeSafetyMode = XTPREFS_IO_SAFETY_MODE_GAME_DIR_ONLY;
}

- (void)setDefaultMiscPrefs
{
	// use self.* to trigger bindings for UI
	
	self.askForGameFileOnTerpStart = DEFAULT_ASK_FOR_GAME_FILE_ON_TERP_START;
	self.printTadsBannerOnGameStart = DEFAULT_PRINT_TADS_BANNER_ON_GAME_START;
	self.askForConfirmationOnGameRestart = DEFAULT_ASK_FOR_CONFIRMATION_ON_GAME_RESTART;
	self.askForConfirmationOnGameQuit = DEFAULT_ASK_FOR_CONFIRMATION_ON_GAME_QUIT;
	self.askForConfirmationOnGameOpenIfGameRunning = DEFAULT_ASK_FOR_CONFIRMATION_ON_GAME_OPEN_IF_GAME_RUNNING;
	self.enableDevelopmentModeFeatures = DEFAULT_ENABLE_DEVELOPMENT_FEATURES;
	self.limitScrollbackBufferSize = DEFAULT_LIMIT_SCROLLBACK_BUFFER_SIZE;
	self.scrollbackBufferSizeInKBs = DEFAULT_SCROLLBACK_BUFFER_SIZE_IN_KB;
	self.tads2Encoding = DEFAULT_TADS2_ENCODING;
	self.tads2EncodingOverride = DEFAULT_TADS2_ENCODING_OVERRIDE;
}

- (void)setDefaultDevModePrefs
{
	// use self.* to trigger bindings for UI
	
	self.printBrokenHtmlMarkup = DEFAULT_PRINT_BROKEN_HTML_MARKUP;
	self.showParserMode = DEFAULT_SHOW_PARSER_MODE;
	self.spellCheckGameText = DEFAULT_SPELL_CHECK_GAME_TEXT;
	self.grammarCheckGameText = DEFAULT_GRAMMAR_CHECK_GAME_TEXT;
}

- (void)refreshDerivedNonPersistedPrefs
{
	self.enableChoiceOfSpecificGamesDirectory = (self.gamesDirectoryMode == XTPREFS_DIR_MODE_SPECIFIC);
	self.enableChoiceOfSpecificSavesDirectory = (self.savesDirectoryMode == XTPREFS_DIR_MODE_SPECIFIC);
	self.enableChoiceOfSpecificTranscriptsDirectory = (self.transcriptsDirectoryMode == XTPREFS_DIR_MODE_SPECIFIC);
	self.enableChoiceOfSpecificCommandScriptsDirectory = (self.commandScriptsDirectoryMode == XTPREFS_DIR_MODE_SPECIFIC);
	
	[self updateFontDescriptions];
}

- (void)updateFontDescriptions
{
	self.defaultFontDescription = [self fontDescriptionForFontName:self.defaultFontName
															  size:self.defaultFontSize];
	
	self.fixedWidthFontDescription = [self fontDescriptionForFontName:self.fixedWidthFontName
																 size:self.fixedWidthFontSize];
	
	self.serifedFontDescription = [self fontDescriptionForFontName:self.serifedFontName
															  size:self.serifedFontSize];
	
	self.sansSerifFontDescription = [self fontDescriptionForFontName:self.sansSerifFontName
																size:self.sansSerifFontSize];
	
	self.scriptFontDescription = [self fontDescriptionForFontName:self.scriptFontName
															 size:self.scriptFontSize];
	
	self.typewriterFontDescription = [self fontDescriptionForFontName:self.typewriterFontName
																 size:self.typewriterFontSize];
	
	self.inputFontDescription = [self fontDescriptionForFontName:self.inputFontName
															size:self.inputFontSize];
	
	self.statusLineFontDescription = [self fontDescriptionForFontName:self.statusLineFontName
																 size:self.statusLineFontSize];
}

- (BOOL)registerMissingUserDefaultsInDict:(NSDictionary *)dict
{
	XT_DEF_SELNAME;
	
	NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
	
	BOOL didSetAValue = NO;
	
	for (NSString *key in dict.allKeys) {
		NSObject *value = [userDefaults objectForKey:key];
		if (value == nil) {
			// missing value, so add the default value:
			NSObject *defaultValue = dict[key];
			if (defaultValue != nil && (! [defaultValue isKindOfClass:[NSNull class]])) {
				if ([defaultValue isKindOfClass:[NSColor class]]) {
					defaultValue = [self dataFromColor:(NSColor *)defaultValue];
				}
				[userDefaults setObject:defaultValue forKey:key];
				XT_TRACE_2(LOG_FMT_OBJ_EQ_OBJ, key, defaultValue);
				didSetAValue = YES;
			}
		}
	}
	
	return didSetAValue;
}

- (NSColor *)restoreColorWithKey:(NSString *)key
					defaultValue:(NSColor *)defaultValue
{
	XT_DEF_SELNAME;

	NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];

	NSData *data = [userDefaults dataForKey:key];
	NSColor *res =nil;
	if (data != nil) {
		res = (NSColor *)[NSUnarchiver unarchiveObjectWithData:data];
	} else {
		XT_WARN_1(@"illegal/missing value for \"%@\" -- using default value", key);
		res = defaultValue;
	}
	return res;
}

- (NSNumber *)restoreBooleanWithKey:(NSString *)key
					   defaultValue:(NSNumber *)defaultValue
{
	XT_DEF_SELNAME;
	
	NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];

	NSNumber *res;
	
	if ([self valueExistsForKey:key inUserDefaults:userDefaults]) {
		NSInteger i = [userDefaults integerForKey:key];
		res = [NSNumber numberWithBool:i];
	} else {
		res = defaultValue;
	}
	
	XT_TRACE_2(@"\"%@\" = %ld", key, res.longValue);
	
	return res;
}

- (NSNumber *)restoreIntegerWithKey:(NSString *)key
					   defaultValue:(NSNumber *)defaultValue
{
	XT_DEF_SELNAME;
	
	NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
	
	NSNumber *res;

	if ([self valueExistsForKey:key inUserDefaults:userDefaults]) {
		NSInteger i = [userDefaults integerForKey:key];
		res = [NSNumber numberWithInteger:i];
	} else {
		res = defaultValue;
	}
	
	XT_TRACE_2(@"\"%@\" = %ld", key, res.integerValue);
	
	return res;
}

- (NSString *)restoreFontNameWithKey:(NSString *)key
						defaultValue:(NSString *)defaultValue
{
	XT_DEF_SELNAME;
	
	NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
	
	NSString *res = [userDefaults stringForKey:key];
	if (res == nil) {
		res = defaultValue;
	}
	
	XT_TRACE_2(@"\"%@\" = \"%@\"", key, res);
	
	return res;
}

- (NSNumber *)restoreFontSizeWithKey:(NSString *)key
						 defaultSize:(NSNumber *)defaultSize
{
	XT_DEF_SELNAME;
	
	NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
	
	CGFloat fontSize = [userDefaults floatForKey:key];
	if (fontSize == 0.0) {
		fontSize = defaultSize.floatValue;
		XT_WARN_2(@"illegal/missing value for \"%@\" -- defaulting to %f", key, fontSize);
	}
	NSNumber *res = [NSNumber numberWithFloat:fontSize];

	XT_TRACE_2(@"\"%@\" = \"%f\"", key, res.floatValue);
	
	return res;
}
	 
- (BOOL)valueExistsForKey:(NSString *)key inUserDefaults:(NSUserDefaults *)userDefaults
{
	NSString *value = [userDefaults stringForKey:key];
	return (value != nil);
}

- (XTPrefsDirectoryMode)directoryModeFromString:(NSString *)s
{
	XT_DEF_SELNAME;
	
	XTPrefsDirectoryMode res;
	
	if ([VALUE_DIRECTORY_MODE_SPECIFIC isEqualToString:s]) {
		res = XTPREFS_DIR_MODE_SPECIFIC;
	} else if ([VALUE_DIRECTORY_MODE_NONE isEqualToString:s]) {
		res = XTPREFS_DIR_MODE_NONE;
	} else if ([VALUE_DIRECTORY_MODE_LAST_SELECTED isEqualToString:s]) {
		res = XTPREFS_DIR_MODE_LAST_SELECTED;
	} else if ([VALUE_DIRECTORY_MODE_CURRENT_GAMEFILE isEqualToString:s]) {
		res = XTPREFS_DIR_MODE_CURRENT_GAMEFILE;
	} else {
		XT_WARN_1(@"got unknown string \"%@\"", s);
		res = XTPREFS_DIR_MODE_NONE;
	}
	
	return res;
}

- (NSString *)stringFromDirectoryMode:(XTPrefsDirectoryMode)mode
{
	XT_DEF_SELNAME;

	NSString *res = nil;
	
	switch (mode) {
		case XTPREFS_DIR_MODE_NONE:
			res = VALUE_DIRECTORY_MODE_NONE;
			break;
		case XTPREFS_DIR_MODE_SPECIFIC:
			res = VALUE_DIRECTORY_MODE_SPECIFIC;
			break;
		case XTPREFS_DIR_MODE_LAST_SELECTED:
			res = VALUE_DIRECTORY_MODE_LAST_SELECTED;
			break;
		case XTPREFS_DIR_MODE_CURRENT_GAMEFILE:
			res = VALUE_DIRECTORY_MODE_CURRENT_GAMEFILE;
			break;
		default:
			XT_WARN_1(@"got unknown mode %ld", mode);
			res = VALUE_DIRECTORY_MODE_NONE;
			break;
	}

	return res;
}

- (XTPrefsFileNameMode)fileNameModeFromString:(NSString *)s
{
	XT_DEF_SELNAME;

	XTPrefsFileNameMode res;
	
	if ([VALUE_FILE_NAME_MODE_DATE_TIME isEqualToString:s]) {
		res = XTPREFS_FILENAME_MODE_GAMENAME_DATETIME;
	} else if ([VALUE_FILE_NAME_MODE_UNTITLED isEqualToString:s]) {
		res = XTPREFS_FILENAME_MODE_UNTITLED;
	} else {
		XT_WARN_1(@"got unknown string \"%@\"", s);
		res = XTPREFS_FILENAME_MODE_UNTITLED;
	}
	
	return res;
}

- (NSString *)stringFromFileNameMode:(XTPrefsFileNameMode)mode
{
	XT_DEF_SELNAME;

	NSString *res = nil;
	
	switch (mode) {
		case XTPREFS_FILENAME_MODE_GAMENAME_DATETIME:
			res = VALUE_FILE_NAME_MODE_DATE_TIME;
			break;
		case XTPREFS_FILENAME_MODE_UNTITLED:
			res = VALUE_FILE_NAME_MODE_UNTITLED;
			break;
		default:
			XT_WARN_1(@"got unknown mode %ld", mode);
			res = VALUE_FILE_NAME_MODE_UNTITLED;
			break;
	}
	
	return res;
}

- (XTPrefsIOSafetyMode)ioSafetyModeFromString:(NSString *)s
{
	XT_DEF_SELNAME;

	XTPrefsIOSafetyMode res;
	
	if ([VALUE_IO_SAFETY_MODE_NO_ACCESS isEqualToString:s]) {
		res = XTPREFS_IO_SAFETY_MODE_NO_ACCESS;
	} else if ([VALUE_IO_SAFETY_MODE_GAME_DIR_ONLY isEqualToString:s]) {
		res = XTPREFS_IO_SAFETY_MODE_GAME_DIR_ONLY;
	} else if ([VALUE_IO_SAFETY_MODE_ANYWHERE isEqualToString:s]) {
		res = XTPREFS_IO_SAFETY_MODE_ANYWHERE;
	} else {
		XT_WARN_1(@"got unknown string \"%@\"", s);
		res = XTPREFS_IO_SAFETY_MODE_GAME_DIR_ONLY;
			//TODO make a DEFAULT..  sym, likewise elsewhere
	}
	
	return res;
}

- (NSString *)stringFromIOSafetyMode:(XTPrefsIOSafetyMode)mode
{
	XT_DEF_SELNAME;

	NSString *res = nil;
	
	switch (mode) {
		case XTPREFS_IO_SAFETY_MODE_NO_ACCESS:
			res = VALUE_IO_SAFETY_MODE_NO_ACCESS;
			break;
		case XTPREFS_IO_SAFETY_MODE_GAME_DIR_ONLY:
			res = VALUE_IO_SAFETY_MODE_GAME_DIR_ONLY;
			break;
		case XTPREFS_IO_SAFETY_MODE_ANYWHERE:
			res = VALUE_IO_SAFETY_MODE_ANYWHERE;
			break;
		default:
			XT_WARN_1(@"got unknown mode %ld", mode);
			res = VALUE_IO_SAFETY_MODE_GAME_DIR_ONLY;
			break;
	}
	
	return res;
}

- (XTPrefsGameWindowStartMode)gameWindowStartModeFromString:(NSString *)s
{
	XT_DEF_SELNAME;
	
	XTPrefsGameWindowStartMode res;

	if ([VALUE_GAME_WINDOW_START_MODE_SAME_AS_LAST isEqualToString:s]) {
		res = XTPREFS_GAME_WINDOW_SAME_AS_LAST;
	} else if ([VALUE_GAME_WINDOW_START_MODE_NICE_IN_MIDDLE isEqualToString:s]) {
		res = XTPREFS_GAME_WINDOW_NICE_IN_MIDDLE;
	} else if ([VALUE_GAME_WINDOW_START_MODE_WHATEVER isEqualToString:s]) {
		res = XTPREFS_GAME_WINDOW_WHATEVER;
	} else {
		XT_WARN_1(@"got unknown string \"%@\"", s);
		res = XTPREFS_GAME_WINDOW_SAME_AS_LAST;
	}
	return res;
}

- (NSString *)stringFromGameWindowStartMode:(XTPrefsGameWindowStartMode)mode
{
	XT_DEF_SELNAME;
	
	NSString *res = nil;
	
	switch (mode) {
		case XTPREFS_GAME_WINDOW_SAME_AS_LAST:
			res = VALUE_GAME_WINDOW_START_MODE_SAME_AS_LAST;
			break;
		case XTPREFS_GAME_WINDOW_NICE_IN_MIDDLE:
			res = VALUE_GAME_WINDOW_START_MODE_NICE_IN_MIDDLE;
			break;
		case XTPREFS_GAME_WINDOW_WHATEVER:
			res = VALUE_GAME_WINDOW_START_MODE_WHATEVER;
			break;
		default:
			XT_WARN_1(@"got unknown mode %ld", mode);
			res = VALUE_GAME_WINDOW_START_MODE_SAME_AS_LAST;
			break;
	}
	return res;
}

- (NSString *)stringFromUrl:(NSURL *)url
{
	NSString *res = @"";
	if (url != nil) {
		res = [url absoluteString];
	}
	return res;
}

- (NSData *)dataFromColor:(NSColor *)color
{
	NSData *res = [NSArchiver archivedDataWithRootObject:color];
	return res;
}

- (NSString *)fontDescriptionForFontName:(NSString *)fontName size:(NSNumber *)size
{
	NSString *res = [NSString stringWithFormat:@"%@ %u pt.", fontName, size.unsignedIntValue];
	return res;
}

@end
