//
//  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"
#import "XTAllocDeallocCounter.h"
#import "XTLoggerAndLevel.h"
#import "XTPrefsItem.h"
#import "XTPrefsItemFloat.h"
#import "XTPrefsItemBool.h"
#import "XTPrefsItemBoolAffectingColors.h"
#import "XTPrefsGroup.h"
#import "XTPrefsUrlToStringValueTransformer.h"


@interface XTPrefs ()

@property XTPrefsGroup *groupDirsAndFiles;
@property XTPrefsGroup *groupFonts;
@property XTPrefsGroup *groupColors;
@property XTPrefsGroup *groupLayout;
@property XTPrefsGroup *groupIOSafety;
@property XTPrefsGroup *groupMisc;
@property XTPrefsGroup *groupDevMode;
@property XTPrefsGroup *groupDevInternal;

@property NSMutableArray<XTPrefsGroup *> *groups;

@property NSArray *unusedUserDefaultsKeys;

@property BOOL skipPersistOnKVOEvent;

@property BOOL enableChoiceOfSpecificGamesDirectory;
@property BOOL enableChoiceOfSpecificSavesDirectory;
@property BOOL enableChoiceOfSpecificTranscriptsDirectory;
@property BOOL enableChoiceOfSpecificCommandScriptsDirectory;

@property NSUInteger observationCount;

@end


@implementation XTPrefs

static XTLogger* logger;

#define KEY_USER_DEFAULTS_WRITE_COUNTER @"XTadsPrefsWriteCounter"

#define DEFAULT_MIN_ALLOWED_FONT_SIZE [NSNumber numberWithInteger:6]
#define DEFAULT_MAX_ALLOWED_FONT_SIZE [NSNumber numberWithInteger:72]

#define EMPTY_DIRECTORY_URL [NSURL URLWithString:@""]

static XTPrefs *singletonInstance = nil;

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

	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;
}

OVERRIDE_ALLOC_FOR_COUNTER

OVERRIDE_DEALLOC_FOR_COUNTER

- (id)init
{
	self = [super init];
	if (self != nil) {
		//TO DO rm:
		//[self cleanupAll];
		
		_groups = [NSMutableArray<XTPrefsGroup *> arrayWithCapacity:10];
		
		[self initGroupDirsAndFiles];
		[self initGroupFonts];
		[self initGroupColors];
		[self initGroupLayout];
		[self initGroupIOSafety];
		[self initGroupMisc];
		[self initGroupDevMode];
		[self initGroupInternal];

		[self refreshDerivedNonPersistedPrefs];
		
		[self initUnusedUserDefaultsKeys];
		
		_skipPersistOnKVOEvent = NO;
		
		_alwaysFalse = NO;
		_minMinAllowedFontSize = DEFAULT_MIN_ALLOWED_FONT_SIZE;
		_maxMaxAllowedFontSize = DEFAULT_MAX_ALLOWED_FONT_SIZE;
		
		_observationCount = 0;
	}
	return self;
}

- (void)initGroupDirsAndFiles
{
	XTPrefsUrlToStringValueTransformer *urlToStringValueTransformer = [XTPrefsUrlToStringValueTransformer new];

	XTPrefsGroup *group = [XTPrefsGroup new];
	[self.groups addObject:group];
	
	_gamesDirectoryMode = [XTPrefsItem itemWithDefaultValue:XTPREFS_DIRECTORY_MODE_NONE
											userDefaultsKey:@"XTadsGamesDirectoryMode"];
	[group add:_gamesDirectoryMode];
	
	_gamesDirectoryWhenSpecific = [XTPrefsItem itemWithDefaultValue:EMPTY_DIRECTORY_URL
													userDefaultsKey:@"XTadsGamesDirectoryWhenSpecific"
												   valueTransformer:urlToStringValueTransformer];
	[group add:_gamesDirectoryWhenSpecific];

	_gamesDirectoryLastUsed = [XTPrefsItem itemWithDefaultValue:EMPTY_DIRECTORY_URL
												userDefaultsKey:@"XTadsGamesDirectoryLastUsed"
											   valueTransformer:urlToStringValueTransformer];
	[group add:_gamesDirectoryLastUsed];

	_savesDirectoryMode = [XTPrefsItem itemWithDefaultValue:XTPREFS_DIRECTORY_MODE_NONE
											userDefaultsKey:@"XTadsSavesDirectoryMode"];
	[group add:_savesDirectoryMode];

	_savesDirectoryWhenSpecific = [XTPrefsItem itemWithDefaultValue:EMPTY_DIRECTORY_URL
													userDefaultsKey:@"XTadsSavesDirectoryWhenSpecific"
												   valueTransformer:urlToStringValueTransformer];
	[group add:_savesDirectoryWhenSpecific];

	_savesDirectoryLastUsed = [XTPrefsItem itemWithDefaultValue:EMPTY_DIRECTORY_URL
												userDefaultsKey:@"XTadsSavesDirectoryLastUsed"
											   valueTransformer:urlToStringValueTransformer];
	[group add:_savesDirectoryLastUsed];

	_savesFileNameMode = [XTPrefsItem itemWithDefaultValue:XTPREFS_FILE_NAME_MODE_DATE_TIME
										   userDefaultsKey:@"XTadsSavesFileNameMode"];
	[group add:_savesFileNameMode];

	_transcriptsDirectoryMode = [XTPrefsItem itemWithDefaultValue:XTPREFS_DIRECTORY_MODE_NONE
												  userDefaultsKey:@"XTadsTranscriptsDirectoryMode"];
	[group add:_transcriptsDirectoryMode];

	_transcriptsDirectoryWhenSpecific = [XTPrefsItem itemWithDefaultValue:EMPTY_DIRECTORY_URL
														  userDefaultsKey:@"XTadsTranscriptsDirectoryWhenSpecific"
														 valueTransformer:urlToStringValueTransformer];
	[group add:_transcriptsDirectoryWhenSpecific];

	_transcriptsDirectoryLastUsed = [XTPrefsItem itemWithDefaultValue:EMPTY_DIRECTORY_URL
													  userDefaultsKey:@"XTadsTranscriptsDirectoryLastUsed"
													 valueTransformer:urlToStringValueTransformer];
	[group add:_transcriptsDirectoryLastUsed];

	_transcriptsFileNameMode = [XTPrefsItem itemWithDefaultValue:XTPREFS_FILE_NAME_MODE_DATE_TIME
												 userDefaultsKey:@"XTadsTranscriptsFileNameMode"];
	[group add:_transcriptsFileNameMode];

	_commandScriptsDirectoryMode = [XTPrefsItem itemWithDefaultValue:XTPREFS_DIRECTORY_MODE_NONE
													 userDefaultsKey:@"XTadsCommandScriptsDirectoryMode"];
	[group add:_commandScriptsDirectoryMode];

	_commandScriptsDirectoryWhenSpecific = [XTPrefsItem itemWithDefaultValue:EMPTY_DIRECTORY_URL
															 userDefaultsKey:@"XTadsCommandScriptsDirectoryWhenSpecific"
															valueTransformer:urlToStringValueTransformer];
	[group add:_commandScriptsDirectoryWhenSpecific];

	_commandScriptsDirectoryLastUsed = [XTPrefsItem itemWithDefaultValue:EMPTY_DIRECTORY_URL
														 userDefaultsKey:@"XTadsCommandScriptsDirectoryLastUsed"
														valueTransformer:urlToStringValueTransformer];
	[group add:_commandScriptsDirectoryLastUsed];

	_commandScriptsFileNameMode = [XTPrefsItem itemWithDefaultValue:XTPREFS_FILE_NAME_MODE_DATE_TIME
													userDefaultsKey:@"XTadsCommandScriptsFileNameMode"];
	[group add:_commandScriptsFileNameMode];

	_groupDirsAndFiles = group;
}

- (void)initGroupFonts
{
	XTPrefsGroup *group = [XTPrefsGroup new];
	[self.groups addObject:group];

	_defaultFontName = [XTPrefsItem itemWithDefaultValue:@"Helvetica"
										 userDefaultsKey:@"XTadsDefaultFontName"];
	[group add:_defaultFontName];
	
	_defaultFontSize = [XTPrefsItemFloat itemWithDefaultValue:[NSNumber numberWithFloat:14]
											  userDefaultsKey:@"XTadsDefaultFontSize"];
	[group add:_defaultFontSize];

	_fixedWidthFontName = [XTPrefsItem itemWithDefaultValue:@"Consolas"
											userDefaultsKey:@"XTadsFixedWidthFontName"];
	[group add:_fixedWidthFontName];
	
	_fixedWidthFontSize = [XTPrefsItemFloat itemWithDefaultValue:[NSNumber numberWithFloat:14]
												 userDefaultsKey:@"XTadsFixedWidthFontSize"];
	[group add:_fixedWidthFontSize];
	
	_serifedFontName = [XTPrefsItem itemWithDefaultValue:@"Georgia"
										 userDefaultsKey:@"XTadsSerifedFontName"];
	[group add:_serifedFontName];
	
	_serifedFontSize = [XTPrefsItemFloat itemWithDefaultValue:[NSNumber numberWithFloat:14]
											  userDefaultsKey:@"XTadsSerifedFontSize"];
	[group add:_serifedFontSize];
	
	_sansSerifFontName = [XTPrefsItem itemWithDefaultValue:@"Helvetica"
										   userDefaultsKey:@"XTadsSansSerifFontName"];
	[group add:_sansSerifFontName];
	
	_sansSerifFontSize = [XTPrefsItemFloat itemWithDefaultValue:[NSNumber numberWithFloat:14]
												userDefaultsKey:@"XTadsSansSerifFontSize"];
	[group add:_sansSerifFontSize];
	
	_scriptFontName = [XTPrefsItem itemWithDefaultValue:@"Apple Chancery"
										userDefaultsKey:@"XTadsScriptFontName"];
	[group add:_scriptFontName];
	
	_scriptFontSize = [XTPrefsItemFloat itemWithDefaultValue:[NSNumber numberWithFloat:14]
											 userDefaultsKey:@"XTadsScriptFontSize"];
	[group add:_scriptFontSize];
	
	_typewriterFontName = [XTPrefsItem itemWithDefaultValue:@"Consolas"
											userDefaultsKey:@"XTadsTypewriterFontName"];
	[group add:_typewriterFontName];
	
	_typewriterFontSize = [XTPrefsItemFloat itemWithDefaultValue:[NSNumber numberWithFloat:14]
												 userDefaultsKey:@"XTadsTypewriterFontSize"];
	[group add:_typewriterFontSize];
	
	_inputFontIsSameAsDefaultFont = [XTPrefsItemBool itemWithDefaultValue:[NSNumber numberWithBool:TRUE]
														  userDefaultsKey:@"XTadsInputFontIsSameAsDefaultFont"];
	[group add:_inputFontIsSameAsDefaultFont];
	
	_inputFontName = [XTPrefsItem itemWithDefaultValue:@"Helvetica"
									   userDefaultsKey:@"XTadsInputFontName"];
	[group add:_inputFontName];
	
	_inputFontSize = [XTPrefsItemFloat itemWithDefaultValue:[NSNumber numberWithFloat:14]
											userDefaultsKey:@"XTadsInputFontSize"];
	[group add:_inputFontSize];
	
	_inputFontUsedEvenIfNotRequestedByGame = [XTPrefsItemBool itemWithDefaultValue:[NSNumber numberWithBool:TRUE]
																   userDefaultsKey:@"XTadsInputFontUsedEvenIfNotRequestedByGame"];
	[group add:_inputFontUsedEvenIfNotRequestedByGame];
	
	_allowGamesToSetFonts = [XTPrefsItemBool itemWithDefaultValue:[NSNumber numberWithBool:TRUE]
												  userDefaultsKey:@"XTadsAllowGamesToSetFonts"];
	[group add:_allowGamesToSetFonts];
	
	_minAllowedFontSize = [XTPrefsItemInteger itemWithDefaultValue:DEFAULT_MIN_ALLOWED_FONT_SIZE
												   userDefaultsKey:@"XTadsMinAllowedFontSize"];
	[group add:_minAllowedFontSize];
	
	_maxAllowedFontSize = [XTPrefsItemInteger itemWithDefaultValue:DEFAULT_MAX_ALLOWED_FONT_SIZE
												   userDefaultsKey:@"XTadsMaxAllowedFontSize"];
	[group add:_maxAllowedFontSize];
	
	_groupFonts = group;
}

- (void)initGroupColors
{
	XTPrefsGroup *group = [XTPrefsGroup new];
	[self.groups addObject:group];
	
	_statusLineTextColor = [XTPrefsItemColor itemWithDefaultValue:[NSColor whiteColor]
												  userDefaultsKey:@"XTadsStatusLineTextColor"];
	[group add:_statusLineTextColor];
	
	_statusLineBackgroundColor = [XTPrefsItemColor itemWithDefaultValue:[NSColor blackColor]
														userDefaultsKey:@"XTadsStatusLineBackgroundColor"];
	[group add:_statusLineBackgroundColor];
	
	_outputAreaTextColor = [XTPrefsItemColor itemWithDefaultValue:[NSColor blackColor]
												  userDefaultsKey:@"XTadsOutputAreaTextColor"];
	[group add:_outputAreaTextColor];
	
	_outputAreaBackgroundColor = [XTPrefsItemColor itemWithDefaultValue:[NSColor whiteColor]
														userDefaultsKey:@"XTadsOutputAreaBackgroundColor"];
	[group add:_outputAreaBackgroundColor];
	
	_inputTextColor = [XTPrefsItemColor itemWithDefaultValue:[NSColor blackColor]
											 userDefaultsKey:@"XTadsInputTextColor"];
	[group add:_inputTextColor];
	
	_linksTextColor = [XTPrefsItemColor itemWithDefaultValue:[NSColor blueColor]
											 userDefaultsKey:@"XTadsLinksTextColor"];
	[group add:_linksTextColor];
	
	_linksUnderline = [XTPrefsItemBoolLinksUnderline itemWithDefaultValue:[NSNumber numberWithBool:TRUE]
														  userDefaultsKey:@"XTadsLinksUnderline"];
	[group add:_linksUnderline];
	
	_linksShowToolTips = [XTPrefsItemBool itemWithDefaultValue:[NSNumber numberWithBool:TRUE]
											   userDefaultsKey:@"XTadsLinksShowTooltips"];
	[group add:_linksShowToolTips];
	
	_allowGamesToSetColors = [XTPrefsItemBoolAffectingColors itemWithDefaultValue:[NSNumber numberWithBool:TRUE]
																  userDefaultsKey:@"XTadsAllowGamesToSetColors"];
	[group add:_allowGamesToSetColors];
	
	_groupColors = group;
}

- (void)initGroupLayout
{
	XTPrefsGroup *group = [XTPrefsGroup new];
	[self.groups addObject:group];
	
	_gameWindowStartMode = [XTPrefsItem itemWithDefaultValue:XTPREFS_GAME_WINDOW_START_MODE_SAME_AS_LAST
											 userDefaultsKey:@"XTadsGameWindowStartMode"];
	[group add:_gameWindowStartMode];
	
	_groupLayout = group;
}

- (void)initGroupIOSafety
{
	XTPrefsGroup *group = [XTPrefsGroup new];
	[self.groups addObject:group];

	_readSafetyMode = [XTPrefsItem itemWithDefaultValue:XTPREFS_IO_SAFETY_MODE_GAME_DIR_ONLY
										userDefaultsKey:@"XTadsIOSafetyModeRead"];
	[group add:_readSafetyMode];
	
	_writeSafetyMode = [XTPrefsItem itemWithDefaultValue:XTPREFS_IO_SAFETY_MODE_GAME_DIR_ONLY
										 userDefaultsKey:@"XTadsIOSafetyModeWrite"];
	[group add:_writeSafetyMode];
	
	_groupIOSafety = group;
}

- (void)initGroupMisc
{
	XTPrefsGroup *group = [XTPrefsGroup new];
	[self.groups addObject:group];
	
	_askForGameFileOnTerpStart = [XTPrefsItemBool itemWithDefaultValue:[NSNumber numberWithBool:TRUE]
													   userDefaultsKey:@"XTadsAskForGameFileOnTerpStart"];
	[group add:_askForGameFileOnTerpStart];
	
	_printTadsBannerOnGameStart = [XTPrefsItemBool itemWithDefaultValue:[NSNumber numberWithBool:FALSE]
														userDefaultsKey:@"XTadsPrintTadsBannerOnGameStart"];
	[group add:_printTadsBannerOnGameStart];
	
	_askForConfirmationOnGameRestart = [XTPrefsItemBool itemWithDefaultValue:[NSNumber numberWithBool:TRUE]
															 userDefaultsKey:@"XTadsAskForConfirmationOnGameRestart"];
	[group add:_askForConfirmationOnGameRestart];
	
	_askForConfirmationOnGameQuit = [XTPrefsItemBool itemWithDefaultValue:[NSNumber numberWithBool:TRUE]
														  userDefaultsKey:@"XTadsAskForConfirmationOnGameQuit"];
	[group add:_askForConfirmationOnGameQuit];
	
	_askForConfirmationOnGameOpenIfGameRunning = [XTPrefsItemBool itemWithDefaultValue:[NSNumber numberWithBool:TRUE]
																	   userDefaultsKey:@"XTadsAskForConfirmationOnGameOpenIfGameRunning"];
	[group add:_askForConfirmationOnGameOpenIfGameRunning];
	
	_enableDevelopmentModeFeatures = [XTPrefsItemBool itemWithDefaultValue:[NSNumber numberWithBool:FALSE]
														   userDefaultsKey:@"XTadsEnableDevelopmentFeatures"];
	[group add:_enableDevelopmentModeFeatures];
	
	_limitScrollbackBufferSize = [XTPrefsItemBool itemWithDefaultValue:[NSNumber numberWithBool:TRUE]
													   userDefaultsKey:@"XTadsLimitScrollBackBufferSize"];
	[group add:_limitScrollbackBufferSize];
	
	_scrollbackBufferSizeInKBs = [XTPrefsItem itemWithDefaultValue:[NSNumber numberWithUnsignedInteger:XTPREFS_SCROLLBACK_BUFFER_SIZE_50KB]
												   userDefaultsKey:@"XTadsScrollBackBufferSizeInKB"];
	[group add:_scrollbackBufferSizeInKBs];
	
	_tads2Encoding = [XTPrefsItem itemWithDefaultValue:[NSNumber numberWithUnsignedInteger:NSISOLatin1StringEncoding]
									   userDefaultsKey:@"XTadsTads2Encoding"];
	[group add:_tads2Encoding];
	
	_tads2EncodingOverride = [XTPrefsItemBool itemWithDefaultValue:[NSNumber numberWithBool:FALSE]
												   userDefaultsKey:@"XTadsTads2EncodingOverride"];
	[group add:_tads2EncodingOverride];
	
	_keepCommandHistoryWhenStartingNewGame = [XTPrefsItemBool itemWithDefaultValue:[NSNumber numberWithBool:TRUE]
																   userDefaultsKey:@"XTadsKeepCommandHistoryWhenStartingNewGame"];
	[group add:_keepCommandHistoryWhenStartingNewGame];
	 
	_emulateHtmlBannerForTradStatusLine = [XTPrefsItemBool itemWithDefaultValue:[NSNumber numberWithBool:TRUE]
																userDefaultsKey:@"XTadsEmulateHtmlBannerForTradStatusLine"];
	[group add:_emulateHtmlBannerForTradStatusLine];

	_groupMisc = group;
}

- (void)initGroupDevMode
{
	XTPrefsGroup *group = [XTPrefsGroup new];
	[self.groups addObject:group];

	_printBrokenHtmlMarkup = [XTPrefsItemBool itemWithDefaultValue:[NSNumber numberWithBool:FALSE]
												   userDefaultsKey:@"XTadsPrintBrokenHtmlMarkup"];
	[group add:_printBrokenHtmlMarkup];
	
	_showParserMode = [XTPrefsItemBool itemWithDefaultValue:[NSNumber numberWithBool:FALSE]
											userDefaultsKey:@"XTadsShowParserMode"];
	[group add:_showParserMode];
	
	_spellCheckGameText = [XTPrefsItemBool itemWithDefaultValue:[NSNumber numberWithBool:FALSE]
												userDefaultsKey:@"XTadsSpellCheckGameText"];
	[group add:_spellCheckGameText];
	
	_grammarCheckGameText = [XTPrefsItemBool itemWithDefaultValue:[NSNumber numberWithBool:FALSE]
												  userDefaultsKey:@"XTadsGrammarCheckGameText"];
	[group add:_grammarCheckGameText];
	
	_logLevelByName = [XTPrefsItemLoglevelByName itemWithDefaultValue:[NSMutableDictionary dictionary]
													  userDefaultsKey:@"XTadsLogLevelByName"];
	[group add:_logLevelByName];
	
	_groupDevMode = group;
}

- (void)initGroupInternal
{
	XTPrefsGroup *group = [XTPrefsGroup new];
	[self.groups addObject:group];

	// Turn on regular press-and-hold behaviour in NSTextView, disabling accented char popups:
	// (https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKitOlderNotes/)
	XTPrefsItemBool *pressAndHoldEnabled = [XTPrefsItemBool itemWithDefaultValue:[NSNumber numberWithBool:NO]
																 userDefaultsKey:@"ApplePressAndHoldEnabled"];
	[group add:pressAndHoldEnabled];
	
	// Turn off Edit|Start Dictation and Edit|Emoji & Symbols
	// See: http://stackoverflow.com/questions/21369736/remove-start-dictation-and-special-characters-from-menu
	XTPrefsItemBool *disabledDictationMenuItem = [XTPrefsItemBool itemWithDefaultValue:[NSNumber numberWithBool:YES]
																	   userDefaultsKey:@"NSDisabledDictationMenuItem"];
	[group add:disabledDictationMenuItem];
	
	// A menu item we don't want
	XTPrefsItemBool *disabledCharacterPaletteMenuItem = [XTPrefsItemBool itemWithDefaultValue:[NSNumber numberWithBool:YES]
																			  userDefaultsKey:@"NSDisabledCharacterPaletteMenuItem"];
	[group add:disabledCharacterPaletteMenuItem];
	
	//Set the NSUserDefault NSConstraintBasedLayoutVisualizeMutuallyExclusiveConstraints to YES to have -[NSWindow visualizeConstraints:]
	// automatically called when this happens.  And/or, break on objc_exception_throw to catch this in the debugger.
	XTPrefsItemBool *visualizeMutuallyExclusiveConstraints = [XTPrefsItemBool itemWithDefaultValue:[NSNumber numberWithBool:NO]
																				   userDefaultsKey:@"NSConstraintBasedLayoutVisualizeMutuallyExclusiveConstraints"];
	[group add:visualizeMutuallyExclusiveConstraints];

	XTPrefsItemInteger *writeCounter = [XTPrefsItemInteger itemWithDefaultValue:[NSNumber numberWithInteger:0]
																userDefaultsKey:KEY_USER_DEFAULTS_WRITE_COUNTER];
	[writeCounter setSkipResetToDefault:YES];
	[group add:writeCounter];
	
	_groupDevInternal = group;
}


- (void)initUnusedUserDefaultsKeys
{
	_unusedUserDefaultsKeys = @[
	    // keys of no-longer-used prefs go here:
		@"XTadsLimitSizeOfScrollBackBuffer",
		@"XTadsSizeOfScrollBackBufferInKB",
		@"XTadsStatusLineFontIsSameAsDefaultFont",
		@"XTadsStatusLineFontName",
		@"XTadsStatusLineFontSize",
		@"XTadsAllowGamesToSetFontColors",
		@"XTadsAllowGamesToSetBackgroundColors"
	];
}

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

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

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

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

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

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

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

	[XTLogConfig restoreFromPrefs];
}

- (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
{
	// Note: this method assumes KV observing is not on

	//--- dev. mode (subset needed early) ---
	[self.logLevelByName restoreFromPersisted];
	[XTLogConfig restoreFromPrefs]; // ...so we get user's log levels as early as possible

	for (XTPrefsGroup *group in self.groups) {
		[group restoreFromPersisted];
	}
	
	[self refreshDerivedNonPersistedPrefs];
}

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

	//XT_WARN_0(@"continue...");
	
	for (XTPrefsGroup *group in self.groups) {
		[group persist];
	}
	
	[self updateWriteCounter];
	[self refreshDerivedNonPersistedPrefs];
	self.skipPersistOnKVOEvent = NO;
}

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

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

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

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

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

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

- (void)updateInputFontWithName:(NSString *)name size:(CGFloat)size
{
	self.inputFontName.value = name;
	self.inputFontSize.value = [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_DEF_SELNAME;
	XT_TRACE_2(@"%@ %@", self, observer);
	
	for (XTPrefsGroup *group in self.groups) {
		[group addObserver:observer];
	}
}

/*TODO rm if not needed
- (void)stopObservingChangesToAll:(NSObject *)observer
{
	XT_DEF_SELNAME;
	XT_TRACE_2(@"%@ %@", self, observer);

	for (XTPrefsGroup *group in self.groups) {
		[group removeObserver:observer];
	}
}*/

//------ 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)refreshDerivedNonPersistedPrefs
{
	self.enableChoiceOfSpecificGamesDirectory = [XTPREFS_DIRECTORY_MODE_SPECIFIC isEqualToString:self.gamesDirectoryMode.value];
	self.enableChoiceOfSpecificSavesDirectory = [XTPREFS_DIRECTORY_MODE_SPECIFIC isEqualToString:self.savesDirectoryMode.value];
	self.enableChoiceOfSpecificTranscriptsDirectory = [XTPREFS_DIRECTORY_MODE_SPECIFIC isEqualToString:self.transcriptsDirectoryMode.value];
	self.enableChoiceOfSpecificCommandScriptsDirectory = [XTPREFS_DIRECTORY_MODE_SPECIFIC isEqualToString:self.commandScriptsDirectoryMode.value];
	
	[self updateFontDescriptions];
}

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

- (BOOL)valueExistsForKey:(NSString *)key inUserDefaults:(NSUserDefaults *)userDefaults
{
	NSString *value = [userDefaults stringForKey:key];
	return (value != nil);
}

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

- (NSArray *)logLevelsSortedOnClassName  // ...of XTLoggerAndLevel
{
	NSMutableArray *res = [NSMutableArray arrayWithCapacity:self.logLevelByName.value.count];
	NSArray *keysSorted = [self.logLevelByName.value.allKeys sortedArrayUsingSelector: @selector(compare:)];
	for (NSString *key in keysSorted) {
		XTLoggerAndLevel *lal = [XTLoggerAndLevel new];
		lal.loggerName = key;
		lal.level = [self.logLevelByName.value objectForKey:key];
		[res addObject:lal];
	}
	return res;
}

- (void)updateLogLevels:(NSArray *)logLevels  // ...of XTLoggerAndLevel
{
	for (XTLoggerAndLevel *lal in logLevels) {
		self.logLevelByName.value[lal.loggerName] = lal.level;
	}
	[self persist];

	[XTLogConfig restoreFromPrefs];
}

- (void)updateLogLevelsFromDict:(NSDictionary *)logLevelsByName // ...of name (NSString) -> level (NSNumber)
{
	for (NSString *name in logLevelsByName.allKeys) {
		[self.logLevelByName.value setObject:logLevelsByName[name] forKey:name];
	}
}

@end
