//
//  BrowserWindowController.mm
//  celestia
//
//  Created by Hank Ramsey on Tue Dec 28 2004.
//  Copyright (c) 2002 Chris Laurel. All rights reserved.
//  Modifications Copyright (c) 2004 Hank Ramsey. All rights reserved.
//

#import "BrowserWindowController.h"
#import "BrowserItem.h"
#import "NSString_ObjCPlusPlus.h"
#import "CelestiaAppCore.h"
#import "CelestiaStar_PrivateAPI.h"
#import "CelestiaBody_PrivateAPI.h"
#import "CelestiaDSO.h"
#import "CelestiaLocation.h"

#include "celestiacore.h"
#include "celestia.h"
#include "selection.h"
#include "starbrowser.h"

#define BROWSER_MAX_DSO_COUNT   300
#define BROWSER_MAX_STAR_COUNT  100


@interface BrowserWindowController(Private)
- (NSDictionary *) deepSkyObjects;
- (NSDictionary *) starsOfKind: (int) kind;
- (BrowserItem *) sol;
@end

@implementation BrowserWindowController

static NSDictionary *rootItems;
static CelestiaCore *appCore;

- (id) init
{
    self = [super initWithWindowNibName: @"BrowserWindow" ];
    if (self)
    {
        appCore = (CelestiaCore*) [[CelestiaAppCore sharedAppCore] appCore];
        rootId = @"solarSystem";
    }
    return self;
}

- (void) windowDidLoad
{
    if ([self window]==nil) NSLog(@"loaded browser window is nil");
}

//--------------------------------------------------------------

- (NSDictionary *) deepSkyObjects
{
    int objCount;
    int i = 0;
    NSMutableDictionary *countPerType;
    NSMutableDictionary *result;
    NSMutableDictionary *tempDict;
    NSDictionary *typeMap;
    NSMutableDictionary *group;
    DSODatabase* catalog = appCore->getSimulation()->getUniverse()->getDSOCatalog();
    DeepSkyObject *obj;
    CelestiaDSO *dsoWrapper;
    NSString *name;
    NSString *type;

    objCount = catalog->size();
    typeMap = [[NSDictionary alloc] initWithObjectsAndKeys:
        NSLocalizedString(@"Galaxies (Barred Spiral)",@""), @"SB",
        NSLocalizedString(@"Galaxies (Spiral)",@""),        @"S",
        NSLocalizedString(@"Galaxies (Elliptical)",@""),    @"E",
        NSLocalizedString(@"Galaxies (Irregular)",@""),     @"Irr",
        NSLocalizedString(@"Nebulae",@""),                  @"Neb",
        [NSString stringWithUTF8String:_("Globulars")],     @"Glob",
        NSLocalizedString(@"Open Clusters",@""),            @"Clust",
        NSLocalizedString(@"Unknown",@""),                  @"Unknown",
        nil];
    tempDict = [NSMutableDictionary dictionaryWithObjectsAndKeys:
        [NSMutableDictionary dictionary], @"SB",
        [NSMutableDictionary dictionary], @"S",
        [NSMutableDictionary dictionary], @"E",
        [NSMutableDictionary dictionary], @"Irr",
        [NSMutableDictionary dictionary], @"Neb",
        [NSMutableDictionary dictionary], @"Glob",
        [NSMutableDictionary dictionary], @"Clust",
        [NSMutableDictionary dictionary], @"Unknown",
        nil];
    countPerType = [NSMutableDictionary dictionaryWithCapacity: [tempDict count]];
    result = [NSMutableDictionary dictionaryWithCapacity: [tempDict count]];

    for (; i < objCount; ++i)
    {
        obj = catalog->getDSO(i);
        if (obj)
        {
            dsoWrapper = [[CelestiaDSO alloc] initWithDSO: obj];
            type = [dsoWrapper type];
            NSNumber *typeCount = [countPerType objectForKey: type];
            if (nil == typeCount)
            {
                typeCount = [NSNumber numberWithInt: 0];
            }
            if ([typeCount intValue] == BROWSER_MAX_DSO_COUNT) continue;
            [countPerType setObject:[NSNumber numberWithInt:([typeCount intValue]+1)] forKey:type];
            if ([type hasPrefix: @"SB"])
                group = [tempDict objectForKey: @"SB"];
            else if ([type hasPrefix: @"S"])
                group = [tempDict objectForKey: @"S"];
            else if ([type hasPrefix: @"E"])
                group = [tempDict objectForKey: @"E"];
            else if ([type hasPrefix: @"Irr"])
                group = [tempDict objectForKey: @"Irr"];
            else if ([type hasPrefix: @"Neb"])
                group = [tempDict objectForKey: @"Neb"];
            else if ([type hasPrefix: @"Clust"])
                group = [tempDict objectForKey: @"Clust"];
            else if ([type hasPrefix: @"Glob"])
                group = [tempDict objectForKey: @"Glob"];
            else
                group = [tempDict objectForKey: @"Unknown"];

            name = [NSString stringWithStdString: catalog->getDSOName(obj)];
            [group setObject: [[BrowserItem alloc] initWithCelestiaDSO: dsoWrapper]
                      forKey: name];
        }
    }

    NSEnumerator *tempEnum = [tempDict keyEnumerator];
    while ((type = [tempEnum nextObject]))
    {
        if ([(NSDictionary *)[tempDict objectForKey: type] count] > 0)
        {
            [result setObject: [[BrowserItem alloc] initWithName: [typeMap objectForKey: type] children: [tempDict objectForKey: type]]
                       forKey: [typeMap objectForKey: type]];
        }
    }

    return result;       
}

- (BrowserItem *) sol
{
    Selection sol = appCore->getSimulation()->getUniverse()->find("Sol");
    return [[BrowserItem alloc] initWithCelestiaStar: [[CelestiaStar alloc] initWithStar: sol.star()]];
}

- (NSDictionary *) starsOfKind: (int) kind
{
    std::vector<const Star*>* nearStars;
    NSInteger starCount = 0;
    Simulation *sim = appCore->getSimulation();
    StarBrowser* sb = new StarBrowser(sim,kind);
    nearStars = sb->listStars( BROWSER_MAX_STAR_COUNT );
    if (nearStars == nil ) return [NSDictionary dictionary];
    starCount = nearStars->size();            
    NSMutableDictionary* starDict = [NSMutableDictionary dictionaryWithCapacity: starCount+2];
    const Star *aStar;
    NSString *starName;
    NSInteger i;
    for (i=0;i<starCount;i++)
    {
        aStar = (*nearStars)[i];
        starName = [NSString  stringWithStdString: sim->getUniverse()->getStarCatalog()->getStarName(*aStar) ];
        [starDict setObject:
            [[BrowserItem alloc] initWithCelestiaStar: [[CelestiaStar alloc] initWithStar: aStar]]
                     forKey: starName];
    }

    delete sb;
    delete nearStars;
    return starDict;
}

-(BrowserItem *) root
{
    if (rootItems) return [rootItems objectForKey: rootId];

    BrowserItem *sol;
    BrowserItem *stars;
    BrowserItem *dsos;

    sol = [self sol];
    [BrowserItem addChildrenToStar: sol];

    stars = [[BrowserItem alloc] initWithName: @""];
    [stars addChild: [[BrowserItem alloc] initWithName:
        NSLocalizedString(@"Nearest Stars",@"")      children:
        [self starsOfKind: StarBrowser::NearestStars]]];
    [stars addChild: [[BrowserItem alloc] initWithName:
        NSLocalizedString(@"Brightest Stars",@"")    children:
        [self starsOfKind: StarBrowser::BrighterStars]]];
    [stars addChild: [[BrowserItem alloc] initWithName:
        NSLocalizedString(@"Stars With Planets",@"") children:
        [self starsOfKind: StarBrowser::StarsWithPlanets]]];

    dsos = [[BrowserItem alloc] initWithName: @"" children: [self deepSkyObjects]];

    rootItems = [[NSDictionary alloc] initWithObjectsAndKeys:
        sol,   @"solarSystem",
        stars, @"star",
        dsos,  @"dso",
        nil];

    return [rootItems objectForKey: rootId];
}

- (BrowserItem *) itemForPathArray: (NSArray*) pathNames
{
    BrowserItem *lastItem = [self root];
    BrowserItem *nextItem = lastItem;
    NSString *lastKey = nil;
    id body;
    unsigned i;
    for (i=1;i<[pathNames count];i++)
    {
        lastKey = [pathNames objectAtIndex: i];
        nextItem = [lastItem childNamed: lastKey];
        if (nil==nextItem) break;
        lastItem = nextItem;
    }

    if (nextItem)
    {
        body = [nextItem body];
        if (body)
        {
            if      ([body respondsToSelector: @selector(star)])
                [BrowserItem addChildrenToStar: nextItem];
            else if ([body respondsToSelector: @selector(body)])
                [BrowserItem addChildrenToBody: nextItem];
        }
    }
    
    return lastItem;
}

- (Selection *) selFromPathArray: (NSArray*) pathNames
{
    Selection *sel = NULL;
    BrowserItem *item = [self itemForPathArray: pathNames];
    id body = [item body];
    if (body)
    {
        if ([body respondsToSelector: @selector(star)])
            sel = new Selection([(CelestiaStar *)body star]);
        else if ([body respondsToSelector: @selector(body)])
            sel = new Selection([(CelestiaBody *)body body]);
        else if ([body respondsToSelector: @selector(DSO)])
            sel = new Selection([(CelestiaDSO *)body DSO]);
        else if ([body respondsToSelector: @selector(location)])
            sel = new Selection([(CelestiaLocation *)body location]);
    }
    return sel;
}

- (NSInteger) browser: (NSBrowser*) sender numberOfRowsInColumn: (NSInteger) column
{
    if (browser != sender)
    {
        browser = sender;
//        if ([browser respondsToSelector:@selector(setColumnResizingType:)])
//            [browser setColumnResizingType: 2];
//        [browser setMinColumnWidth: 80];
        [browser setDoubleAction:@selector(doubleClick:)];
    }

    BrowserItem *itemForColumn = [self itemForPathArray: [[sender pathToColumn: column ] componentsSeparatedByString: [sender pathSeparator] ] ];
    return [itemForColumn childCount];
}

- (BOOL) isLeaf: (BrowserItem *) aItem
{
    if ([aItem childCount] > 0) return NO;
    id body = [aItem body];
    if (body)
    {
        if      ([body respondsToSelector: @selector(star)])
        {
            if (appCore->getSimulation()->getUniverse()->getSolarSystem([(CelestiaStar *)body star]))
                return NO;
        }
        else if ([body respondsToSelector: @selector(body)])
        {
            if ([(CelestiaBody *)body body]->getSatellites() || [(CelestiaBody *)body body]->getLocations())
                return NO;
        }
    }
    return YES;
}

- (void) browser: (NSBrowser*) sender willDisplayCell: (id) cell atRow: (NSInteger) row column: (NSInteger) column
{
    BrowserItem *itemForColumn = [self itemForPathArray: [[sender pathToColumn: column ] componentsSeparatedByString: [sender pathSeparator] ] ];
    NSArray* colKeys = [itemForColumn allChildNames];
    BOOL isLeaf = YES;

    if (row >= (NSInteger)[colKeys count])
        return;

    NSString* itemName = [colKeys objectAtIndex: row];

    if (!itemName)
        itemName = @"????";
    else
        isLeaf = [self isLeaf: [itemForColumn childNamed: itemName ]];

    NSRange rightParenRange = {NSNotFound, 0};
    NSRange leftParenRange  = {NSNotFound, 0};
    NSRange parenRange      = {NSNotFound, 0};

    if (isLeaf)
    {
        rightParenRange = [itemName rangeOfString:@")"
                                          options:NSBackwardsSearch];
        if (rightParenRange.length == 1)
        {
            leftParenRange = NSMakeRange(0, rightParenRange.location - 1);
            parenRange = [itemName rangeOfString:@"("
                                         options:NSBackwardsSearch
                                           range:leftParenRange];
            if (parenRange.length == 1)
            {
                parenRange.length = rightParenRange.location - parenRange.location + 1;
            }
        }
    }

    if (parenRange.location != NSNotFound)
    {
        NSDictionary *parenAttr = [NSDictionary dictionaryWithObjectsAndKeys:
            [NSColor grayColor], NSForegroundColorAttributeName,
            nil];
        NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString: itemName];
        [attrStr setAttributes:parenAttr range:parenRange];

        [cell setAttributedStringValue:attrStr];
    }
    else
    {
        [cell setTitle: itemName ];
    }

    [cell setLeaf: isLeaf];
}

- (IBAction) go: (id) sender
{
    Selection *sel = [self selFromPathArray: [[browser path] componentsSeparatedByString: [browser pathSeparator] ]];
    if (sel)
    {
        appCore->getSimulation()->setSelection(*sel);
        if ([sender tag]!=0) appCore->charEntered([sender tag]);
        delete sel;
    }
}

- (void) doubleClick: (id) sender
{
    browser = sender;
    NSArray *pathArray = [[browser path] componentsSeparatedByString: [browser pathSeparator]];
    Selection *sel = [self selFromPathArray: pathArray];
    if (sel)
    {
        appCore->getSimulation()->setSelection(*sel);
        appCore->charEntered('g');
        delete sel;
    }
}

- (void)tabView: (NSTabView *)aTabView didSelectTabViewItem: (NSTabViewItem *)aTabViewItem
{
    rootId = [aTabViewItem identifier];
}

- (void)windowDidBecomeKey:(NSNotification *)aNotification
{
    if ([aNotification object] == [self window])
    {
        NSTabViewItem *curTab = [tabView selectedTabViewItem];
        if (nil == curTab) return;
        rootId = [curTab identifier];

        if (browser && [rootId isEqualToString: @"star"])
        {
            BrowserItem *rootItem = [self root];
            [rootItem addChild: [[BrowserItem alloc] initWithName:
                NSLocalizedString(@"Nearest Stars",@"")   children:
                [self starsOfKind: StarBrowser::NearestStars]]];

            // Immediately synch browser display with reloaded star list,
            // but only if star list is showing
            NSArray *selection = [[browser path] componentsSeparatedByString: [browser pathSeparator]];
            if ([selection count]>1 && [[selection objectAtIndex: 1] isEqualToString: NSLocalizedString(@"Nearest Stars",@"")])
            {
                for (int i=1; i<[browser numberOfVisibleColumns]; ++i)
                    [browser reloadColumn: i];
            }
        }
    }
}
@end
