//
//  CelestiaSettings.mm
//  celestia
//
//  Created by Hank Ramsey on Fri Oct 29 2004.
//  Copyright (C) 2001-9, the Celestia Development Team
//

#import "CelestiaSettings.h"
#import "NSString_ObjCPlusPlus.h"
#import "CelestiaController.h"
#import "CelestiaAppCore.h"
#include "celestiacore.h"
#include "celestia.h"
#include "render.h"


class MacOSXWatcher : public CelestiaWatcher
{
    private:
        CelestiaSettings *control;

    public:
    
        MacOSXWatcher(CelestiaCore* _appCore, CelestiaSettings* _control) : CelestiaWatcher(*_appCore), control(_control)
        {};
            
    public:

    void notifyChange(CelestiaCore* core, int i)
    {
        if ( 0 == (i & (
        CelestiaCore::LabelFlagsChanged 
        | CelestiaCore::RenderFlagsChanged 
        | CelestiaCore::VerbosityLevelChanged 
        | CelestiaCore::TimeZoneChanged       
        | CelestiaCore::AmbientLightChanged   
        | CelestiaCore::FaintestChanged       
        ))) { return; } 
        [control validateItems];
    };
};

@implementation CelestiaSettings

        static CelestiaCore *appCore;

#define CS_DefaultsName @"Celestia-1.4.0"
#define CS_NUM_PREV_VERSIONS 1
#define CS_AppDefaults @"defaults"
#define TAGDEF(tag,key) key, [NSNumber numberWithInt: tag], 

static NSString *CS_PREV_VERSIONS[CS_NUM_PREV_VERSIONS] = {
    @"Celestia-1.3.2"
};

CelestiaSettings* sharedInstance;


+(CelestiaSettings*) shared
{
    // class method to get single shared instance
    if (sharedInstance == nil) {
        sharedInstance = [[CelestiaSettings alloc]init];
    }
    return sharedInstance;
}

static CelestiaController *control;
static NSMutableDictionary* tagMap;

-(CelestiaSettings*)init
{
    if (sharedInstance != nil) {
        return sharedInstance;
    }

    self = [super init];

    tagMap = [ NSMutableDictionary dictionaryWithCapacity: 100 ];

    appCore = (CelestiaCore*) [[CelestiaAppCore sharedAppCore] appCore];

    tagDict = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
//        TAGDEF(101,@"time")
//        TAGDEF(999,@"timeScale")
        // render flags
        TAGDEF(400,@"showStars")
        TAGDEF(401,@"showPlanets")
        TAGDEF(402,@"showGalaxies")
        TAGDEF(430,@"showGlobulars")
        TAGDEF(417,@"showNebulae")
        TAGDEF(403,@"showDiagrams")
        TAGDEF(413,@"showBoundaries")
        TAGDEF(404,@"showCloudMaps")
        TAGDEF(407,@"showNightMaps")
        TAGDEF(408,@"showAtmospheres")
        TAGDEF(415,@"showCometTails")
        TAGDEF(416,@"showMarkers")
        TAGDEF(405,@"showOrbits")
//	TAGDEF(999,@"showPartialTrajectories")
        TAGDEF(409,@"showSmoothLines")
        TAGDEF(410,@"showEclipseShadows")
        TAGDEF(412,@"showRingShadows")
        TAGDEF(419,@"showCloudShadows")
//        TAGDEF(411,@"showStarsAsPoints")
        TAGDEF(414,@"showAutoMag")
        TAGDEF(406,@"showCelestialSphere")
        TAGDEF(420,@"showEclipticGrid")
        TAGDEF(421,@"showHorizonGrid")
        TAGDEF(422,@"showGalacticGrid")
        // object labels
        TAGDEF(500,@"showStarLabels")
        TAGDEF(501,@"showPlanetLabels")
        TAGDEF(502,@"showMoonLabels")
        TAGDEF(503,@"showConstellationLabels")
        TAGDEF(504,@"showGalaxyLabels")
        TAGDEF(514,@"showGlobularLabels")
        TAGDEF(505,@"showAsteroidLabels")
        TAGDEF(506,@"showSpacecraftLabels")
        TAGDEF(507,@"showLocationLabels")
        TAGDEF(508,@"showCometLabels")
        TAGDEF(509,@"showNebulaLabels")
        TAGDEF(510,@"showOpenClusterLabels")
        TAGDEF(511,@"showLatinConstellationLabels")
        TAGDEF(512,@"showDwarfPlanetLabels")
        TAGDEF(513,@"showMinorMoonLabels")
        // popups
        TAGDEF(610,@"hudDetail")
        TAGDEF(620,@"starStyle")
        TAGDEF(630,@"renderPath")
        TAGDEF(640,@"resolution")
        TAGDEF(650,@"timeZone")
        TAGDEF(660,@"dateFormat")
        // orbits
//        TAGDEF(999,@"minimumOrbitSize")
        TAGDEF(700,@"showPlanetOrbits")
        TAGDEF(701,@"showMoonOrbits")
        TAGDEF(702,@"showAsteroidOrbits")
        TAGDEF(704,@"showSpacecraftOrbits")
        TAGDEF(703,@"showCometOrbits")
        TAGDEF(705,@"showStellarOrbits")
        TAGDEF(706,@"showDwarfPlanetOrbits")
        TAGDEF(707,@"showMinorMoonOrbits")
        // feature labels
        TAGDEF(903,@"minimumFeatureSize")
        TAGDEF(800,@"showCityLabels")
        TAGDEF(801,@"showObservatoryLabels")
        TAGDEF(802,@"showLandingSiteLabels")
        TAGDEF(803,@"showCraterLabels")
        TAGDEF(804,@"showVallisLabels")
        TAGDEF(805,@"showMonsLabels")
        TAGDEF(806,@"showPlanumLabels")
        TAGDEF(807,@"showChasmaLabels")
        TAGDEF(808,@"showPateraLabels")
        TAGDEF(809,@"showMareLabels")
        TAGDEF(810,@"showRupesLabels")
        TAGDEF(811,@"showTesseraLabels")
        TAGDEF(812,@"showRegioLabels")
        TAGDEF(813,@"showChaosLabels")
        TAGDEF(814,@"showTerraLabels")
        TAGDEF(815,@"showAstrumLabels")
        TAGDEF(816,@"showCoronaLabels")
        TAGDEF(817,@"showDorsumLabels")
        TAGDEF(818,@"showFossaLabels")
        TAGDEF(819,@"showCatenaLabels")
        TAGDEF(820,@"showMensaLabels")
        TAGDEF(821,@"showRimaLabels")
        TAGDEF(822,@"showUndaeLabels")
        TAGDEF(824,@"showReticulumLabels")
        TAGDEF(825,@"showPlanitiaLabels")
        TAGDEF(826,@"showLineaLabels")
        TAGDEF(827,@"showFluctusLabels")
        TAGDEF(828,@"showFarrumLabels")
        TAGDEF(829,@"showEruptiveCenterLabels")
        TAGDEF(831,@"showOtherLabels")
        // stars
//        TAGDEF(999,@"distanceLimit")
        TAGDEF(900,@"ambientLightLevel")
//        TAGDEF(901,@"brightnessBias")
        TAGDEF(902,@"faintestVisible")
        TAGDEF(904,@"galaxyBrightness")
//        TAGDEF(999,@"saturationMagnitude")
        // alt surface
        TAGDEF(1100,@"altSurface")
        nil];
    keyArray = [tagDict allValues];

    NSDictionary *volatileTagDict = [NSDictionary dictionaryWithObjectsAndKeys:
        TAGDEF(1000,@"showBodyAxes")
        TAGDEF(1001,@"showFrameAxes")
        TAGDEF(1002,@"showSunDirection")
        TAGDEF(1003,@"showVelocityVector")
        TAGDEF(1004,@"showPlanetographicGrid")
        TAGDEF(1005,@"showTerminator")
        nil];
    [tagDict addEntriesFromDictionary: volatileTagDict];

    return self;
}

-(void) setControl: (id) _control
{
    control = _control;
}

- (NSDictionary *) defaultsDictionary
{	
	NSMutableDictionary* theDictionary = [NSMutableDictionary dictionaryWithCapacity: [keyArray count]];
	NSEnumerator* keys = [ keyArray objectEnumerator];
        id key;
	while ( nil != (key = [keys nextObject]) )
	{
//                NSLog([NSString stringWithFormat: @"default dict entry %@ %@", key, [self valueForKey: key]]);
		[ theDictionary setObject: [self valueForKey: key] forKey: key];
	}
	return theDictionary;
}

-(NSDictionary*) findUserDefaults
{
	NSUserDefaults* defs = [NSUserDefaults standardUserDefaults];
    NSDictionary *appDefaults = [self findAppDefaults];
	NSDictionary* dict = [defs objectForKey: CS_DefaultsName ];
    if (dict == nil )
    {
        // Scan for older versions
        int i = 0;
        for (; i < CS_NUM_PREV_VERSIONS; ++i)
        {
            if ((dict = [defs objectForKey:CS_PREV_VERSIONS[i]]))
                break;
        }

        if (dict)
        {
            [self upgradeUserDefaults: dict fromVersion: CS_PREV_VERSIONS[i]];
        }
        else
        {
            dict = appDefaults;
        }
    }

    if (appDefaults)
        [defs registerDefaults: [NSDictionary dictionaryWithObject: appDefaults forKey: CS_DefaultsName]];

    return dict;
}

-(NSDictionary *) findAppDefaults
{
    NSDictionary *appDefaults = nil;
    NSString *appDefaultsFile = [[NSBundle mainBundle] pathForResource:CS_AppDefaults ofType:@"plist"];

    if (appDefaultsFile)
    {
        appDefaults = [NSDictionary dictionaryWithContentsOfFile: appDefaultsFile];
    }

    return appDefaults ? [appDefaults objectForKey: CS_DefaultsName] : nil;
}

-(void) loadUserDefaults 
{
	id key;
	NSDictionary* defaultsDictionary = [self findUserDefaults ];
	if ( defaultsDictionary == nil ) return;
	NSEnumerator* keys = [ defaultsDictionary keyEnumerator];
	while ( nil != (key = [keys nextObject]) )
	{
		[self setValue: [defaultsDictionary objectForKey: key] forKey: key];
	}
}

-(void) loadAppDefaults
{
    NSUserDefaults *userDefaults = nil;
    NSDictionary *appDefaults = [self findAppDefaults];

    if (appDefaults)
    {
        // Replace previous settings with defaults, but
        // preserves favorites
        userDefaults = [NSUserDefaults standardUserDefaults];
        [userDefaults setObject: appDefaults forKey: CS_DefaultsName];
        [userDefaults synchronize];
        [self loadUserDefaults];
    }
}

-(void) storeUserDefaults 
{
//        NSLog(@"storing user defaults");
	NSUserDefaults* defs = [NSUserDefaults standardUserDefaults];
	[defs setObject: [self defaultsDictionary] forKey: CS_DefaultsName];
	[defs synchronize];
//        NSLog(@"stored user defaults");
}

-(void) upgradeUserDefaults: (NSDictionary *)dict fromVersion: (NSString *)old
{
    NSUserDefaults* defs = [NSUserDefaults standardUserDefaults];
    [defs setObject: dict forKey: CS_DefaultsName];
    [defs removeObjectForKey: old];
    [defs synchronize];
}

-(id) valueForTag: (NSInteger) tag { 
    return [self valueForKey: [tagDict objectForKey: [NSNumber numberWithInteger: tag] ] ];
}

-(void) setValue: (id) value forTag: (NSInteger) tag
{
    id key = [tagDict objectForKey: [NSNumber numberWithInteger: tag] ];
    if (key!= nil)
    {
        [self setValue: value forKey: key ];
    }
}

- (id)valueForUndefinedKey: (NSString *) key
{
#ifdef DEBUG
    if ( key ) NSLog(@"unbound value for %@", key);
#endif
    return nil;
}

- (void)setValue: (id) value forUndefinedKey: (NSString *) key
{
#ifdef DEBUG
    NSLog(@"unbound key set for %@", key);
#endif
}

- (void)setNilValueForKey: (NSString *) key
{
#ifdef DEBUG
    NSLog(@"nil value for %@", key);
#endif
}

// Time Settings

-(double) time { return appCore->getSimulation()->getTime(); }
-(void) setTime: (double) value { appCore->getSimulation()->setTime(value); }

-(double) timeScale { return appCore->getSimulation()->getTimeScale(); }
-(void) setTimeScale: (double) value { appCore->getSimulation()->setTimeScale(value); }

-(BOOL) synchTime { return appCore->getSimulation()->getSyncTime(); }
-(void) setSynchTime: (BOOL) value { appCore->getSimulation()->setSyncTime(value); }

// Gaze Settings

-(float) fieldOfView { return appCore->getSimulation()->getObserver().getFOV(); }
-(void)  setFieldOfView: (float) value { appCore->getSimulation()->getObserver().setFOV(value); }

// Observer Settings

// Cruise Settings

// Velocity
// AngularVelocity

-(int) setValue: (BOOL) value forBit: (int) bit inSet: (int) set
{
    int result = value
        ? ((bit&set) ? set : (set|bit) )
        : ((bit&set) ?  (set^bit) : set);
//    NSLog([NSString stringWithFormat: @"setValue %d forBit: %d inSet: %d = %d",value,bit,set,result]);
//    NSLog([NSString stringWithFormat: @"bit was: %d bit is: %d ",(set&bit),(result&bit)]);
    return result;
}

// Visibility Settings

#define RENDERMETHODS(flag)  -(BOOL) show##flag { return (appCore->getRenderer()->getRenderFlags()&Renderer::Show##flag) != 0; } -(void) setShow##flag: (BOOL) value  { appCore->getRenderer()->setRenderFlags( [self setValue: value forBit: Renderer::Show##flag inSet: appCore->getRenderer()->getRenderFlags() ] ); } 

RENDERMETHODS(Stars)
RENDERMETHODS(Planets)
RENDERMETHODS(Galaxies)
RENDERMETHODS(Globulars)
RENDERMETHODS(Nebulae)
RENDERMETHODS(OpenClusters)
RENDERMETHODS(Diagrams)
RENDERMETHODS(Boundaries)
RENDERMETHODS(CloudMaps)
RENDERMETHODS(NightMaps)
RENDERMETHODS(Atmospheres)
RENDERMETHODS(CometTails)
RENDERMETHODS(Markers)
RENDERMETHODS(Orbits)
RENDERMETHODS(PartialTrajectories)
RENDERMETHODS(SmoothLines)
RENDERMETHODS(EclipseShadows)
RENDERMETHODS(RingShadows)
RENDERMETHODS(CloudShadows)
RENDERMETHODS(AutoMag)
RENDERMETHODS(CelestialSphere)
RENDERMETHODS(EclipticGrid)
RENDERMETHODS(HorizonGrid)
RENDERMETHODS(GalacticGrid)

// Label Settings

#define LABELMETHODS(flag)  -(BOOL) show##flag##Labels { return (appCore->getRenderer()->getLabelMode()&Renderer::flag##Labels) != 0; } -(void) setShow##flag##Labels : (BOOL) value  { appCore->getRenderer()->setLabelMode( [self setValue: value forBit: Renderer::flag##Labels inSet: appCore->getRenderer()->getLabelMode()] ); } 

LABELMETHODS(Star)
LABELMETHODS(Planet)
LABELMETHODS(Moon)
LABELMETHODS(Constellation)
LABELMETHODS(Galaxy)
LABELMETHODS(Globular)
LABELMETHODS(Nebula)
LABELMETHODS(OpenCluster)
LABELMETHODS(Asteroid)
LABELMETHODS(Spacecraft)
LABELMETHODS(Location)
LABELMETHODS(Comet)
LABELMETHODS(DwarfPlanet)
LABELMETHODS(MinorMoon)

-(BOOL) showLatinConstellationLabels {
    return (appCore->getRenderer()->getLabelMode() & Renderer::I18nConstellationLabels) == 0;
}
-(void) setShowLatinConstellationLabels : (BOOL) value {
    appCore->getRenderer()->setLabelMode( [self setValue: (!value) forBit:  Renderer::I18nConstellationLabels inSet: appCore->getRenderer()->getLabelMode()] );
}

// Orbit Settings

#define ORBITMETHODS(flag)  -(BOOL) show##flag##Orbits { return (appCore->getRenderer()->getOrbitMask()&Body::flag) != 0; } -(void) setShow##flag##Orbits: (BOOL) value  { appCore->getRenderer()->setOrbitMask([self setValue: value forBit: Body::flag inSet: appCore->getRenderer()->getOrbitMask()]); } 

ORBITMETHODS(Planet)
ORBITMETHODS(Moon)
ORBITMETHODS(Asteroid)
ORBITMETHODS(Spacecraft)
ORBITMETHODS(Comet)
ORBITMETHODS(Stellar)
ORBITMETHODS(DwarfPlanet)
ORBITMETHODS(MinorMoon)


-(float) minimumOrbitSize { return appCore->getRenderer()->getMinimumOrbitSize(); }
-(void)  setMinimumOrbitSize: (float) value { appCore->getRenderer()->setMinimumOrbitSize(value); }

// Feature Settings

#define FEATUREMETHODS(flag)  -(BOOL) show##flag##Labels { return (appCore->getSimulation()->getObserver().getLocationFilter()&Location::flag) != 0; } -(void) setShow##flag##Labels: (BOOL) value  { appCore->getSimulation()->getObserver().setLocationFilter([self setValue: value forBit: Location::flag inSet: appCore->getSimulation()->getObserver().getLocationFilter()]); } 

FEATUREMETHODS(City)
FEATUREMETHODS(Observatory)
FEATUREMETHODS(LandingSite)
FEATUREMETHODS(Crater)
FEATUREMETHODS(Vallis)
FEATUREMETHODS(Mons)
FEATUREMETHODS(Planum)
FEATUREMETHODS(Chasma)
FEATUREMETHODS(Patera)
FEATUREMETHODS(Mare)
FEATUREMETHODS(Rupes)
FEATUREMETHODS(Tessera)
FEATUREMETHODS(Regio)
FEATUREMETHODS(Chaos)
FEATUREMETHODS(Terra)
FEATUREMETHODS(Astrum)
FEATUREMETHODS(Corona)
FEATUREMETHODS(Dorsum)
FEATUREMETHODS(Fossa)
FEATUREMETHODS(Catena)
FEATUREMETHODS(Mensa)
FEATUREMETHODS(Rima)
FEATUREMETHODS(Undae)
FEATUREMETHODS(Reticulum)
FEATUREMETHODS(Planitia)
FEATUREMETHODS(Linea)
FEATUREMETHODS(Fluctus)
FEATUREMETHODS(Farrum)
FEATUREMETHODS(EruptiveCenter)
FEATUREMETHODS(Other)

-(float) minimumFeatureSize { return appCore->getRenderer()->getMinimumFeatureSize(); }
-(void)  setMinimumFeatureSize: (float) value { appCore->getRenderer()->setMinimumFeatureSize(value); }

// Refmark Settings

-(BOOL) showBodyAxes
{
    return appCore->referenceMarkEnabled("body axes");
}

-(void) setShowBodyAxes: (BOOL) value
{
    appCore->toggleReferenceMark("body axes");
}

-(BOOL) showFrameAxes
{
    return appCore->referenceMarkEnabled("frame axes");
}

-(void) setShowFrameAxes: (BOOL) value
{
    appCore->toggleReferenceMark("frame axes");
}

-(BOOL) showSunDirection
{
    return appCore->referenceMarkEnabled("sun direction");
}

-(void) setShowSunDirection: (BOOL) value
{
    appCore->toggleReferenceMark("sun direction");
}

-(BOOL) showVelocityVector
{
    return appCore->referenceMarkEnabled("velocity vector");
}

-(void) setShowVelocityVector: (BOOL) value
{
    appCore->toggleReferenceMark("velocity vector");
}

-(BOOL) showPlanetographicGrid
{
    return appCore->referenceMarkEnabled("planetographic grid");
}

-(void) setShowPlanetographicGrid: (BOOL) value
{
    appCore->toggleReferenceMark("planetographic grid");
}

-(BOOL) showTerminator
{
    return appCore->referenceMarkEnabled("terminator");
}

-(void) setShowTerminator: (BOOL) value
{
    appCore->toggleReferenceMark("terminator");
}


// Lighting Settings

-(float) ambientLightLevel { return appCore->getRenderer()->getAmbientLightLevel(); }
-(void)  setAmbientLightLevel: (float) value { appCore->getRenderer()->setAmbientLightLevel(value); }

-(float) galaxyBrightness { return Galaxy::getLightGain(); }
-(void)  setGalaxyBrightness: (float) value { Galaxy::setLightGain(value); }

// Star Settings

-(float) distanceLimit { return appCore->getRenderer()->getDistanceLimit(); }
-(void)  setDistanceLimit: (float) value { appCore->getRenderer()->setDistanceLimit(value); }

-(float) faintestVisible 
{ 
//    return appCore->getSimulation()->getFaintestVisible(); 
    if ((appCore->getRenderer()->getRenderFlags() & Renderer::ShowAutoMag) == 0)
    {
        return appCore->getSimulation()->getFaintestVisible();
    }
    else
    {
        return appCore->getRenderer()->getFaintestAM45deg();
    }                
}

-(void)  setFaintestVisible: (float) value 
{
    if ((appCore->getRenderer()->getRenderFlags() & Renderer::ShowAutoMag) == 0)
    {
        appCore->setFaintest(value);
    }
    else
    {
        appCore->getRenderer()->setFaintestAM45deg(value);
        appCore->setFaintestAutoMag();
    }                
}

-(int)  starStyle { return appCore->getRenderer()->getStarStyle(); }
-(void) setStarStyle: (int) value { appCore->getRenderer()->setStarStyle((Renderer::StarStyle)value); }

// Texture Settings

-(int)  resolution { return appCore->getRenderer()->getResolution(); }
-(void) setResolution: (int) value { appCore->getRenderer()->setResolution(value); }

// Overlay Settings

-(int)  hudDetail { return appCore->getHudDetail(); }
-(void) setHudDetail: (int) value { appCore->setHudDetail(value); }

// Time settings

// Timezone values are inverted to maintain backward compatibility
-(int)  timeZone { return appCore->getTimeZoneBias()==0 ? 1 : 0; }
-(void) setTimeZone: (int) value
{
    appCore->setTimeZoneBias(0==value ? 1 : 0);
}

-(int)  dateFormat { return appCore->getDateFormat(); }
-(void) setDateFormat: (int) value { appCore->setDateFormat((astro::Date::Format)value); }

// Other Settings

-(int)  renderPath { return appCore->getRenderer()->getGLContext()->getRenderPath(); }
-(void) setRenderPath: (int) value { appCore->getRenderer()->getGLContext()->setRenderPath((GLContext::GLRenderPath)value); }

// Alt Surface Setting

- (NSUInteger) altSurface
{
    // return alternate surface index
    string displayedSurfName = appCore->getSimulation()->getActiveObserver()->getDisplayedSurface();
    CelestiaSelection *sel = [[[CelestiaAppCore sharedAppCore] simulation] selection];
    if ([sel body])
    {
        NSArray *surfaces = [[sel body] alternateSurfaceNames];
        if ( surfaces && [surfaces count] > 0 )
        {
            NSString *displayedSurfStr = [NSString stringWithStdString: displayedSurfName];
            NSUInteger index = [surfaces indexOfObject: displayedSurfStr];
            if (index != NSNotFound)
                return index+1;
        }
    }
    return 0;
}

- (void) setAltSurface: (int) index
{
    // Handle the alternate surface submenu
    CelestiaSelection *sel = [[[CelestiaAppCore sharedAppCore] simulation] selection];
    if ([sel body])
    {
        if ( index == 0 )
        {
            appCore->getSimulation()->getActiveObserver()->setDisplayedSurface(string(""));
            return;
        }
        NSArray *surfaces = [[sel body] alternateSurfaceNames];
        if ( surfaces && [surfaces count] > 0 )
        {
            string surfName;
            index--;
            if (index >= 0 && (unsigned)index < [surfaces count])
                surfName = [[surfaces objectAtIndex: index] stdString];
            appCore->getSimulation()->getActiveObserver()->setDisplayedSurface(surfName);
        }
    }
}


// GUI Tag Methods ----------------------------------------------------------


- (void) selectPopUpButtonItem: (id) item withIndex: (NSInteger) index
{
    id item2 = [ tagMap objectForKey: [NSNumber numberWithInteger: ([item tag]-index) ]];
    if ([item2 isKindOfClass: [NSPopUpButton class]])
    {
        NSInteger popUpIndex = [item2 indexOfItemWithTag: [item tag]];
        if (popUpIndex >= 0)
            [item2 selectItemAtIndex: popUpIndex];
    }
}

- (void) actionForItem: (id) item
{
    NSInteger tag = [item tag];
    NSInteger valueTag = tag;
    id value;

    if ( tag <= 128 ) tag = [self tagForKey: tag ]; // handle menu item for setting; obsolete?
    if ( tag <= 128 ) { appCore->charEntered(tag); return; }
    switch ( tag/100)
    {
        case 4: case 5: case 7: case 8: case 10: // 400, 500, 700, 800, 1000
            [self setValue: [NSNumber numberWithInteger: (1-[[self valueForTag: tag] integerValue])] forTag: tag ];
        break;
        case 6: // 600
        case 11: // 1100
            valueTag = (tag / 100 == 6) ? (tag / 10 * 10) : (tag / 100 * 100);
            value = [NSNumber numberWithInteger:(tag / 100 == 6) ? (tag % 10) : (tag % 100)];
            [self setValue:value forTag:valueTag];
            [self selectPopUpButtonItem:item withIndex:[value integerValue]];
            //[self setPopUpButtonWithTag: ((tag/10)*10) toIndex: [value intValue]];
            if ([[tagDict objectForKey:[NSNumber numberWithInteger:valueTag]] isEqualToString: @"renderPath"])
            {
                if ([[self valueForTag:valueTag] integerValue] != [value integerValue])
                    [[control valueForKey:@"renderPanelController"] performSelector:@selector(displayRenderPathWarning:) withObject:[item title]];
                else
                    [[control valueForKey:@"renderPanelController"] performSelector:@selector(hideRenderPathWarning)];
            }
        break;
        case 9: // 900
            [self setValue: [NSNumber numberWithFloat: [item floatValue]] forTag: tag];
        break;
    }
}

- (BOOL) validateButton: (id) item atIndex: (int) index withValue: (int) value 
{
    if ( (index==0) && [item isKindOfClass: [NSPopUpButton class] ] )
    {
        NSEnumerator* items = [ [item itemArray] objectEnumerator ];
        id menuItem;
        while (menuItem = [items nextObject])
        {
            if ( [menuItem tag] == ([item tag]+value) )
            {
                [item selectItem: menuItem];
                break;
            }
        }
    }
    else
        [item setState:  (value == index) ? NSOnState: NSOffState ];
    return YES;
}


- (BOOL) validateItem: (id) item
{
    NSInteger tag = [item tag];
    if ( tag <= 128 ) tag = [self tagForKey: tag ];
    if ( tag == 32 ) 
    {
//        [item setState:  (fabs(appCore->getSimulation()->getTimeScale()) == 0.0 ) ? NSOnState : NSOffState ]; 
    }
    else if ( tag <= 128 ) 
    {
        [item setState: NSOffState ];
    }
    else 
    {
        switch ( tag/100)
        {
		case 4: case 5: case 7: case 8: case 10:
		// 400s, 500s, 700s, 800s, 1000s (checkboxes)
            [item setState: [[self valueForTag: tag] intValue] ? NSOnState : NSOffState ];
		break;
		case 6:
		// 600s (popups)
            [self validateButton: item atIndex: tag%10 withValue: [[self valueForTag: tag-(tag%10)] intValue] ];
		break;
		case 9:
		// 900s (floats)
            [item setFloatValue: [[self valueForTag: tag] floatValue] ];                
		break;
        case 11:
        // 1100s (alt surface, menu item)
            {
                NSUInteger index = tag - 1100;
                [item setState:([self altSurface] == index) ? NSOnState: NSOffState];
            }
        }
    }
    return YES;
}


- (void) defineKeyForItem: (id) item
{
    NSInteger tag = [item tag];
    if ( tag != 0 )
    {
        NSNumber* itemKey = [NSNumber numberWithInteger: tag ];
        if ( [ tagMap objectForKey: itemKey ] == 0 )
        {
            [tagMap setObject: item forKey: itemKey];
        }
    }
}


- (NSInteger) tagForKey: (NSInteger) key
{
    NSInteger tag;
    switch (key)
    {
            case 112: tag = 501; break;  // LabelPlanets
            case 109: tag = 502; break;  // LabelMoons
            case 119: tag = 505; break;  // LabelAsteroids
            case  98: tag = 500; break;  // LabelStars
            case 101: tag = 504; break;  // LabelGalaxies
            case 110: tag = 506; break;  // LabelSpacecraft
            case  87: tag = 508; break;  // LabelComets
            case  61: tag = 503; break;  // LabelConstellations
            case 105: tag = 404; break;  // CloudMaps
            case   1: tag = 408; break;  // Atmospheres
            case  12: tag = 407; break;  // NightMaps
            case   5: tag = 410; break;  // EclipseShadows
            case 111: tag = 405; break;  // Orbits
            case 117: tag = 402; break;  // Galaxies
            case  47: tag = 403; break;  // Diagrams
            case   2: tag = 413; break;  // Boundaries
            case  59: tag = 406; break;  // CelestialSphere
            case  25: tag = 414; break;  // AutoMag
            case  20: tag = 415; break;  // CometTails
            case  11: tag = 416; break;  // Markers
//            case  19: tag = 411; break;  // StarsAsPoints
            case  24: tag = 409; break;  // SmoothLines
            default : tag = key; break; // Special or not a setting
    }
    return tag;
}

- (void) validateItemForTag: (NSInteger) tag
{
    if ( tag <= 128 ) tag = [self tagForKey: tag ];
    id item = [ tagMap objectForKey: [NSNumber numberWithInteger: tag ]];
    if ( [item isKindOfClass: [NSMenuItem class]] ) return; // auto-validated
    if ( item != 0 )
    {
        [ self validateItem: item ];
    }
}

- (void) validateItems
{
    id obj;
    NSEnumerator* enumerator = [[tagMap allKeys] objectEnumerator];
    while ((obj = [enumerator nextObject]) != nil)
    {
        [self validateItemForTag: [obj intValue]];
    }
}


- (BOOL)     validateMenuItem: (id) item
{
//    if ( [startupCondition condition] == 0 ) return NO;
    if ( [item tag] == 0 )
    {
        return [item isEnabled];
    }
    else
    {
        return [self validateItem: item ];
    }
}

- (IBAction) activateMenuItem: (id) item
{
    NSInteger tag = [item tag];
    if ( tag != 0 )
    {
        if ( tag < 0 ) // simulate key press and hold
        {
            [control keyPress: -tag hold: 2];
        } 
        else 
        {
            [self actionForItem: item ];
        }
        [self validateItemForTag: tag];
    }
}

- (IBAction) activateSwitchButton: (id) item
{
    NSInteger tag = [item tag];
    if ( tag != 0 )
    {
        [self actionForItem: item];
        [control forceDisplay]; 
    }
}

- (IBAction) activateMatrixButton: (id) item
{
    item = [item selectedCell];
    NSInteger tag = [item tag];
    if ( tag != 0 )
    {
        [self actionForItem: item];
    }
}

- (void) scanForKeys: (id) item
{
//    NSLog(@"scanning item %@\n", [item description]);

    if ( [item isKindOfClass: [NSTabViewItem class]] )
    {
//	 NSLog(@"scanning viewitem\n");
        item = [item view];
        [ self scanForKeys: item ];
        return;
    }


    if ([item isKindOfClass: [NSMenuItem class]] && [item tag] != 0)
        {
//	 NSLog(@"scanning menuItem\n");
            [self defineKeyForItem: item];
            [item setTarget: self];
            [item setAction: @selector(activateMenuItem:)];
            return; 
        }
    else if ([item isKindOfClass: [NSSlider class]] && [item tag] != 0)
        {
//            NSLog(@"scanning cell\n");
            [self defineKeyForItem: item];
            [item setTarget: self];
            [item setAction: @selector(activateSwitchButton:)];
            return; 
        }

    if ( [item isKindOfClass: [NSTabView class]] )
    {
//	 NSLog(@"scanning tabview\n");
        item = [ [item tabViewItems] objectEnumerator ];
    }
    else if ( [item isKindOfClass: [NSPopUpButton class]] )
    {
        [self defineKeyForItem: item];
//	 NSLog(@"scanning popupbutton\n");
        item = [ [item itemArray] objectEnumerator ];
    }
    else if ([item isKindOfClass: [NSControl class]] && [item tag] != 0)
        {
//	 NSLog(@"scanning control\n");
            [self defineKeyForItem: item];
            [item setTarget: self];
            [item setAction: @selector(activateSwitchButton:)];
            return; 
        }
    else if ( [item isKindOfClass: [NSMatrix class]] )
    {
//	 NSLog(@"scanning matrix\n");
        item = [ [item cells] objectEnumerator ];
    }
    else if ( [item isKindOfClass: [NSView class]] )
    {
//	 NSLog(@"scanning view\n");
//        NSLog(@"subviews items = %@\n", [item subviews]);
        item = [ [item subviews] objectEnumerator ];
//        NSLog(@"view items = %@\n", item);
    }

    if ( [item isKindOfClass: [NSEnumerator class] ] )
    {
//	 NSLog(@"scanning array\n");
        id subitem;
        while(subitem = [item nextObject])
        {
//            NSLog(@"scanning array  item\n");
            [ self scanForKeys: subitem ];
        }
        return;
    }

    if ([item isKindOfClass: [NSCell class]] && [item tag] != 0)
        {
//	 NSLog(@"scanning cell\n");
            [self defineKeyForItem: item];
            [item setTarget: self];
            [item setAction: @selector(activateMatrixButton:)];
            return; 
        }

    if ( [item isKindOfClass: [NSWindow class]] )
    {
//        NSLog(@"scanning window\n");
        item = [item contentView ];
        [ self scanForKeys: item ];
        return;
    }
}

@end
